]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.2.1
[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       elem = elem.offsetParent;
579     }
580     return position;
581   }
582
583   function getScrolls(elem) {
584     var position = {
585       x: 0,
586       y: 0
587     };
588     while (elem && !isBody(elem)) {
589       position.x += elem.scrollLeft;
590       position.y += elem.scrollTop;
591       elem = elem.parentNode;
592     }
593     return position;
594   }
595
596   function isBody(element) {
597     return (/^(?:body|html)$/i).test(element.tagName);
598   }
599 };
600
601 $.event = {
602   get: function(e, win) {
603     win = win || window;
604     return e || win.event;
605   },
606   getWheel: function(e) {
607     return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
608   },
609   isRightClick: function(e) {
610     return (e.which == 3 || e.button == 2);
611   },
612   getPos: function(e, win) {
613     // get mouse position
614     win = win || window;
615     e = e || win.event;
616     var doc = win.document;
617     doc = doc.documentElement || doc.body;
618     //TODO(nico): make touch event handling better
619     if(e.touches && e.touches.length) {
620       e = e.touches[0];
621     }
622     var page = {
623       x: e.pageX || (e.clientX + doc.scrollLeft),
624       y: e.pageY || (e.clientY + doc.scrollTop)
625     };
626     return page;
627   },
628   stop: function(e) {
629     if (e.stopPropagation) e.stopPropagation();
630     e.cancelBubble = true;
631     if (e.preventDefault) e.preventDefault();
632     else e.returnValue = false;
633   }
634 };
635
636 $jit.util = $jit.id = $;
637
638 var Class = function(properties) {
639   properties = properties || {};
640   var klass = function() {
641     for ( var key in this) {
642       if (typeof this[key] != 'function')
643         this[key] = $.unlink(this[key]);
644     }
645     this.constructor = klass;
646     if (Class.prototyping)
647       return this;
648     var instance = this.initialize ? this.initialize.apply(this, arguments)
649         : this;
650     //typize
651     this.$$family = 'class';
652     return instance;
653   };
654
655   for ( var mutator in Class.Mutators) {
656     if (!properties[mutator])
657       continue;
658     properties = Class.Mutators[mutator](properties, properties[mutator]);
659     delete properties[mutator];
660   }
661
662   $.extend(klass, this);
663   klass.constructor = Class;
664   klass.prototype = properties;
665   return klass;
666 };
667
668 Class.Mutators = {
669
670   Implements: function(self, klasses) {
671     $.each($.splat(klasses), function(klass) {
672       Class.prototyping = klass;
673       var instance = (typeof klass == 'function') ? new klass : klass;
674       for ( var prop in instance) {
675         if (!(prop in self)) {
676           self[prop] = instance[prop];
677         }
678       }
679       delete Class.prototyping;
680     });
681     return self;
682   }
683
684 };
685
686 $.extend(Class, {
687
688   inherit: function(object, properties) {
689     for ( var key in properties) {
690       var override = properties[key];
691       var previous = object[key];
692       var type = $.type(override);
693       if (previous && type == 'function') {
694         if (override != previous) {
695           Class.override(object, key, override);
696         }
697       } else if (type == 'object') {
698         object[key] = $.merge(previous, override);
699       } else {
700         object[key] = override;
701       }
702     }
703     return object;
704   },
705
706   override: function(object, name, method) {
707     var parent = Class.prototyping;
708     if (parent && object[name] != parent[name])
709       parent = null;
710     var override = function() {
711       var previous = this.parent;
712       this.parent = parent ? parent[name] : object[name];
713       var value = method.apply(this, arguments);
714       this.parent = previous;
715       return value;
716     };
717     object[name] = override;
718   }
719
720 });
721
722 Class.prototype.implement = function() {
723   var proto = this.prototype;
724   $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725     Class.inherit(proto, properties);
726   });
727   return this;
728 };
729
730 $jit.Class = Class;
731
732 /*
733   Object: $jit.json
734   
735   Provides JSON utility functions.
736   
737   Most of these functions are JSON-tree traversal and manipulation functions.
738 */
739 $jit.json = {
740   /*
741      Method: prune
742   
743      Clears all tree nodes having depth greater than maxLevel.
744   
745      Parameters:
746   
747         tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748         maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
749
750   */
751   prune: function(tree, maxLevel) {
752     this.each(tree, function(elem, i) {
753       if (i == maxLevel && elem.children) {
754         delete elem.children;
755         elem.children = [];
756       }
757     });
758   },
759   /*
760      Method: getParent
761   
762      Returns the parent node of the node having _id_ as id.
763   
764      Parameters:
765   
766         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767         id - (string) The _id_ of the child node whose parent will be returned.
768
769     Returns:
770
771         A tree JSON node if any, or false otherwise.
772   
773   */
774   getParent: function(tree, id) {
775     if (tree.id == id)
776       return false;
777     var ch = tree.children;
778     if (ch && ch.length > 0) {
779       for ( var i = 0; i < ch.length; i++) {
780         if (ch[i].id == id)
781           return tree;
782         else {
783           var ans = this.getParent(ch[i], id);
784           if (ans)
785             return ans;
786         }
787       }
788     }
789     return false;
790   },
791   /*
792      Method: getSubtree
793   
794      Returns the subtree that matches the given id.
795   
796      Parameters:
797   
798         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799         id - (string) A node *unique* identifier.
800   
801      Returns:
802   
803         A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
804
805   */
806   getSubtree: function(tree, id) {
807     if (tree.id == id)
808       return tree;
809     for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810       var t = this.getSubtree(ch[i], id);
811       if (t != null)
812         return t;
813     }
814     return null;
815   },
816   /*
817      Method: eachLevel
818   
819       Iterates on tree nodes with relative depth less or equal than a specified level.
820   
821      Parameters:
822   
823         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824         initLevel - (number) An integer specifying the initial relative level. Usually zero.
825         toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826         action - (function) A function that receives a node and an integer specifying the actual level of the node.
827           
828     Example:
829    (start code js)
830      $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831         alert(node.name + ' ' + depth);
832      });
833    (end code)
834   */
835   eachLevel: function(tree, initLevel, toLevel, action) {
836     if (initLevel <= toLevel) {
837       action(tree, initLevel);
838       if(!tree.children) return;
839       for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840         this.eachLevel(ch[i], initLevel + 1, toLevel, action);
841       }
842     }
843   },
844   /*
845      Method: each
846   
847       A JSON tree iterator.
848   
849      Parameters:
850   
851         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852         action - (function) A function that receives a node.
853
854     Example:
855     (start code js)
856       $jit.json.each(tree, function(node) {
857         alert(node.name);
858       });
859     (end code)
860           
861   */
862   each: function(tree, action) {
863     this.eachLevel(tree, 0, Number.MAX_VALUE, action);
864   }
865 };
866
867
868 /*
869      An object containing multiple type of transformations. 
870 */
871
872 $jit.Trans = {
873   $extend: true,
874   
875   linear: function(p){
876     return p;
877   }
878 };
879
880 var Trans = $jit.Trans;
881
882 (function(){
883
884   var makeTrans = function(transition, params){
885     params = $.splat(params);
886     return $.extend(transition, {
887       easeIn: function(pos){
888         return transition(pos, params);
889       },
890       easeOut: function(pos){
891         return 1 - transition(1 - pos, params);
892       },
893       easeInOut: function(pos){
894         return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895             2 * (1 - pos), params)) / 2;
896       }
897     });
898   };
899
900   var transitions = {
901
902     Pow: function(p, x){
903       return Math.pow(p, x[0] || 6);
904     },
905
906     Expo: function(p){
907       return Math.pow(2, 8 * (p - 1));
908     },
909
910     Circ: function(p){
911       return 1 - Math.sin(Math.acos(p));
912     },
913
914     Sine: function(p){
915       return 1 - Math.sin((1 - p) * Math.PI / 2);
916     },
917
918     Back: function(p, x){
919       x = x[0] || 1.618;
920       return Math.pow(p, 2) * ((x + 1) * p - x);
921     },
922
923     Bounce: function(p){
924       var value;
925       for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926         if (p >= (7 - 4 * a) / 11) {
927           value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
928           break;
929         }
930       }
931       return value;
932     },
933
934     Elastic: function(p, x){
935       return Math.pow(2, 10 * --p)
936           * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
937     }
938
939   };
940
941   $.each(transitions, function(val, key){
942     Trans[key] = makeTrans(val);
943   });
944
945   $.each( [
946       'Quad', 'Cubic', 'Quart', 'Quint'
947   ], function(elem, i){
948     Trans[elem] = makeTrans(function(p){
949       return Math.pow(p, [
950         i + 2
951       ]);
952     });
953   });
954
955 })();
956
957 /*
958    A Class that can perform animations for generic objects.
959
960    If you are looking for animation transitions please take a look at the <Trans> object.
961
962    Used by:
963
964    <Graph.Plot>
965    
966    Based on:
967    
968    The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
969
970 */
971
972 var Animation = new Class( {
973
974   initialize: function(options){
975     this.setOptions(options);
976   },
977
978   setOptions: function(options){
979     var opt = {
980       duration: 2500,
981       fps: 40,
982       transition: Trans.Quart.easeInOut,
983       compute: $.empty,
984       complete: $.empty,
985       link: 'ignore'
986     };
987     this.opt = $.merge(opt, options || {});
988     return this;
989   },
990
991   step: function(){
992     var time = $.time(), opt = this.opt;
993     if (time < this.time + opt.duration) {
994       var delta = opt.transition((time - this.time) / opt.duration);
995       opt.compute(delta);
996     } else {
997       this.timer = clearInterval(this.timer);
998       opt.compute(1);
999       opt.complete();
1000     }
1001   },
1002
1003   start: function(){
1004     if (!this.check())
1005       return this;
1006     this.time = 0;
1007     this.startTimer();
1008     return this;
1009   },
1010
1011   startTimer: function(){
1012     var that = this, fps = this.opt.fps;
1013     if (this.timer)
1014       return false;
1015     this.time = $.time() - this.time;
1016     this.timer = setInterval((function(){
1017       that.step();
1018     }), Math.round(1000 / fps));
1019     return true;
1020   },
1021
1022   pause: function(){
1023     this.stopTimer();
1024     return this;
1025   },
1026
1027   resume: function(){
1028     this.startTimer();
1029     return this;
1030   },
1031
1032   stopTimer: function(){
1033     if (!this.timer)
1034       return false;
1035     this.time = $.time() - this.time;
1036     this.timer = clearInterval(this.timer);
1037     return true;
1038   },
1039
1040   check: function(){
1041     if (!this.timer)
1042       return true;
1043     if (this.opt.link == 'cancel') {
1044       this.stopTimer();
1045       return true;
1046     }
1047     return false;
1048   }
1049 });
1050
1051
1052 var Options = function() {
1053   var args = arguments;
1054   for(var i=0, l=args.length, ans={}; i<l; i++) {
1055     var opt = Options[args[i]];
1056     if(opt.$extend) {
1057       $.extend(ans, opt);
1058     } else {
1059       ans[args[i]] = opt;  
1060     }
1061   }
1062   return ans;
1063 };
1064
1065 /*
1066  * File: Options.AreaChart.js
1067  *
1068 */
1069
1070 /*
1071   Object: Options.AreaChart
1072   
1073   <AreaChart> options. 
1074   Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1075   
1076   Syntax:
1077   
1078   (start code js)
1079
1080   Options.AreaChart = {
1081     animate: true,
1082     labelOffset: 3,
1083     type: 'stacked',
1084     selectOnHover: true,
1085     showAggregates: true,
1086     showLabels: true,
1087     filterOnClick: false,
1088     restoreOnRightClick: false
1089   };
1090   
1091   (end code)
1092   
1093   Example:
1094   
1095   (start code js)
1096
1097   var areaChart = new $jit.AreaChart({
1098     animate: true,
1099     type: 'stacked:gradient',
1100     selectOnHover: true,
1101     filterOnClick: true,
1102     restoreOnRightClick: true
1103   });
1104   
1105   (end code)
1106
1107   Parameters:
1108   
1109   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112   selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114   showLabels - (boolean) Default's *true*. Display the name of the slots.
1115   filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116   restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1117   
1118 */
1119   
1120 Options.AreaChart = {
1121   $extend: true,
1122
1123   animate: true,
1124   labelOffset: 3, // label offset
1125   type: 'stacked', // gradient
1126   Tips: {
1127     enable: false,
1128     onShow: $.empty,
1129     onHide: $.empty
1130   },
1131   Events: {
1132     enable: false,
1133     onClick: $.empty
1134   },
1135   selectOnHover: true,
1136   showAggregates: true,
1137   showLabels: true,
1138   filterOnClick: false,
1139   restoreOnRightClick: false
1140 };
1141
1142 /*
1143  * File: Options.Margin.js
1144  *
1145 */
1146
1147 /*
1148   Object: Options.Margin
1149   
1150   Canvas drawing margins. 
1151   
1152   Syntax:
1153   
1154   (start code js)
1155
1156   Options.Margin = {
1157     top: 0,
1158     left: 0,
1159     right: 0,
1160     bottom: 0
1161   };
1162   
1163   (end code)
1164   
1165   Example:
1166   
1167   (start code js)
1168
1169   var viz = new $jit.Viz({
1170     Margin: {
1171       right: 10,
1172       bottom: 20
1173     }
1174   });
1175   
1176   (end code)
1177
1178   Parameters:
1179   
1180   top - (number) Default's *0*. Top margin.
1181   left - (number) Default's *0*. Left margin.
1182   right - (number) Default's *0*. Right margin.
1183   bottom - (number) Default's *0*. Bottom margin.
1184   
1185 */
1186
1187 Options.Margin = {
1188   $extend: false,
1189   
1190   top: 0,
1191   left: 0,
1192   right: 0,
1193   bottom: 0
1194 };
1195
1196 /*
1197  * File: Options.Canvas.js
1198  *
1199 */
1200
1201 /*
1202   Object: Options.Canvas
1203   
1204   These are Canvas general options, like where to append it in the DOM, its dimensions, background, 
1205   and other more advanced options.
1206   
1207   Syntax:
1208   
1209   (start code js)
1210
1211   Options.Canvas = {
1212     injectInto: 'id',
1213     width: false,
1214     height: false,
1215     useCanvas: false,
1216     withLabels: true,
1217     background: false
1218   };  
1219   (end code)
1220   
1221   Example:
1222   
1223   (start code js)
1224   var viz = new $jit.Viz({
1225     injectInto: 'someContainerId',
1226     width: 500,
1227     height: 700
1228   });
1229   (end code)
1230   
1231   Parameters:
1232   
1233   injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234   width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235   height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236   useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237   withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238   background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1239 */
1240
1241 Options.Canvas = {
1242     $extend: true,
1243     
1244     injectInto: 'id',
1245     width: false,
1246     height: false,
1247     useCanvas: false,
1248     withLabels: true,
1249     background: false,
1250     colorStop1: 'rgba(255,255,255,1)',
1251     colorStop2: 'rgba(255,255,255,0)'
1252 };
1253
1254 /*
1255  * File: Options.Tree.js
1256  *
1257 */
1258
1259 /*
1260   Object: Options.Tree
1261   
1262   Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1263   
1264   Syntax:
1265   
1266   (start code js)
1267   Options.Tree = {
1268     orientation: "left",
1269     subtreeOffset: 8,
1270     siblingOffset: 5,
1271     indent:10,
1272     multitree: false,
1273     align:"center"
1274   };
1275   (end code)
1276   
1277   Example:
1278   
1279   (start code js)
1280   var st = new $jit.ST({
1281     orientation: 'left',
1282     subtreeOffset: 1,
1283     siblingOFfset: 5,
1284     multitree: true
1285   });
1286   (end code)
1287
1288   Parameters:
1289     
1290   subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291   siblingOffset - (number) Default's 5. Separation offset between siblings.
1292   orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293   align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294   indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295   multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1296      
1297 */
1298 Options.Tree = {
1299     $extend: true,
1300     
1301     orientation: "left",
1302     subtreeOffset: 8,
1303     siblingOffset: 5,
1304     indent:10,
1305     multitree: false,
1306     align:"center"
1307 };
1308
1309
1310 /*
1311  * File: Options.Node.js
1312  *
1313 */
1314
1315 /*
1316   Object: Options.Node
1317
1318   Provides Node rendering options for Tree and Graph based visualizations.
1319
1320   Syntax:
1321     
1322   (start code js)
1323   Options.Node = {
1324     overridable: false,
1325     type: 'circle',
1326     color: '#ccb',
1327     alpha: 1,
1328     dim: 3,
1329     height: 20,
1330     width: 90,
1331     autoHeight: false,
1332     autoWidth: false,
1333     lineWidth: 1,
1334     transform: true,
1335     align: "center",
1336     angularWidth:1,
1337     span:1,
1338     CanvasStyles: {}
1339   };
1340   (end code)
1341   
1342   Example:
1343   
1344   (start code js)
1345   var viz = new $jit.Viz({
1346     Node: {
1347       overridable: true,
1348       width: 30,
1349       autoHeight: true,
1350       type: 'rectangle'
1351     }
1352   });
1353   (end code)
1354   
1355   Parameters:
1356
1357   overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358   type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359   color - (string) Default's *#ccb*. Node color.
1360   alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361   dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362   height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363   width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364   autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365   autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366   lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367   transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368   align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369   angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370   span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371   CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1372
1373 */
1374 Options.Node = {
1375   $extend: false,
1376   
1377   overridable: false,
1378   type: 'circle',
1379   color: '#ccb',
1380   alpha: 1,
1381   dim: 3,
1382   height: 20,
1383   width: 90,
1384   autoHeight: false,
1385   autoWidth: false,
1386   lineWidth: 1,
1387   transform: true,
1388   align: "center",
1389   angularWidth:1,
1390   span:1,
1391   //Raw canvas styles to be
1392   //applied to the context instance
1393   //before plotting a node
1394   CanvasStyles: {}
1395 };
1396
1397
1398 /*
1399  * File: Options.Edge.js
1400  *
1401 */
1402
1403 /*
1404   Object: Options.Edge
1405
1406   Provides Edge rendering options for Tree and Graph based visualizations.
1407
1408   Syntax:
1409     
1410   (start code js)
1411   Options.Edge = {
1412     overridable: false,
1413     type: 'line',
1414     color: '#ccb',
1415     lineWidth: 1,
1416     dim:15,
1417     alpha: 1,
1418     CanvasStyles: {}
1419   };
1420   (end code)
1421   
1422   Example:
1423   
1424   (start code js)
1425   var viz = new $jit.Viz({
1426     Edge: {
1427       overridable: true,
1428       type: 'line',
1429       color: '#fff',
1430       CanvasStyles: {
1431         shadowColor: '#ccc',
1432         shadowBlur: 10
1433       }
1434     }
1435   });
1436   (end code)
1437   
1438   Parameters:
1439     
1440    overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441    type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442    color - (string) Default's '#ccb'. Edge color.
1443    lineWidth - (number) Default's *1*. Line/Edge width.
1444    alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445    dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446    epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447    CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1448
1449   See also:
1450    
1451    If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1452 */
1453 Options.Edge = {
1454   $extend: false,
1455   
1456   overridable: false,
1457   type: 'line',
1458   color: '#ccb',
1459   lineWidth: 1,
1460   dim:15,
1461   alpha: 1,
1462   epsilon: 7,
1463
1464   //Raw canvas styles to be
1465   //applied to the context instance
1466   //before plotting an edge
1467   CanvasStyles: {}
1468 };
1469
1470
1471 /*
1472  * File: Options.Fx.js
1473  *
1474 */
1475
1476 /*
1477   Object: Options.Fx
1478
1479   Provides animation options like duration of the animations, frames per second and animation transitions.  
1480
1481   Syntax:
1482   
1483   (start code js)
1484     Options.Fx = {
1485       fps:40,
1486       duration: 2500,
1487       transition: $jit.Trans.Quart.easeInOut,
1488       clearCanvas: true
1489     };
1490   (end code)
1491   
1492   Example:
1493   
1494   (start code js)
1495   var viz = new $jit.Viz({
1496     duration: 1000,
1497     fps: 35,
1498     transition: $jit.Trans.linear
1499   });
1500   (end code)
1501   
1502   Parameters:
1503   
1504   clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505   duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506   fps - (number) Default's *40*. Frames per second.
1507   transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1508   
1509   Object: $jit.Trans
1510   
1511   This object is used for specifying different animation transitions in all visualizations.
1512
1513   There are many different type of animation transitions.
1514
1515   linear:
1516
1517   Displays a linear transition
1518
1519   >Trans.linear
1520   
1521   (see Linear.png)
1522
1523   Quad:
1524
1525   Displays a Quadratic transition.
1526
1527   >Trans.Quad.easeIn
1528   >Trans.Quad.easeOut
1529   >Trans.Quad.easeInOut
1530   
1531  (see Quad.png)
1532
1533  Cubic:
1534
1535  Displays a Cubic transition.
1536
1537  >Trans.Cubic.easeIn
1538  >Trans.Cubic.easeOut
1539  >Trans.Cubic.easeInOut
1540
1541  (see Cubic.png)
1542
1543  Quart:
1544
1545  Displays a Quartetic transition.
1546
1547  >Trans.Quart.easeIn
1548  >Trans.Quart.easeOut
1549  >Trans.Quart.easeInOut
1550
1551  (see Quart.png)
1552
1553  Quint:
1554
1555  Displays a Quintic transition.
1556
1557  >Trans.Quint.easeIn
1558  >Trans.Quint.easeOut
1559  >Trans.Quint.easeInOut
1560
1561  (see Quint.png)
1562
1563  Expo:
1564
1565  Displays an Exponential transition.
1566
1567  >Trans.Expo.easeIn
1568  >Trans.Expo.easeOut
1569  >Trans.Expo.easeInOut
1570
1571  (see Expo.png)
1572
1573  Circ:
1574
1575  Displays a Circular transition.
1576
1577  >Trans.Circ.easeIn
1578  >Trans.Circ.easeOut
1579  >Trans.Circ.easeInOut
1580
1581  (see Circ.png)
1582
1583  Sine:
1584
1585  Displays a Sineousidal transition.
1586
1587  >Trans.Sine.easeIn
1588  >Trans.Sine.easeOut
1589  >Trans.Sine.easeInOut
1590
1591  (see Sine.png)
1592
1593  Back:
1594
1595  >Trans.Back.easeIn
1596  >Trans.Back.easeOut
1597  >Trans.Back.easeInOut
1598
1599  (see Back.png)
1600
1601  Bounce:
1602
1603  Bouncy transition.
1604
1605  >Trans.Bounce.easeIn
1606  >Trans.Bounce.easeOut
1607  >Trans.Bounce.easeInOut
1608
1609  (see Bounce.png)
1610
1611  Elastic:
1612
1613  Elastic curve.
1614
1615  >Trans.Elastic.easeIn
1616  >Trans.Elastic.easeOut
1617  >Trans.Elastic.easeInOut
1618
1619  (see Elastic.png)
1620  
1621  Based on:
1622      
1623  Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1624
1625
1626 */
1627 Options.Fx = {
1628   $extend: true,
1629   
1630   fps:40,
1631   duration: 2500,
1632   transition: $jit.Trans.Quart.easeInOut,
1633   clearCanvas: true
1634 };
1635
1636 /*
1637  * File: Options.Label.js
1638  *
1639 */
1640 /*
1641   Object: Options.Label
1642
1643   Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.  
1644
1645   Syntax:
1646   
1647   (start code js)
1648     Options.Label = {
1649       overridable: false,
1650       type: 'HTML', //'SVG', 'Native'
1651       style: ' ',
1652       size: 10,
1653       family: 'sans-serif',
1654       textAlign: 'center',
1655       textBaseline: 'alphabetic',
1656       color: '#fff'
1657     };
1658   (end code)
1659   
1660   Example:
1661   
1662   (start code js)
1663   var viz = new $jit.Viz({
1664     Label: {
1665       type: 'Native',
1666       size: 11,
1667       color: '#ccc'
1668     }
1669   });
1670   (end code)
1671   
1672   Parameters:
1673     
1674   overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675   type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676   style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677   size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678   family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679   color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1680 */
1681 Options.Label = {
1682   $extend: false,
1683   
1684   overridable: false,
1685   type: 'HTML', //'SVG', 'Native'
1686   style: ' ',
1687   size: 10,
1688   family: 'sans-serif',
1689   textAlign: 'center',
1690   textBaseline: 'alphabetic',
1691   color: '#fff'
1692 };
1693
1694
1695 /*
1696  * File: Options.Tips.js
1697  *
1698  */
1699
1700 /*
1701   Object: Options.Tips
1702   
1703   Tips options
1704   
1705   Syntax:
1706     
1707   (start code js)
1708   Options.Tips = {
1709     enable: false,
1710     type: 'auto',
1711     offsetX: 20,
1712     offsetY: 20,
1713     onShow: $.empty,
1714     onHide: $.empty
1715   };
1716   (end code)
1717   
1718   Example:
1719   
1720   (start code js)
1721   var viz = new $jit.Viz({
1722     Tips: {
1723       enable: true,
1724       type: 'Native',
1725       offsetX: 10,
1726       offsetY: 10,
1727       onShow: function(tip, node) {
1728         tip.innerHTML = node.name;
1729       }
1730     }
1731   });
1732   (end code)
1733
1734   Parameters:
1735
1736   enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class. 
1737   type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738   offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739   offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740   onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741   onHide() - This callack is used when hiding a tooltip.
1742
1743 */
1744 Options.Tips = {
1745   $extend: false,
1746   
1747   enable: false,
1748   type: 'auto',
1749   offsetX: 20,
1750   offsetY: 20,
1751   force: false,
1752   onShow: $.empty,
1753   onHide: $.empty
1754 };
1755
1756
1757 /*
1758  * File: Options.NodeStyles.js
1759  *
1760  */
1761
1762 /*
1763   Object: Options.NodeStyles
1764   
1765   Apply different styles when a node is hovered or selected.
1766   
1767   Syntax:
1768     
1769   (start code js)
1770   Options.NodeStyles = {
1771     enable: false,
1772     type: 'auto',
1773     stylesHover: false,
1774     stylesClick: false
1775   };
1776   (end code)
1777   
1778   Example:
1779   
1780   (start code js)
1781   var viz = new $jit.Viz({
1782     NodeStyles: {
1783       enable: true,
1784       type: 'Native',
1785       stylesHover: {
1786         dim: 30,
1787         color: '#fcc'
1788       },
1789       duration: 600
1790     }
1791   });
1792   (end code)
1793
1794   Parameters:
1795   
1796   enable - (boolean) Default's *false*. Whether to enable this option.
1797   type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798   stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799   stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1800 */
1801
1802 Options.NodeStyles = {
1803   $extend: false,
1804   
1805   enable: false,
1806   type: 'auto',
1807   stylesHover: false,
1808   stylesClick: false
1809 };
1810
1811
1812 /*
1813  * File: Options.Events.js
1814  *
1815 */
1816
1817 /*
1818   Object: Options.Events
1819   
1820   Configuration for adding mouse/touch event handlers to Nodes.
1821   
1822   Syntax:
1823   
1824   (start code js)
1825   Options.Events = {
1826     enable: false,
1827     enableForEdges: false,
1828     type: 'auto',
1829     onClick: $.empty,
1830     onRightClick: $.empty,
1831     onMouseMove: $.empty,
1832     onMouseEnter: $.empty,
1833     onMouseLeave: $.empty,
1834     onDragStart: $.empty,
1835     onDragMove: $.empty,
1836     onDragCancel: $.empty,
1837     onDragEnd: $.empty,
1838     onTouchStart: $.empty,
1839     onTouchMove: $.empty,
1840     onTouchEnd: $.empty,
1841     onTouchCancel: $.empty,
1842     onMouseWheel: $.empty
1843   };
1844   (end code)
1845   
1846   Example:
1847   
1848   (start code js)
1849   var viz = new $jit.Viz({
1850     Events: {
1851       enable: true,
1852       onClick: function(node, eventInfo, e) {
1853         viz.doSomething();
1854       },
1855       onMouseEnter: function(node, eventInfo, e) {
1856         viz.canvas.getElement().style.cursor = 'pointer';
1857       },
1858       onMouseLeave: function(node, eventInfo, e) {
1859         viz.canvas.getElement().style.cursor = '';
1860       }
1861     }
1862   });
1863   (end code)
1864   
1865   Parameters:
1866   
1867   enable - (boolean) Default's *false*. Whether to enable the Event system.
1868   enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869   type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870   onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1871   onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1872   onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner).  *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873   onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1874   onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1875   onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1876   onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1877   onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1878   onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1879   onTouchStart(node, eventInfo, e) - Behaves just like onDragStart. 
1880   onTouchMove(node, eventInfo, e) - Behaves just like onDragMove. 
1881   onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd. 
1882   onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883   onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1884 */
1885
1886 Options.Events = {
1887   $extend: false,
1888   
1889   enable: false,
1890   enableForEdges: false,
1891   type: 'auto',
1892   onClick: $.empty,
1893   onRightClick: $.empty,
1894   onMouseMove: $.empty,
1895   onMouseEnter: $.empty,
1896   onMouseLeave: $.empty,
1897   onDragStart: $.empty,
1898   onDragMove: $.empty,
1899   onDragCancel: $.empty,
1900   onDragEnd: $.empty,
1901   onTouchStart: $.empty,
1902   onTouchMove: $.empty,
1903   onTouchEnd: $.empty,
1904   onMouseWheel: $.empty
1905 };
1906
1907 /*
1908  * File: Options.Navigation.js
1909  *
1910 */
1911
1912 /*
1913   Object: Options.Navigation
1914   
1915   Panning and zooming options for Graph/Tree based visualizations. These options are implemented 
1916   by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1917   
1918   Syntax:
1919   
1920   (start code js)
1921
1922   Options.Navigation = {
1923     enable: false,
1924     type: 'auto',
1925     panning: false, //true, 'avoid nodes'
1926     zooming: false
1927   };
1928   
1929   (end code)
1930   
1931   Example:
1932     
1933   (start code js)
1934   var viz = new $jit.Viz({
1935     Navigation: {
1936       enable: true,
1937       panning: 'avoid nodes',
1938       zooming: 20
1939     }
1940   });
1941   (end code)
1942   
1943   Parameters:
1944   
1945   enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946   panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947   zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1948   
1949 */
1950
1951 Options.Navigation = {
1952   $extend: false,
1953   
1954   enable: false,
1955   type: 'auto',
1956   panning: false, //true | 'avoid nodes'
1957   zooming: false
1958 };
1959
1960 /*
1961  * File: Options.Controller.js
1962  *
1963 */
1964
1965 /*
1966   Object: Options.Controller
1967   
1968   Provides controller methods. Controller methods are callback functions that get called at different stages 
1969   of the animation, computing or plotting of the visualization.
1970   
1971   Implemented by:
1972     
1973   All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1974   
1975   Syntax:
1976   
1977   (start code js)
1978
1979   Options.Controller = {
1980     onBeforeCompute: $.empty,
1981     onAfterCompute:  $.empty,
1982     onCreateLabel:   $.empty,
1983     onPlaceLabel:    $.empty,
1984     onComplete:      $.empty,
1985     onBeforePlotLine:$.empty,
1986     onAfterPlotLine: $.empty,
1987     onBeforePlotNode:$.empty,
1988     onAfterPlotNode: $.empty,
1989     request:         false
1990   };
1991   
1992   (end code)
1993   
1994   Example:
1995     
1996   (start code js)
1997   var viz = new $jit.Viz({
1998     onBeforePlotNode: function(node) {
1999       if(node.selected) {
2000         node.setData('color', '#ffc');
2001       } else {
2002         node.removeData('color');
2003       }
2004     },
2005     onBeforePlotLine: function(adj) {
2006       if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007         adj.setData('color', '#ffc');
2008       } else {
2009         adj.removeData('color');
2010       }
2011     },
2012     onAfterCompute: function() {
2013       alert("computed!");
2014     }
2015   });
2016   (end code)
2017   
2018   Parameters:
2019
2020    onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021    onAfterCompute() - This method is triggered after all animations or computations ended.
2022    onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023    onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024    onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025    onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026    onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027    onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2028
2029     *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2030     
2031     request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.  
2032  
2033  */
2034 Options.Controller = {
2035   $extend: true,
2036   
2037   onBeforeCompute: $.empty,
2038   onAfterCompute:  $.empty,
2039   onCreateLabel:   $.empty,
2040   onPlaceLabel:    $.empty,
2041   onComplete:      $.empty,
2042   onBeforePlotLine:$.empty,
2043   onAfterPlotLine: $.empty,
2044   onBeforePlotNode:$.empty,
2045   onAfterPlotNode: $.empty,
2046   request:         false
2047 };
2048
2049
2050 /*
2051  * File: Extras.js
2052  * 
2053  * Provides Extras such as Tips and Style Effects.
2054  * 
2055  * Description:
2056  * 
2057  * Provides the <Tips> and <NodeStyles> classes and functions.
2058  *
2059  */
2060
2061 /*
2062  * Manager for mouse events (clicking and mouse moving).
2063  * 
2064  * This class is used for registering objects implementing onClick
2065  * and onMousemove methods. These methods are called when clicking or
2066  * moving the mouse around  the Canvas.
2067  * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2068  * 
2069  */
2070 var ExtrasInitializer = {
2071   initialize: function(className, viz) {
2072     this.viz = viz;
2073     this.canvas = viz.canvas;
2074     this.config = viz.config[className];
2075     this.nodeTypes = viz.fx.nodeTypes;
2076     var type = this.config.type;
2077     this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078     this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079     this.isEnabled() && this.initializePost();
2080   },
2081   initializePost: $.empty,
2082   setAsProperty: $.lambda(false),
2083   isEnabled: function() {
2084     return this.config.enable;
2085   },
2086   isLabel: function(e, win) {
2087     e = $.event.get(e, win);
2088     var labelContainer = this.labelContainer,
2089         target = e.target || e.srcElement;
2090     if(target && target.parentNode == labelContainer)
2091       return target;
2092     return false;
2093   }
2094 };
2095
2096 var EventsInterface = {
2097   onMouseUp: $.empty,
2098   onMouseDown: $.empty,
2099   onMouseMove: $.empty,
2100   onMouseOver: $.empty,
2101   onMouseOut: $.empty,
2102   onMouseWheel: $.empty,
2103   onTouchStart: $.empty,
2104   onTouchMove: $.empty,
2105   onTouchEnd: $.empty,
2106   onTouchCancel: $.empty
2107 };
2108
2109 var MouseEventsManager = new Class({
2110   initialize: function(viz) {
2111     this.viz = viz;
2112     this.canvas = viz.canvas;
2113     this.node = false;
2114     this.edge = false;
2115     this.registeredObjects = [];
2116     this.attachEvents();
2117   },
2118   
2119   attachEvents: function() {
2120     var htmlCanvas = this.canvas.getElement(), 
2121         that = this;
2122     htmlCanvas.oncontextmenu = $.lambda(false);
2123     $.addEvents(htmlCanvas, {
2124       'mouseup': function(e, win) {
2125         var event = $.event.get(e, win);
2126         that.handleEvent('MouseUp', e, win, 
2127             that.makeEventObject(e, win), 
2128             $.event.isRightClick(event));
2129       },
2130       'mousedown': function(e, win) {
2131         var event = $.event.get(e, win);
2132         that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win), 
2133             $.event.isRightClick(event));
2134       },
2135       'mousemove': function(e, win) {
2136         that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2137       },
2138       'mouseover': function(e, win) {
2139         that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2140       },
2141       'mouseout': function(e, win) {
2142         that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2143       },
2144       'touchstart': function(e, win) {
2145         that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2146       },
2147       'touchmove': function(e, win) {
2148         that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2149       },
2150       'touchend': function(e, win) {
2151         that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2152       }
2153     });
2154     //attach mousewheel event
2155     var handleMouseWheel = function(e, win) {
2156       var event = $.event.get(e, win);
2157       var wheel = $.event.getWheel(event);
2158       that.handleEvent('MouseWheel', e, win, wheel);
2159     };
2160     //TODO(nico): this is a horrible check for non-gecko browsers!
2161     if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162       $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2163     } else {
2164       htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2165     }
2166   },
2167   
2168   register: function(obj) {
2169     this.registeredObjects.push(obj);
2170   },
2171   
2172   handleEvent: function() {
2173     var args = Array.prototype.slice.call(arguments),
2174         type = args.shift();
2175     for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176       regs[i]['on' + type].apply(regs[i], args);
2177     }
2178   },
2179   
2180   makeEventObject: function(e, win) {
2181     var that = this,
2182         graph = this.viz.graph,
2183         fx = this.viz.fx,
2184         ntypes = fx.nodeTypes,
2185         etypes = fx.edgeTypes;
2186     return {
2187       pos: false,
2188       node: false,
2189       edge: false,
2190       contains: false,
2191       getNodeCalled: false,
2192       getEdgeCalled: false,
2193       getPos: function() {
2194         //TODO(nico): check why this can't be cache anymore when using edge detection
2195         //if(this.pos) return this.pos;
2196         var canvas = that.viz.canvas,
2197             s = canvas.getSize(),
2198             p = canvas.getPos(),
2199             ox = canvas.translateOffsetX,
2200             oy = canvas.translateOffsetY,
2201             sx = canvas.scaleOffsetX,
2202             sy = canvas.scaleOffsetY,
2203             pos = $.event.getPos(e, win);
2204         this.pos = {
2205           x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206           y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2207         };
2208         return this.pos;
2209       },
2210       getNode: function() {
2211         if(this.getNodeCalled) return this.node;
2212         this.getNodeCalled = true;
2213         for(var id in graph.nodes) {
2214           var n = graph.nodes[id],
2215               geom = n && ntypes[n.getData('type')],
2216               contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2217           if(contains) {
2218             this.contains = contains;
2219             return that.node = this.node = n;
2220           }
2221         }
2222         return that.node = this.node = false;
2223       },
2224       getEdge: function() {
2225         if(this.getEdgeCalled) return this.edge;
2226         this.getEdgeCalled = true;
2227         var hashset = {};
2228         for(var id in graph.edges) {
2229           var edgeFrom = graph.edges[id];
2230           hashset[id] = true;
2231           for(var edgeId in edgeFrom) {
2232             if(edgeId in hashset) continue;
2233             var e = edgeFrom[edgeId],
2234                 geom = e && etypes[e.getData('type')],
2235                 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2236             if(contains) {
2237               this.contains = contains;
2238               return that.edge = this.edge = e;
2239             }
2240           }
2241         }
2242         return that.edge = this.edge = false;
2243       },
2244       getContains: function() {
2245         if(this.getNodeCalled) return this.contains;
2246         this.getNode();
2247         return this.contains;
2248       }
2249     };
2250   }
2251 });
2252
2253 /* 
2254  * Provides the initialization function for <NodeStyles> and <Tips> implemented 
2255  * by all main visualizations.
2256  *
2257  */
2258 var Extras = {
2259   initializeExtras: function() {
2260     var mem = new MouseEventsManager(this), that = this;
2261     $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262       var obj = new Extras.Classes[k](k, that);
2263       if(obj.isEnabled()) {
2264         mem.register(obj);
2265       }
2266       if(obj.setAsProperty()) {
2267         that[k.toLowerCase()] = obj;
2268       }
2269     });
2270   }   
2271 };
2272
2273 Extras.Classes = {};
2274 /*
2275   Class: Events
2276    
2277   This class defines an Event API to be accessed by the user.
2278   The methods implemented are the ones defined in the <Options.Events> object.
2279 */
2280
2281 Extras.Classes.Events = new Class({
2282   Implements: [ExtrasInitializer, EventsInterface],
2283   
2284   initializePost: function() {
2285     this.fx = this.viz.fx;
2286     this.ntypes = this.viz.fx.nodeTypes;
2287     this.etypes = this.viz.fx.edgeTypes;
2288     
2289     this.hovered = false;
2290     this.pressed = false;
2291     this.touched = false;
2292
2293     this.touchMoved = false;
2294     this.moved = false;
2295     
2296   },
2297   
2298   setAsProperty: $.lambda(true),
2299   
2300   onMouseUp: function(e, win, event, isRightClick) {
2301     var evt = $.event.get(e, win);
2302     if(!this.moved) {
2303       if(isRightClick) {
2304         this.config.onRightClick(this.hovered, event, evt);
2305       } else {
2306         this.config.onClick(this.pressed, event, evt);
2307       }
2308     }
2309     if(this.pressed) {
2310       if(this.moved) {
2311         this.config.onDragEnd(this.pressed, event, evt);
2312       } else {
2313         this.config.onDragCancel(this.pressed, event, evt);
2314       }
2315       this.pressed = this.moved = false;
2316     }
2317   },
2318
2319   onMouseOut: function(e, win, event) {
2320    //mouseout a label
2321    var evt = $.event.get(e, win), label;
2322    if(this.dom && (label = this.isLabel(e, win))) {
2323      this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2324                               event, evt);
2325      this.hovered = false;
2326      return;
2327    }
2328    //mouseout canvas
2329    var rt = evt.relatedTarget,
2330        canvasWidget = this.canvas.getElement();
2331    while(rt && rt.parentNode) {
2332      if(canvasWidget == rt.parentNode) return;
2333      rt = rt.parentNode;
2334    }
2335    if(this.hovered) {
2336      this.config.onMouseLeave(this.hovered,
2337          event, evt);
2338      this.hovered = false;
2339    }
2340   },
2341   
2342   onMouseOver: function(e, win, event) {
2343     //mouseover a label
2344     var evt = $.event.get(e, win), label;
2345     if(this.dom && (label = this.isLabel(e, win))) {
2346       this.hovered = this.viz.graph.getNode(label.id);
2347       this.config.onMouseEnter(this.hovered,
2348                                event, evt);
2349     }
2350   },
2351   
2352   onMouseMove: function(e, win, event) {
2353    var label, evt = $.event.get(e, win);
2354    if(this.pressed) {
2355      this.moved = true;
2356      this.config.onDragMove(this.pressed, event, evt);
2357      return;
2358    }
2359    if(this.dom) {
2360      this.config.onMouseMove(this.hovered,
2361          event, evt);
2362    } else {
2363      if(this.hovered) {
2364        var hn = this.hovered;
2365        var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366        var contains = geom && geom.contains 
2367          && geom.contains.call(this.fx, hn, event.getPos());
2368        if(contains) {
2369          this.config.onMouseMove(hn, event, evt);
2370          return;
2371        } else {
2372          this.config.onMouseLeave(hn, event, evt);
2373          this.hovered = false;
2374        }
2375      }
2376      if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377        this.config.onMouseEnter(this.hovered, event, evt);
2378      } else {
2379        this.config.onMouseMove(false, event, evt);
2380      }
2381    }
2382   },
2383   
2384   onMouseWheel: function(e, win, delta) {
2385     this.config.onMouseWheel(delta, $.event.get(e, win));
2386   },
2387   
2388   onMouseDown: function(e, win, event) {
2389     var evt = $.event.get(e, win);
2390     this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391     this.config.onDragStart(this.pressed, event, evt);
2392   },
2393   
2394   onTouchStart: function(e, win, event) {
2395     var evt = $.event.get(e, win);
2396     this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397     this.config.onTouchStart(this.touched, event, evt);
2398   },
2399   
2400   onTouchMove: function(e, win, event) {
2401     var evt = $.event.get(e, win);
2402     if(this.touched) {
2403       this.touchMoved = true;
2404       this.config.onTouchMove(this.touched, event, evt);
2405     }
2406   },
2407   
2408   onTouchEnd: function(e, win, event) {
2409     var evt = $.event.get(e, win);
2410     if(this.touched) {
2411       if(this.touchMoved) {
2412         this.config.onTouchEnd(this.touched, event, evt);
2413       } else {
2414         this.config.onTouchCancel(this.touched, event, evt);
2415       }
2416       this.touched = this.touchMoved = false;
2417     }
2418   }
2419 });
2420
2421 /*
2422    Class: Tips
2423     
2424    A class containing tip related functions. This class is used internally.
2425    
2426    Used by:
2427    
2428    <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2429    
2430    See also:
2431    
2432    <Options.Tips>
2433 */
2434
2435 Extras.Classes.Tips = new Class({
2436   Implements: [ExtrasInitializer, EventsInterface],
2437   
2438   initializePost: function() {
2439     //add DOM tooltip
2440     if(document.body) {
2441       var tip = $('_tooltip') || document.createElement('div');
2442       tip.id = '_tooltip';
2443       tip.className = 'tip';
2444       $.extend(tip.style, {
2445         position: 'absolute',
2446         display: 'none',
2447         zIndex: 13000
2448       });
2449       document.body.appendChild(tip);
2450       this.tip = tip;
2451       this.node = false;
2452     }
2453   },
2454   
2455   setAsProperty: $.lambda(true),
2456   
2457   onMouseOut: function(e, win) {
2458     //mouseout a label
2459     if(this.dom && this.isLabel(e, win)) {
2460       this.hide(true);
2461       return;
2462     }
2463     //mouseout canvas
2464     var rt = e.relatedTarget,
2465         canvasWidget = this.canvas.getElement();
2466     while(rt && rt.parentNode) {
2467       if(canvasWidget == rt.parentNode) return;
2468       rt = rt.parentNode;
2469     }
2470     this.hide(false);
2471   },
2472   
2473   onMouseOver: function(e, win) {
2474     //mouseover a label
2475     var label;
2476     if(this.dom && (label = this.isLabel(e, win))) {
2477       this.node = this.viz.graph.getNode(label.id);
2478       this.config.onShow(this.tip, this.node, label);
2479     }
2480   },
2481   
2482   onMouseMove: function(e, win, opt) {
2483     if(this.dom && this.isLabel(e, win)) {
2484       this.setTooltipPosition($.event.getPos(e, win));
2485     }
2486     if(!this.dom) {
2487       var node = opt.getNode();
2488       if(!node) {
2489         this.hide(true);
2490         return;
2491       }
2492       if(this.config.force || !this.node || this.node.id != node.id) {
2493         this.node = node;
2494         this.config.onShow(this.tip, node, opt.getContains());
2495       }
2496       this.setTooltipPosition($.event.getPos(e, win));
2497     }
2498   },
2499   
2500   setTooltipPosition: function(pos) {
2501     var tip = this.tip, 
2502         style = tip.style, 
2503         cont = this.config;
2504     style.display = '';
2505     //get window dimensions
2506     var win = {
2507       'height': document.body.clientHeight,
2508       'width': document.body.clientWidth
2509     };
2510     //get tooltip dimensions
2511     var obj = {
2512       'width': tip.offsetWidth,
2513       'height': tip.offsetHeight  
2514     };
2515     //set tooltip position
2516     var x = cont.offsetX, y = cont.offsetY;
2517     style.top = ((pos.y + y + obj.height > win.height)?  
2518         (pos.y - obj.height - y) : pos.y + y) + 'px';
2519     style.left = ((pos.x + obj.width + x > win.width)? 
2520         (pos.x - obj.width - x) : pos.x + x) + 'px';
2521   },
2522   
2523   hide: function(triggerCallback) {
2524     if(!SUGAR.util.isTouchScreen()) {
2525         this.tip.style.display = 'none';
2526     }
2527     triggerCallback && this.config.onHide();
2528   }
2529 });
2530
2531 /*
2532   Class: NodeStyles
2533    
2534   Change node styles when clicking or hovering a node. This class is used internally.
2535   
2536   Used by:
2537   
2538   <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2539   
2540   See also:
2541   
2542   <Options.NodeStyles>
2543 */
2544 Extras.Classes.NodeStyles = new Class({
2545   Implements: [ExtrasInitializer, EventsInterface],
2546   
2547   initializePost: function() {
2548     this.fx = this.viz.fx;
2549     this.types = this.viz.fx.nodeTypes;
2550     this.nStyles = this.config;
2551     this.nodeStylesOnHover = this.nStyles.stylesHover;
2552     this.nodeStylesOnClick = this.nStyles.stylesClick;
2553     this.hoveredNode = false;
2554     this.fx.nodeFxAnimation = new Animation();
2555     
2556     this.down = false;
2557     this.move = false;
2558   },
2559   
2560   onMouseOut: function(e, win) {
2561     this.down = this.move = false;
2562     if(!this.hoveredNode) return;
2563     //mouseout a label
2564     if(this.dom && this.isLabel(e, win)) {
2565       this.toggleStylesOnHover(this.hoveredNode, false);
2566     }
2567     //mouseout canvas
2568     var rt = e.relatedTarget,
2569         canvasWidget = this.canvas.getElement();
2570     while(rt && rt.parentNode) {
2571       if(canvasWidget == rt.parentNode) return;
2572       rt = rt.parentNode;
2573     }
2574     this.toggleStylesOnHover(this.hoveredNode, false);
2575     this.hoveredNode = false;
2576   },
2577   
2578   onMouseOver: function(e, win) {
2579     //mouseover a label
2580     var label;
2581     if(this.dom && (label = this.isLabel(e, win))) {
2582       var node = this.viz.graph.getNode(label.id);
2583       if(node.selected) return;
2584       this.hoveredNode = node;
2585       this.toggleStylesOnHover(this.hoveredNode, true);
2586     }
2587   },
2588   
2589   onMouseDown: function(e, win, event, isRightClick) {
2590     if(isRightClick) return;
2591     var label;
2592     if(this.dom && (label = this.isLabel(e, win))) {
2593       this.down = this.viz.graph.getNode(label.id);
2594     } else if(!this.dom) {
2595       this.down = event.getNode();
2596     }
2597     this.move = false;
2598   },
2599   
2600   onMouseUp: function(e, win, event, isRightClick) {
2601     if(isRightClick) return;
2602     if(!this.move) {
2603       this.onClick(event.getNode());
2604     }
2605     this.down = this.move = false;
2606   },
2607   
2608   getRestoredStyles: function(node, type) {
2609     var restoredStyles = {}, 
2610         nStyles = this['nodeStylesOn' + type];
2611     for(var prop in nStyles) {
2612       restoredStyles[prop] = node.styles['$' + prop];
2613     }
2614     return restoredStyles;
2615   },
2616   
2617   toggleStylesOnHover: function(node, set) {
2618     if(this.nodeStylesOnHover) {
2619       this.toggleStylesOn('Hover', node, set);
2620     }
2621   },
2622
2623   toggleStylesOnClick: function(node, set) {
2624     if(this.nodeStylesOnClick) {
2625       this.toggleStylesOn('Click', node, set);
2626     }
2627   },
2628   
2629   toggleStylesOn: function(type, node, set) {
2630     var viz = this.viz;
2631     var nStyles = this.nStyles;
2632     if(set) {
2633       var that = this;
2634       if(!node.styles) {
2635         node.styles = $.merge(node.data, {});
2636       }
2637       for(var s in this['nodeStylesOn' + type]) {
2638         var $s = '$' + s;
2639         if(!($s in node.styles)) {
2640             node.styles[$s] = node.getData(s); 
2641         }
2642       }
2643       viz.fx.nodeFx($.extend({
2644         'elements': {
2645           'id': node.id,
2646           'properties': that['nodeStylesOn' + type]
2647          },
2648          transition: Trans.Quart.easeOut,
2649          duration:300,
2650          fps:40
2651       }, this.config));
2652     } else {
2653       var restoredStyles = this.getRestoredStyles(node, type);
2654       viz.fx.nodeFx($.extend({
2655         'elements': {
2656           'id': node.id,
2657           'properties': restoredStyles
2658          },
2659          transition: Trans.Quart.easeOut,
2660          duration:300,
2661          fps:40
2662       }, this.config));
2663     }
2664   },
2665
2666   onClick: function(node) {
2667     if(!node) return;
2668     var nStyles = this.nodeStylesOnClick;
2669     if(!nStyles) return;
2670     //if the node is selected then unselect it
2671     if(node.selected) {
2672       this.toggleStylesOnClick(node, false);
2673       delete node.selected;
2674     } else {
2675       //unselect all selected nodes...
2676       this.viz.graph.eachNode(function(n) {
2677         if(n.selected) {
2678           for(var s in nStyles) {
2679             n.setData(s, n.styles['$' + s], 'end');
2680           }
2681           delete n.selected;
2682         }
2683       });
2684       //select clicked node
2685       this.toggleStylesOnClick(node, true);
2686       node.selected = true;
2687       delete node.hovered;
2688       this.hoveredNode = false;
2689     }
2690   },
2691   
2692   onMouseMove: function(e, win, event) {
2693     //if mouse button is down and moving set move=true
2694     if(this.down) this.move = true;
2695     //already handled by mouseover/out
2696     if(this.dom && this.isLabel(e, win)) return;
2697     var nStyles = this.nodeStylesOnHover;
2698     if(!nStyles) return;
2699     
2700     if(!this.dom) {
2701       if(this.hoveredNode) {
2702         var geom = this.types[this.hoveredNode.getData('type')];
2703         var contains = geom && geom.contains && geom.contains.call(this.fx, 
2704             this.hoveredNode, event.getPos());
2705         if(contains) return;
2706       }
2707       var node = event.getNode();
2708       //if no node is being hovered then just exit
2709       if(!this.hoveredNode && !node) return;
2710       //if the node is hovered then exit
2711       if(node.hovered) return;
2712       //select hovered node
2713       if(node && !node.selected) {
2714         //check if an animation is running and exit it
2715         this.fx.nodeFxAnimation.stopTimer();
2716         //unselect all hovered nodes...
2717         this.viz.graph.eachNode(function(n) {
2718           if(n.hovered && !n.selected) {
2719             for(var s in nStyles) {
2720               n.setData(s, n.styles['$' + s], 'end');
2721             }
2722             delete n.hovered;
2723           }
2724         });
2725         //select hovered node
2726         node.hovered = true;
2727         this.hoveredNode = node;
2728         this.toggleStylesOnHover(node, true);
2729       } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730         //check if an animation is running and exit it
2731         this.fx.nodeFxAnimation.stopTimer();
2732         //unselect hovered node
2733         this.toggleStylesOnHover(this.hoveredNode, false);
2734         delete this.hoveredNode.hovered;
2735         this.hoveredNode = false;
2736       }
2737     }
2738   }
2739 });
2740
2741 Extras.Classes.Navigation = new Class({
2742   Implements: [ExtrasInitializer, EventsInterface],
2743   
2744   initializePost: function() {
2745     this.pos = false;
2746     this.pressed = false;
2747   },
2748   
2749   onMouseWheel: function(e, win, scroll) {
2750     if(!this.config.zooming) return;
2751     $.event.stop($.event.get(e, win));
2752     var val = this.config.zooming / 1000,
2753         ans = 1 + scroll * val;
2754     this.canvas.scale(ans, ans);
2755   },
2756   
2757   onMouseDown: function(e, win, eventInfo) {
2758     if(!this.config.panning) return;
2759     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760     this.pressed = true;
2761     this.pos = eventInfo.getPos();
2762     var canvas = this.canvas,
2763         ox = canvas.translateOffsetX,
2764         oy = canvas.translateOffsetY,
2765         sx = canvas.scaleOffsetX,
2766         sy = canvas.scaleOffsetY;
2767     this.pos.x *= sx;
2768     this.pos.x += ox;
2769     this.pos.y *= sy;
2770     this.pos.y += oy;
2771   },
2772   
2773   onMouseMove: function(e, win, eventInfo) {
2774     if(!this.config.panning) return;
2775     if(!this.pressed) return;
2776     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777     var thispos = this.pos, 
2778         currentPos = eventInfo.getPos(),
2779         canvas = this.canvas,
2780         ox = canvas.translateOffsetX,
2781         oy = canvas.translateOffsetY,
2782         sx = canvas.scaleOffsetX,
2783         sy = canvas.scaleOffsetY;
2784     currentPos.x *= sx;
2785     currentPos.y *= sy;
2786     currentPos.x += ox;
2787     currentPos.y += oy;
2788     var x = currentPos.x - thispos.x,
2789         y = currentPos.y - thispos.y;
2790     this.pos = currentPos;
2791     this.canvas.translate(x * 1/sx, y * 1/sy);
2792   },
2793   
2794   onMouseUp: function(e, win, eventInfo, isRightClick) {
2795     if(!this.config.panning) return;
2796     this.pressed = false;
2797   }
2798 });
2799
2800
2801 /*
2802  * File: Canvas.js
2803  *
2804  */
2805
2806 /*
2807  Class: Canvas
2808  
2809         A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to 
2810         know more about <Canvas> options take a look at <Options.Canvas>.
2811  
2812  A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior 
2813  across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2814  
2815  Example:
2816  
2817  Suppose we have this HTML
2818  
2819  (start code xml)
2820         <div id="infovis"></div>
2821  (end code)
2822  
2823  Now we create a new Visualization
2824  
2825  (start code js)
2826         var viz = new $jit.Viz({
2827                 //Where to inject the canvas. Any div container will do.
2828                 'injectInto':'infovis',
2829                  //width and height for canvas. 
2830                  //Default's to the container offsetWidth and Height.
2831                  'width': 900,
2832                  'height':500
2833          });
2834  (end code)
2835
2836  The generated HTML will look like this
2837  
2838  (start code xml)
2839  <div id="infovis">
2840         <div id="infovis-canvaswidget" style="position:relative;">
2841         <canvas id="infovis-canvas" width=900 height=500
2842         style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843         <div id="infovis-label"
2844         style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2845         </div>
2846         </div>
2847  </div>
2848  (end code)
2849  
2850  As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851  of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2852  */
2853
2854 var Canvas;
2855 (function() {
2856   //check for native canvas support
2857   var canvasType = typeof HTMLCanvasElement,
2858       supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859   //create element function
2860   function $E(tag, props) {
2861     var elem = document.createElement(tag);
2862     for(var p in props) {
2863       if(typeof props[p] == "object") {
2864         $.extend(elem[p], props[p]);
2865       } else {
2866         elem[p] = props[p];
2867       }
2868     }
2869     if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870       elem = G_vmlCanvasManager.initElement(elem);
2871     }
2872     return elem;
2873   }
2874   //canvas widget which we will call just Canvas
2875   $jit.Canvas = Canvas = new Class({
2876     canvases: [],
2877     pos: false,
2878     element: false,
2879     labelContainer: false,
2880     translateOffsetX: 0,
2881     translateOffsetY: 0,
2882     scaleOffsetX: 1,
2883     scaleOffsetY: 1,
2884     
2885     initialize: function(viz, opt) {
2886       this.viz = viz;
2887       this.opt = opt;
2888       var id = $.type(opt.injectInto) == 'string'? 
2889           opt.injectInto:opt.injectInto.id,
2890           idLabel = id + "-label", 
2891           wrapper = $(id),
2892           width = opt.width || wrapper.offsetWidth,
2893           height = opt.height || wrapper.offsetHeight;
2894       this.id = id;
2895       //canvas options
2896       var canvasOptions = {
2897         injectInto: id,
2898         width: width,
2899         height: height
2900       };
2901       //create main wrapper
2902       this.element = $E('div', {
2903         'id': id + '-canvaswidget',
2904         'style': {
2905           'position': 'relative',
2906           'width': width + 'px',
2907           'height': height + 'px'
2908         }
2909       });
2910       //create label container
2911       this.labelContainer = this.createLabelContainer(opt.Label.type, 
2912           idLabel, canvasOptions);
2913       //create primary canvas
2914       this.canvases.push(new Canvas.Base({
2915         config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916         plot: function(base) {
2917           viz.fx.plot();
2918         },
2919         resize: function() {
2920           viz.refresh();
2921         }
2922       }));
2923       //create secondary canvas
2924       var back = opt.background;
2925       if(back) {
2926         var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927         this.canvases.push(new Canvas.Base(backCanvas));
2928       }
2929       //insert canvases
2930       var len = this.canvases.length;
2931       while(len--) {
2932         this.element.appendChild(this.canvases[len].canvas);
2933         if(len > 0) {
2934           this.canvases[len].plot();
2935         }
2936       }
2937       this.element.appendChild(this.labelContainer);
2938       wrapper.appendChild(this.element);
2939       //Update canvas position when the page is scrolled.
2940       var timer = null, that = this;
2941       $.addEvent(window, 'scroll', function() {
2942         clearTimeout(timer);
2943         timer = setTimeout(function() {
2944           that.getPos(true); //update canvas position
2945         }, 500);
2946       });
2947       $.addEvent(window, 'click', function() {
2948         clearTimeout(timer);
2949         timer = setTimeout(function() {
2950           that.getPos(true); //update canvas position
2951         }, 500);
2952       });
2953       sb = document.getElementById('sb'+id);
2954       $.addEvent(sb, 'scroll', function() {
2955         clearTimeout(timer);
2956         timer = setTimeout(function() {
2957           that.getPos(true); //update canvas position
2958         }, 500);
2959       });
2960     },
2961     /*
2962       Method: getCtx
2963       
2964       Returns the main canvas context object
2965       
2966       Example:
2967       
2968       (start code js)
2969        var ctx = canvas.getCtx();
2970        //Now I can use the native canvas context
2971        //and for example change some canvas styles
2972        ctx.globalAlpha = 1;
2973       (end code)
2974     */
2975     getCtx: function(i) {
2976       return this.canvases[i || 0].getCtx();
2977     },
2978     /*
2979       Method: getConfig
2980       
2981       Returns the current Configuration for this Canvas Widget.
2982       
2983       Example:
2984       
2985       (start code js)
2986        var config = canvas.getConfig();
2987       (end code)
2988     */
2989     getConfig: function() {
2990       return this.opt;
2991     },
2992     /*
2993       Method: getElement
2994
2995       Returns the main Canvas DOM wrapper
2996       
2997       Example:
2998       
2999       (start code js)
3000        var wrapper = canvas.getElement();
3001        //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3002       (end code)
3003     */
3004     getElement: function() {
3005       return this.element;
3006     },
3007     /*
3008       Method: getSize
3009       
3010       Returns canvas dimensions.
3011       
3012       Returns:
3013       
3014       An object with *width* and *height* properties.
3015       
3016       Example:
3017       (start code js)
3018       canvas.getSize(); //returns { width: 900, height: 500 }
3019       (end code)
3020     */
3021     getSize: function(i) {
3022       return this.canvases[i || 0].getSize();
3023     },
3024     /*
3025       Method: resize
3026       
3027       Resizes the canvas.
3028       
3029       Parameters:
3030       
3031       width - New canvas width.
3032       height - New canvas height.
3033       
3034       Example:
3035       
3036       (start code js)
3037        canvas.resize(width, height);
3038       (end code)
3039     
3040     */
3041     resize: function(width, height) {
3042       this.getPos(true);
3043       this.translateOffsetX = this.translateOffsetY = 0;
3044       this.scaleOffsetX = this.scaleOffsetY = 1;
3045       for(var i=0, l=this.canvases.length; i<l; i++) {
3046         this.canvases[i].resize(width, height);
3047       }
3048       var style = this.element.style;
3049       style.width = width + 'px';
3050       style.height = height + 'px';
3051       if(this.labelContainer)
3052         this.labelContainer.style.width = width + 'px';
3053     },
3054     /*
3055       Method: translate
3056       
3057       Applies a translation to the canvas.
3058       
3059       Parameters:
3060       
3061       x - (number) x offset.
3062       y - (number) y offset.
3063       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3064       
3065       Example:
3066       
3067       (start code js)
3068        canvas.translate(30, 30);
3069       (end code)
3070     
3071     */
3072     translate: function(x, y, disablePlot) {
3073       this.translateOffsetX += x*this.scaleOffsetX;
3074       this.translateOffsetY += y*this.scaleOffsetY;
3075       for(var i=0, l=this.canvases.length; i<l; i++) {
3076         this.canvases[i].translate(x, y, disablePlot);
3077       }
3078     },
3079     /*
3080       Method: scale
3081       
3082       Scales the canvas.
3083       
3084       Parameters:
3085       
3086       x - (number) scale value.
3087       y - (number) scale value.
3088       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3089       
3090       Example:
3091       
3092       (start code js)
3093        canvas.scale(0.5, 0.5);
3094       (end code)
3095     
3096     */
3097     scale: function(x, y, disablePlot) {
3098         if(!disablePlot) {
3099                 disablePlot = false;
3100                 }
3101       var px = this.scaleOffsetX * x,
3102           py = this.scaleOffsetY * y;
3103       var dx = this.translateOffsetX * (x -1) / px,
3104           dy = this.translateOffsetY * (y -1) / py;
3105       this.scaleOffsetX = px;
3106       this.scaleOffsetY = py;
3107       for(var i=0, l=this.canvases.length; i<l; i++) {
3108         this.canvases[i].scale(x, y, true);
3109       }
3110       this.translate(dx, dy, disablePlot);
3111     },
3112     /*
3113       Method: getPos
3114       
3115       Returns the canvas position as an *x, y* object.
3116       
3117       Parameters:
3118       
3119       force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3120       
3121       Returns:
3122       
3123       An object with *x* and *y* properties.
3124       
3125       Example:
3126       (start code js)
3127       canvas.getPos(true); //returns { x: 900, y: 500 }
3128       (end code)
3129     */
3130     getPos: function(force){
3131       if(force || !this.pos) {
3132         return this.pos = $.getPos(this.getElement());
3133       }
3134       return this.pos;
3135     },
3136     /*
3137        Method: clear
3138        
3139        Clears the canvas.
3140     */
3141     clear: function(i){
3142       this.canvases[i||0].clear();
3143     },
3144     
3145     path: function(type, action){
3146       var ctx = this.canvases[0].getCtx();
3147       ctx.beginPath();
3148       action(ctx);
3149       ctx[type]();
3150       ctx.closePath();
3151     },
3152     
3153     createLabelContainer: function(type, idLabel, dim) {
3154       var NS = 'http://www.w3.org/2000/svg';
3155       if(type == 'HTML' || type == 'Native') {
3156         return $E('div', {
3157           'id': idLabel,
3158           'style': {
3159             'overflow': 'visible',
3160             'position': 'absolute',
3161             'top': 0,
3162             'left': 0,
3163             'width': dim.width + 'px',
3164             'height': 0
3165           }
3166         });
3167       } else if(type == 'SVG') {
3168         var svgContainer = document.createElementNS(NS, 'svg:svg');
3169         svgContainer.setAttribute("width", dim.width);
3170         svgContainer.setAttribute('height', dim.height);
3171         var style = svgContainer.style;
3172         style.position = 'absolute';
3173         style.left = style.top = '0px';
3174         var labelContainer = document.createElementNS(NS, 'svg:g');
3175         labelContainer.setAttribute('width', dim.width);
3176         labelContainer.setAttribute('height', dim.height);
3177         labelContainer.setAttribute('x', 0);
3178         labelContainer.setAttribute('y', 0);
3179         labelContainer.setAttribute('id', idLabel);
3180         svgContainer.appendChild(labelContainer);
3181         return svgContainer;
3182       }
3183     }
3184   });
3185   //base canvas wrapper
3186   Canvas.Base = new Class({
3187     translateOffsetX: 0,
3188     translateOffsetY: 0,
3189     scaleOffsetX: 1,
3190     scaleOffsetY: 1,
3191
3192     initialize: function(viz) {
3193       this.viz = viz;
3194       this.opt = viz.config;
3195       this.size = false;
3196       this.createCanvas();
3197       this.translateToCenter();
3198     },
3199     createCanvas: function() {
3200       var opt = this.opt,
3201           width = opt.width,
3202           height = opt.height;
3203       this.canvas = $E('canvas', {
3204         'id': opt.injectInto + opt.idSuffix,
3205         'width': width,
3206         'height': height,
3207         'style': {
3208           'position': 'absolute',
3209           'top': 0,
3210           'left': 0,
3211           'width': width + 'px',
3212           'height': height + 'px'
3213         }
3214       });
3215     },
3216     getCtx: function() {
3217       if(!this.ctx) 
3218         return this.ctx = this.canvas.getContext('2d');
3219       return this.ctx;
3220     },
3221     getSize: function() {
3222       if(this.size) return this.size;
3223       var canvas = this.canvas;
3224       return this.size = {
3225         width: canvas.width,
3226         height: canvas.height
3227       };
3228     },
3229     translateToCenter: function(ps) {
3230       var size = this.getSize(),
3231           width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3232           height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3233       var ctx = this.getCtx();
3234       ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3235       ctx.translate(width/2, height/2);
3236     },
3237     resize: function(width, height) {
3238       var size = this.getSize(),
3239           canvas = this.canvas,
3240           styles = canvas.style;
3241       this.size = false;
3242       canvas.width = width;
3243       canvas.height = height;
3244       styles.width = width + "px";
3245       styles.height = height + "px";
3246       //small ExCanvas fix
3247       //if(!supportsCanvas) {
3248         //this.translateToCenter(size);
3249       //} else {
3250         this.translateToCenter();
3251       //}
3252       this.translateOffsetX =
3253         this.translateOffsetY = 0;
3254       this.scaleOffsetX = 
3255         this.scaleOffsetY = 1;
3256       this.clear();
3257       this.viz.resize(width, height, this);
3258     },
3259     translate: function(x, y, disablePlot) {
3260       var sx = this.scaleOffsetX,
3261           sy = this.scaleOffsetY;
3262       this.translateOffsetX += x*sx;
3263       this.translateOffsetY += y*sy;
3264       this.getCtx().translate(x, y);
3265       !disablePlot && this.plot();
3266     },
3267     scale: function(x, y, disablePlot) {
3268       this.scaleOffsetX *= x;
3269       this.scaleOffsetY *= y;
3270       this.getCtx().scale(x, y);
3271       !disablePlot && this.plot();
3272     },
3273     clear: function(){
3274       var size = this.getSize(),
3275           ox = this.translateOffsetX,
3276           oy = this.translateOffsetY,
3277           sx = this.scaleOffsetX,
3278           sy = this.scaleOffsetY;
3279       this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx, 
3280                               (-size.height / 2 - oy) * 1/sy, 
3281                               size.width * 1/sx, size.height * 1/sy);
3282     },
3283     plot: function() {
3284       this.clear();
3285       this.viz.plot(this);
3286     }
3287   });
3288   //background canvases
3289   //TODO(nico): document this!
3290   Canvas.Background = {};
3291   Canvas.Background.Circles = new Class({
3292     initialize: function(viz, options) {
3293       this.viz = viz;
3294       this.config = $.merge({
3295         idSuffix: '-bkcanvas',
3296         levelDistance: 100,
3297         numberOfCircles: 6,
3298         CanvasStyles: {},
3299         offset: 0
3300       }, options);
3301     },
3302     resize: function(width, height, base) {
3303       this.plot(base);
3304     },
3305     plot: function(base) {
3306       var canvas = base.canvas,
3307           ctx = base.getCtx(),
3308           conf = this.config,
3309           styles = conf.CanvasStyles;
3310       //set canvas styles
3311       for(var s in styles) ctx[s] = styles[s];
3312       var n = conf.numberOfCircles,
3313           rho = conf.levelDistance;
3314       for(var i=1; i<=n; i++) {
3315         ctx.beginPath();
3316         ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3317         ctx.stroke();
3318         ctx.closePath();
3319       }
3320       //TODO(nico): print labels too!
3321     }
3322   });
3323   Canvas.Background.Fade = new Class({
3324     initialize: function(viz, options) {
3325       this.viz = viz;
3326       this.config = $.merge({
3327         idSuffix: '-bkcanvas',
3328         CanvasStyles: {},
3329         offset: 0
3330       }, options);
3331     },
3332     resize: function(width, height, base) {
3333       this.plot(base);
3334     },
3335     plot: function(base) {
3336       var canvas = base.canvas,
3337           ctx = base.getCtx(),
3338           conf = this.config,
3339           styles = conf.CanvasStyles,
3340           size = base.getSize();
3341                   ctx.fillStyle = 'rgb(255,255,255)';
3342                   ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3343       //TODO(nico): print labels too!
3344     }
3345   });
3346 })();
3347
3348
3349 /*
3350  * File: Polar.js
3351  * 
3352  * Defines the <Polar> class.
3353  *
3354  * Description:
3355  *
3356  * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3357  *
3358  * See also:
3359  *
3360  * <http://en.wikipedia.org/wiki/Polar_coordinates>
3361  *
3362 */
3363
3364 /*
3365    Class: Polar
3366
3367    A multi purpose polar representation.
3368
3369    Description:
3370  
3371    The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3372  
3373    See also:
3374  
3375    <http://en.wikipedia.org/wiki/Polar_coordinates>
3376  
3377    Parameters:
3378
3379       theta - An angle.
3380       rho - The norm.
3381 */
3382
3383 var Polar = function(theta, rho) {
3384   this.theta = theta;
3385   this.rho = rho;
3386 };
3387
3388 $jit.Polar = Polar;
3389
3390 Polar.prototype = {
3391     /*
3392        Method: getc
3393     
3394        Returns a complex number.
3395     
3396        Parameters:
3397
3398        simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3399
3400       Returns:
3401     
3402           A complex number.
3403     */
3404     getc: function(simple) {
3405         return this.toComplex(simple);
3406     },
3407
3408     /*
3409        Method: getp
3410     
3411        Returns a <Polar> representation.
3412     
3413        Returns:
3414     
3415           A variable in polar coordinates.
3416     */
3417     getp: function() {
3418         return this;
3419     },
3420
3421
3422     /*
3423        Method: set
3424     
3425        Sets a number.
3426
3427        Parameters:
3428
3429        v - A <Complex> or <Polar> instance.
3430     
3431     */
3432     set: function(v) {
3433         v = v.getp();
3434         this.theta = v.theta; this.rho = v.rho;
3435     },
3436
3437     /*
3438        Method: setc
3439     
3440        Sets a <Complex> number.
3441
3442        Parameters:
3443
3444        x - A <Complex> number real part.
3445        y - A <Complex> number imaginary part.
3446     
3447     */
3448     setc: function(x, y) {
3449         this.rho = Math.sqrt(x * x + y * y);
3450         this.theta = Math.atan2(y, x);
3451         if(this.theta < 0) this.theta += Math.PI * 2;
3452     },
3453
3454     /*
3455        Method: setp
3456     
3457        Sets a polar number.
3458
3459        Parameters:
3460
3461        theta - A <Polar> number angle property.
3462        rho - A <Polar> number rho property.
3463     
3464     */
3465     setp: function(theta, rho) {
3466         this.theta = theta; 
3467         this.rho = rho;
3468     },
3469
3470     /*
3471        Method: clone
3472     
3473        Returns a copy of the current object.
3474     
3475        Returns:
3476     
3477           A copy of the real object.
3478     */
3479     clone: function() {
3480         return new Polar(this.theta, this.rho);
3481     },
3482
3483     /*
3484        Method: toComplex
3485     
3486         Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3487     
3488         Parameters:
3489
3490         simple - _optional_ If *true* this method will only return an object with x and y properties (and not the whole <Complex> instance). Default's *false*.
3491  
3492         Returns:
3493     
3494           A new <Complex> instance.
3495     */
3496     toComplex: function(simple) {
3497         var x = Math.cos(this.theta) * this.rho;
3498         var y = Math.sin(this.theta) * this.rho;
3499         if(simple) return { 'x': x, 'y': y};
3500         return new Complex(x, y);
3501     },
3502
3503     /*
3504        Method: add
3505     
3506         Adds two <Polar> instances.
3507     
3508        Parameters:
3509
3510        polar - A <Polar> number.
3511
3512        Returns:
3513     
3514           A new Polar instance.
3515     */
3516     add: function(polar) {
3517         return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3518     },
3519     
3520     /*
3521        Method: scale
3522     
3523         Scales a polar norm.
3524     
3525         Parameters:
3526
3527         number - A scale factor.
3528         
3529         Returns:
3530     
3531           A new Polar instance.
3532     */
3533     scale: function(number) {
3534         return new Polar(this.theta, this.rho * number);
3535     },
3536     
3537     /*
3538        Method: equals
3539     
3540        Comparison method.
3541
3542        Returns *true* if the theta and rho properties are equal.
3543
3544        Parameters:
3545
3546        c - A <Polar> number.
3547
3548        Returns:
3549
3550        *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3551     */
3552     equals: function(c) {
3553         return this.theta == c.theta && this.rho == c.rho;
3554     },
3555     
3556     /*
3557        Method: $add
3558     
3559         Adds two <Polar> instances affecting the current object.
3560     
3561        Paramters:
3562
3563        polar - A <Polar> instance.
3564
3565        Returns:
3566     
3567           The changed object.
3568     */
3569     $add: function(polar) {
3570         this.theta = this.theta + polar.theta; this.rho += polar.rho;
3571         return this;
3572     },
3573
3574     /*
3575        Method: $madd
3576     
3577         Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3578     
3579        Parameters:
3580
3581        polar - A <Polar> instance.
3582
3583        Returns:
3584     
3585           The changed object.
3586     */
3587     $madd: function(polar) {
3588         this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3589         return this;
3590     },
3591
3592     
3593     /*
3594        Method: $scale
3595     
3596         Scales a polar instance affecting the object.
3597     
3598       Parameters:
3599
3600       number - A scaling factor.
3601
3602       Returns:
3603     
3604           The changed object.
3605     */
3606     $scale: function(number) {
3607         this.rho *= number;
3608         return this;
3609     },
3610     
3611     /*
3612        Method: interpolate
3613     
3614         Calculates a polar interpolation between two points at a given delta moment.
3615
3616         Parameters:
3617       
3618         elem - A <Polar> instance.
3619         delta - A delta factor ranging [0, 1].
3620     
3621        Returns:
3622     
3623           A new <Polar> instance representing an interpolation between _this_ and _elem_
3624     */
3625     interpolate: function(elem, delta) {
3626         var pi = Math.PI, pi2 = pi * 2;
3627         var ch = function(t) {
3628             var a =  (t < 0)? (t % pi2) + pi2 : t % pi2;
3629             return a;
3630         };
3631         var tt = this.theta, et = elem.theta;
3632         var sum, diff = Math.abs(tt - et);
3633         if(diff == pi) {
3634           if(tt > et) {
3635             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3636           } else {
3637             sum = ch((et - pi2 + (tt - (et)) * delta));
3638           }
3639         } else if(diff >= pi) {
3640           if(tt > et) {
3641             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3642           } else {
3643             sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3644           }
3645         } else {  
3646           sum = ch((et + (tt - et) * delta)) ;
3647         }
3648         var r = (this.rho - elem.rho) * delta + elem.rho;
3649         return {
3650           'theta': sum,
3651           'rho': r
3652         };
3653     }
3654 };
3655
3656
3657 var $P = function(a, b) { return new Polar(a, b); };
3658
3659 Polar.KER = $P(0, 0);
3660
3661
3662
3663 /*
3664  * File: Complex.js
3665  * 
3666  * Defines the <Complex> class.
3667  *
3668  * Description:
3669  *
3670  * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3671  *
3672  * See also:
3673  *
3674  * <http://en.wikipedia.org/wiki/Complex_number>
3675  *
3676 */
3677
3678 /*
3679    Class: Complex
3680     
3681    A multi-purpose Complex Class with common methods.
3682  
3683    Description:
3684  
3685    The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3686  
3687    See also:
3688  
3689    <http://en.wikipedia.org/wiki/Complex_number>
3690
3691    Parameters:
3692
3693    x - _optional_ A Complex number real part.
3694    y - _optional_ A Complex number imaginary part.
3695  
3696 */
3697
3698 var Complex = function(x, y) {
3699   this.x = x;
3700   this.y = y;
3701 };
3702
3703 $jit.Complex = Complex;
3704
3705 Complex.prototype = {
3706     /*
3707        Method: getc
3708     
3709        Returns a complex number.
3710     
3711        Returns:
3712     
3713           A complex number.
3714     */
3715     getc: function() {
3716         return this;
3717     },
3718
3719     /*
3720        Method: getp
3721     
3722        Returns a <Polar> representation of this number.
3723     
3724        Parameters:
3725
3726        simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3727
3728        Returns:
3729     
3730           A variable in <Polar> coordinates.
3731     */
3732     getp: function(simple) {
3733         return this.toPolar(simple);
3734     },
3735
3736
3737     /*
3738        Method: set
3739     
3740        Sets a number.
3741
3742        Parameters:
3743
3744        c - A <Complex> or <Polar> instance.
3745     
3746     */
3747     set: function(c) {
3748       c = c.getc(true);
3749       this.x = c.x; 
3750       this.y = c.y;
3751     },
3752
3753     /*
3754        Method: setc
3755     
3756        Sets a complex number.
3757
3758        Parameters:
3759
3760        x - A <Complex> number Real part.
3761        y - A <Complex> number Imaginary part.
3762     
3763     */
3764     setc: function(x, y) {
3765         this.x = x; 
3766         this.y = y;
3767     },
3768
3769     /*
3770        Method: setp
3771     
3772        Sets a polar number.
3773
3774        Parameters:
3775
3776        theta - A <Polar> number theta property.
3777        rho - A <Polar> number rho property.
3778     
3779     */
3780     setp: function(theta, rho) {
3781         this.x = Math.cos(theta) * rho;
3782         this.y = Math.sin(theta) * rho;
3783     },
3784
3785     /*
3786        Method: clone
3787     
3788        Returns a copy of the current object.
3789     
3790        Returns:
3791     
3792           A copy of the real object.
3793     */
3794     clone: function() {
3795         return new Complex(this.x, this.y);
3796     },
3797
3798     /*
3799        Method: toPolar
3800     
3801        Transforms cartesian to polar coordinates.
3802     
3803        Parameters:
3804
3805        simple - _optional_ If *true* this method will only return an object with theta and rho properties (and not the whole <Polar> instance). Default's *false*.
3806        
3807        Returns:
3808     
3809           A new <Polar> instance.
3810     */
3811     
3812     toPolar: function(simple) {
3813         var rho = this.norm();
3814         var atan = Math.atan2(this.y, this.x);
3815         if(atan < 0) atan += Math.PI * 2;
3816         if(simple) return { 'theta': atan, 'rho': rho };
3817         return new Polar(atan, rho);
3818     },
3819     /*
3820        Method: norm
3821     
3822        Calculates a <Complex> number norm.
3823     
3824        Returns:
3825     
3826           A real number representing the complex norm.
3827     */
3828     norm: function () {
3829         return Math.sqrt(this.squaredNorm());
3830     },
3831     
3832     /*
3833        Method: squaredNorm
3834     
3835        Calculates a <Complex> number squared norm.
3836     
3837        Returns:
3838     
3839           A real number representing the complex squared norm.
3840     */
3841     squaredNorm: function () {
3842         return this.x*this.x + this.y*this.y;
3843     },
3844
3845     /*
3846        Method: add
3847     
3848        Returns the result of adding two complex numbers.
3849        
3850        Does not alter the original object.
3851
3852        Parameters:
3853     
3854           pos - A <Complex> instance.
3855     
3856        Returns:
3857     
3858          The result of adding two complex numbers.
3859     */
3860     add: function(pos) {
3861         return new Complex(this.x + pos.x, this.y + pos.y);
3862     },
3863
3864     /*
3865        Method: prod
3866     
3867        Returns the result of multiplying two <Complex> numbers.
3868        
3869        Does not alter the original object.
3870
3871        Parameters:
3872     
3873           pos - A <Complex> instance.
3874     
3875        Returns:
3876     
3877          The result of multiplying two complex numbers.
3878     */
3879     prod: function(pos) {
3880         return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3881     },
3882
3883     /*
3884        Method: conjugate
3885     
3886        Returns the conjugate of this <Complex> number.
3887
3888        Does not alter the original object.
3889
3890        Returns:
3891     
3892          The conjugate of this <Complex> number.
3893     */
3894     conjugate: function() {
3895         return new Complex(this.x, -this.y);
3896     },
3897
3898
3899     /*
3900        Method: scale
3901     
3902        Returns the result of scaling a <Complex> instance.
3903        
3904        Does not alter the original object.
3905
3906        Parameters:
3907     
3908           factor - A scale factor.
3909     
3910        Returns:
3911     
3912          The result of scaling this complex to a factor.
3913     */
3914     scale: function(factor) {
3915         return new Complex(this.x * factor, this.y * factor);
3916     },
3917
3918     /*
3919        Method: equals
3920     
3921        Comparison method.
3922
3923        Returns *true* if both real and imaginary parts are equal.
3924
3925        Parameters:
3926
3927        c - A <Complex> instance.
3928
3929        Returns:
3930
3931        A boolean instance indicating if both <Complex> numbers are equal.
3932     */
3933     equals: function(c) {
3934         return this.x == c.x && this.y == c.y;
3935     },
3936
3937     /*
3938        Method: $add
3939     
3940        Returns the result of adding two <Complex> numbers.
3941        
3942        Alters the original object.
3943
3944        Parameters:
3945     
3946           pos - A <Complex> instance.
3947     
3948        Returns:
3949     
3950          The result of adding two complex numbers.
3951     */
3952     $add: function(pos) {
3953         this.x += pos.x; this.y += pos.y;
3954         return this;    
3955     },
3956     
3957     /*
3958        Method: $prod
3959     
3960        Returns the result of multiplying two <Complex> numbers.
3961        
3962        Alters the original object.
3963
3964        Parameters:
3965     
3966           pos - A <Complex> instance.
3967     
3968        Returns:
3969     
3970          The result of multiplying two complex numbers.
3971     */
3972     $prod:function(pos) {
3973         var x = this.x, y = this.y;
3974         this.x = x*pos.x - y*pos.y;
3975         this.y = y*pos.x + x*pos.y;
3976         return this;
3977     },
3978     
3979     /*
3980        Method: $conjugate
3981     
3982        Returns the conjugate for this <Complex>.
3983        
3984        Alters the original object.
3985
3986        Returns:
3987     
3988          The conjugate for this complex.
3989     */
3990     $conjugate: function() {
3991         this.y = -this.y;
3992         return this;
3993     },
3994     
3995     /*
3996        Method: $scale
3997     
3998        Returns the result of scaling a <Complex> instance.
3999        
4000        Alters the original object.
4001
4002        Parameters:
4003     
4004           factor - A scale factor.
4005     
4006        Returns:
4007     
4008          The result of scaling this complex to a factor.
4009     */
4010     $scale: function(factor) {
4011         this.x *= factor; this.y *= factor;
4012         return this;
4013     },
4014     
4015     /*
4016        Method: $div
4017     
4018        Returns the division of two <Complex> numbers.
4019        
4020        Alters the original object.
4021
4022        Parameters:
4023     
4024           pos - A <Complex> number.
4025     
4026        Returns:
4027     
4028          The result of scaling this complex to a factor.
4029     */
4030     $div: function(pos) {
4031         var x = this.x, y = this.y;
4032         var sq = pos.squaredNorm();
4033         this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4034         return this.$scale(1 / sq);
4035     }
4036 };
4037
4038 var $C = function(a, b) { return new Complex(a, b); };
4039
4040 Complex.KER = $C(0, 0);
4041
4042
4043
4044 /*
4045  * File: Graph.js
4046  *
4047 */
4048
4049 /*
4050  Class: Graph
4051
4052  A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4053
4054  An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4055  
4056  Example:
4057
4058  (start code js)
4059    //create new visualization
4060    var viz = new $jit.Viz(options);
4061    //load JSON data
4062    viz.loadJSON(json);
4063    //access model
4064    viz.graph; //<Graph> instance
4065  (end code)
4066  
4067  Implements:
4068  
4069  The following <Graph.Util> methods are implemented in <Graph>
4070  
4071   - <Graph.Util.getNode>
4072   - <Graph.Util.eachNode>
4073   - <Graph.Util.computeLevels>
4074   - <Graph.Util.eachBFS>
4075   - <Graph.Util.clean>
4076   - <Graph.Util.getClosestNodeToPos>
4077   - <Graph.Util.getClosestNodeToOrigin>
4078  
4079 */  
4080
4081 $jit.Graph = new Class({
4082
4083   initialize: function(opt, Node, Edge, Label) {
4084     var innerOptions = {
4085     'complex': false,
4086     'Node': {}
4087     };
4088     this.Node = Node;
4089     this.Edge = Edge;
4090     this.Label = Label;
4091     this.opt = $.merge(innerOptions, opt || {});
4092     this.nodes = {};
4093     this.edges = {};
4094     
4095     //add nodeList methods
4096     var that = this;
4097     this.nodeList = {};
4098     for(var p in Accessors) {
4099       that.nodeList[p] = (function(p) {
4100         return function() {
4101           var args = Array.prototype.slice.call(arguments);
4102           that.eachNode(function(n) {
4103             n[p].apply(n, args);
4104           });
4105         };
4106       })(p);
4107     }
4108
4109  },
4110
4111 /*
4112      Method: getNode
4113     
4114      Returns a <Graph.Node> by *id*.
4115
4116      Parameters:
4117
4118      id - (string) A <Graph.Node> id.
4119
4120      Example:
4121
4122      (start code js)
4123        var node = graph.getNode('nodeId');
4124      (end code)
4125 */  
4126  getNode: function(id) {
4127     if(this.hasNode(id)) return this.nodes[id];
4128     return false;
4129  },
4130
4131  /*
4132    Method: getByName
4133   
4134    Returns a <Graph.Node> by *name*.
4135   
4136    Parameters:
4137   
4138    name - (string) A <Graph.Node> name.
4139   
4140    Example:
4141   
4142    (start code js)
4143      var node = graph.getByName('someName');
4144    (end code)
4145   */  
4146   getByName: function(name) {
4147     for(var id in this.nodes) {
4148       var n = this.nodes[id];
4149       if(n.name == name) return n;
4150     }
4151     return false;
4152   },
4153
4154 /*
4155    Method: getAdjacence
4156   
4157    Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4158
4159    Parameters:
4160
4161    id - (string) A <Graph.Node> id.
4162    id2 - (string) A <Graph.Node> id.
4163 */  
4164   getAdjacence: function (id, id2) {
4165     if(id in this.edges) {
4166       return this.edges[id][id2];
4167     }
4168     return false;
4169  },
4170
4171     /*
4172      Method: addNode
4173     
4174      Adds a node.
4175      
4176      Parameters:
4177     
4178       obj - An object with the properties described below
4179
4180       id - (string) A node id
4181       name - (string) A node's name
4182       data - (object) A node's data hash
4183
4184     See also:
4185     <Graph.Node>
4186
4187   */  
4188   addNode: function(obj) { 
4189    if(!this.nodes[obj.id]) {  
4190      var edges = this.edges[obj.id] = {};
4191      this.nodes[obj.id] = new Graph.Node($.extend({
4192         'id': obj.id,
4193         'name': obj.name,
4194         'data': $.merge(obj.data || {}, {}),
4195         'adjacencies': edges 
4196       }, this.opt.Node), 
4197       this.opt.complex, 
4198       this.Node, 
4199       this.Edge,
4200       this.Label);
4201     }
4202     return this.nodes[obj.id];
4203   },
4204   
4205     /*
4206      Method: addAdjacence
4207     
4208      Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4209      
4210      Parameters:
4211     
4212       obj - (object) A <Graph.Node> object.
4213       obj2 - (object) Another <Graph.Node> object.
4214       data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4215
4216     See also:
4217
4218     <Graph.Node>, <Graph.Adjacence>
4219     */  
4220   addAdjacence: function (obj, obj2, data) {
4221     if(!this.hasNode(obj.id)) { this.addNode(obj); }
4222     if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4223     obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4224     if(!obj.adjacentTo(obj2)) {
4225       var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4226       var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4227       adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4228       return adjsObj[obj2.id];
4229     }
4230     return this.edges[obj.id][obj2.id];
4231  },
4232
4233     /*
4234      Method: removeNode
4235     
4236      Removes a <Graph.Node> matching the specified *id*.
4237
4238      Parameters:
4239
4240      id - (string) A node's id.
4241
4242     */  
4243   removeNode: function(id) {
4244     if(this.hasNode(id)) {
4245       delete this.nodes[id];
4246       var adjs = this.edges[id];
4247       for(var to in adjs) {
4248         delete this.edges[to][id];
4249       }
4250       delete this.edges[id];
4251     }
4252   },
4253   
4254 /*
4255      Method: removeAdjacence
4256     
4257      Removes a <Graph.Adjacence> matching *id1* and *id2*.
4258
4259      Parameters:
4260
4261      id1 - (string) A <Graph.Node> id.
4262      id2 - (string) A <Graph.Node> id.
4263 */  
4264   removeAdjacence: function(id1, id2) {
4265     delete this.edges[id1][id2];
4266     delete this.edges[id2][id1];
4267   },
4268
4269    /*
4270      Method: hasNode
4271     
4272      Returns a boolean indicating if the node belongs to the <Graph> or not.
4273      
4274      Parameters:
4275     
4276         id - (string) Node id.
4277    */  
4278   hasNode: function(id) {
4279     return id in this.nodes;
4280   },
4281   
4282   /*
4283     Method: empty
4284
4285     Empties the Graph
4286
4287   */
4288   empty: function() { this.nodes = {}; this.edges = {};}
4289
4290 });
4291
4292 var Graph = $jit.Graph;
4293
4294 /*
4295  Object: Accessors
4296  
4297  Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4298  
4299  */
4300 var Accessors;
4301
4302 (function () {
4303   var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4304     var data;
4305     type = type || 'current';
4306     prefix = "$" + (prefix ? prefix + "-" : "");
4307
4308     if(type == 'current') {
4309       data = this.data;
4310     } else if(type == 'start') {
4311       data = this.startData;
4312     } else if(type == 'end') {
4313       data = this.endData;
4314     }
4315
4316     var dollar = prefix + prop;
4317
4318     if(force) {
4319       return data[dollar];
4320     }
4321
4322     if(!this.Config.overridable)
4323       return prefixConfig[prop] || 0;
4324
4325     return (dollar in data) ?
4326       data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4327   }
4328
4329   var setDataInternal = function(prefix, prop, value, type) {
4330     type = type || 'current';
4331     prefix = '$' + (prefix ? prefix + '-' : '');
4332
4333     var data;
4334
4335     if(type == 'current') {
4336       data = this.data;
4337     } else if(type == 'start') {
4338       data = this.startData;
4339     } else if(type == 'end') {
4340       data = this.endData;
4341     }
4342
4343     data[prefix + prop] = value;
4344   }
4345
4346   var removeDataInternal = function(prefix, properties) {
4347     prefix = '$' + (prefix ? prefix + '-' : '');
4348     var that = this;
4349     $.each(properties, function(prop) {
4350       var pref = prefix + prop;
4351       delete that.data[pref];
4352       delete that.endData[pref];
4353       delete that.startData[pref];
4354     });
4355   }
4356
4357   Accessors = {
4358     /*
4359     Method: getData
4360
4361     Returns the specified data value property.
4362     This is useful for querying special/reserved <Graph.Node> data properties
4363     (i.e dollar prefixed properties).
4364
4365     Parameters:
4366
4367       prop  - (string) The name of the property. The dollar sign is not needed. For
4368               example *getData(width)* will return *data.$width*.
4369       type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
4370               data properties also. These properties are used when making animations.
4371       force - (boolean) Whether to obtain the true value of the property (equivalent to
4372               *data.$prop*) or to check for *node.overridable = true* first.
4373
4374     Returns:
4375
4376       The value of the dollar prefixed property or the global Node/Edge property
4377       value if *overridable=false*
4378
4379     Example:
4380     (start code js)
4381      node.getData('width'); //will return node.data.$width if Node.overridable=true;
4382     (end code)
4383     */
4384     getData: function(prop, type, force) {
4385       return getDataInternal.call(this, "", prop, type, force, this.Config);
4386     },
4387
4388
4389     /*
4390     Method: setData
4391
4392     Sets the current data property with some specific value.
4393     This method is only useful for reserved (dollar prefixed) properties.
4394
4395     Parameters:
4396
4397       prop  - (string) The name of the property. The dollar sign is not necessary. For
4398               example *setData(width)* will set *data.$width*.
4399       value - (mixed) The value to store.
4400       type  - (string) The type of the data property to store. Default's "current" but
4401               can also be "start" or "end".
4402
4403     Example:
4404     
4405     (start code js)
4406      node.setData('width', 30);
4407     (end code)
4408     
4409     If we were to make an animation of a node/edge width then we could do
4410     
4411     (start code js)
4412       var node = viz.getNode('nodeId');
4413       //set start and end values
4414       node.setData('width', 10, 'start');
4415       node.setData('width', 30, 'end');
4416       //will animate nodes width property
4417       viz.fx.animate({
4418         modes: ['node-property:width'],
4419         duration: 1000
4420       });
4421     (end code)
4422     */
4423     setData: function(prop, value, type) {
4424       setDataInternal.call(this, "", prop, value, type);
4425     },
4426
4427     /*
4428     Method: setDataset
4429
4430     Convenience method to set multiple data values at once.
4431     
4432     Parameters:
4433     
4434     types - (array|string) A set of 'current', 'end' or 'start' values.
4435     obj - (object) A hash containing the names and values of the properties to be altered.
4436
4437     Example:
4438     (start code js)
4439       node.setDataset(['current', 'end'], {
4440         'width': [100, 5],
4441         'color': ['#fff', '#ccc']
4442       });
4443       //...or also
4444       node.setDataset('end', {
4445         'width': 5,
4446         'color': '#ccc'
4447       });
4448     (end code)
4449     
4450     See also: 
4451     
4452     <Accessors.setData>
4453     
4454     */
4455     setDataset: function(types, obj) {
4456       types = $.splat(types);
4457       for(var attr in obj) {
4458         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4459           this.setData(attr, val[i], types[i]);
4460         }
4461       }
4462     },
4463     
4464     /*
4465     Method: removeData
4466
4467     Remove data properties.
4468
4469     Parameters:
4470
4471     One or more property names as arguments. The dollar sign is not needed.
4472
4473     Example:
4474     (start code js)
4475     node.removeData('width'); //now the default width value is returned
4476     (end code)
4477     */
4478     removeData: function() {
4479       removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4480     },
4481
4482     /*
4483     Method: getCanvasStyle
4484
4485     Returns the specified canvas style data value property. This is useful for
4486     querying special/reserved <Graph.Node> canvas style data properties (i.e.
4487     dollar prefixed properties that match with $canvas-<name of canvas style>).
4488
4489     Parameters:
4490
4491       prop  - (string) The name of the property. The dollar sign is not needed. For
4492               example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4493       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4494               data properties also.
4495               
4496     Example:
4497     (start code js)
4498       node.getCanvasStyle('shadowBlur');
4499     (end code)
4500     
4501     See also:
4502     
4503     <Accessors.getData>
4504     */
4505     getCanvasStyle: function(prop, type, force) {
4506       return getDataInternal.call(
4507           this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4508     },
4509
4510     /*
4511     Method: setCanvasStyle
4512
4513     Sets the canvas style data property with some specific value.
4514     This method is only useful for reserved (dollar prefixed) properties.
4515     
4516     Parameters:
4517     
4518     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4519     value - (mixed) The value to set to the property.
4520     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4521     
4522     Example:
4523     
4524     (start code js)
4525      node.setCanvasStyle('shadowBlur', 30);
4526     (end code)
4527     
4528     If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4529     
4530     (start code js)
4531       var node = viz.getNode('nodeId');
4532       //set start and end values
4533       node.setCanvasStyle('shadowBlur', 10, 'start');
4534       node.setCanvasStyle('shadowBlur', 30, 'end');
4535       //will animate nodes canvas style property for nodes
4536       viz.fx.animate({
4537         modes: ['node-style:shadowBlur'],
4538         duration: 1000
4539       });
4540     (end code)
4541     
4542     See also:
4543     
4544     <Accessors.setData>.
4545     */
4546     setCanvasStyle: function(prop, value, type) {
4547       setDataInternal.call(this, 'canvas', prop, value, type);
4548     },
4549
4550     /*
4551     Method: setCanvasStyles
4552
4553     Convenience method to set multiple styles at once.
4554
4555     Parameters:
4556     
4557     types - (array|string) A set of 'current', 'end' or 'start' values.
4558     obj - (object) A hash containing the names and values of the properties to be altered.
4559
4560     See also:
4561     
4562     <Accessors.setDataset>.
4563     */
4564     setCanvasStyles: function(types, obj) {
4565       types = $.splat(types);
4566       for(var attr in obj) {
4567         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4568           this.setCanvasStyle(attr, val[i], types[i]);
4569         }
4570       }
4571     },
4572
4573     /*
4574     Method: removeCanvasStyle
4575
4576     Remove canvas style properties from data.
4577
4578     Parameters:
4579     
4580     A variable number of canvas style strings.
4581
4582     See also:
4583     
4584     <Accessors.removeData>.
4585     */
4586     removeCanvasStyle: function() {
4587       removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4588     },
4589
4590     /*
4591     Method: getLabelData
4592
4593     Returns the specified label data value property. This is useful for
4594     querying special/reserved <Graph.Node> label options (i.e.
4595     dollar prefixed properties that match with $label-<name of label style>).
4596
4597     Parameters:
4598
4599       prop  - (string) The name of the property. The dollar sign prefix is not needed. For
4600               example *getLabelData(size)* will return *data[$label-size]*.
4601       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4602               data properties also.
4603               
4604     See also:
4605     
4606     <Accessors.getData>.
4607     */
4608     getLabelData: function(prop, type, force) {
4609       return getDataInternal.call(
4610           this, 'label', prop, type, force, this.Label);
4611     },
4612
4613     /*
4614     Method: setLabelData
4615
4616     Sets the current label data with some specific value.
4617     This method is only useful for reserved (dollar prefixed) properties.
4618
4619     Parameters:
4620     
4621     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4622     value - (mixed) The value to set to the property.
4623     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4624     
4625     Example:
4626     
4627     (start code js)
4628      node.setLabelData('size', 30);
4629     (end code)
4630     
4631     If we were to make an animation of a node label size then we could do
4632     
4633     (start code js)
4634       var node = viz.getNode('nodeId');
4635       //set start and end values
4636       node.setLabelData('size', 10, 'start');
4637       node.setLabelData('size', 30, 'end');
4638       //will animate nodes label size
4639       viz.fx.animate({
4640         modes: ['label-property:size'],
4641         duration: 1000
4642       });
4643     (end code)
4644     
4645     See also:
4646     
4647     <Accessors.setData>.
4648     */
4649     setLabelData: function(prop, value, type) {
4650       setDataInternal.call(this, 'label', prop, value, type);
4651     },
4652
4653     /*
4654     Method: setLabelDataset
4655
4656     Convenience function to set multiple label data at once.
4657
4658     Parameters:
4659     
4660     types - (array|string) A set of 'current', 'end' or 'start' values.
4661     obj - (object) A hash containing the names and values of the properties to be altered.
4662
4663     See also:
4664     
4665     <Accessors.setDataset>.
4666     */
4667     setLabelDataset: function(types, obj) {
4668       types = $.splat(types);
4669       for(var attr in obj) {
4670         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4671           this.setLabelData(attr, val[i], types[i]);
4672         }
4673       }
4674     },
4675
4676     /*
4677     Method: removeLabelData
4678
4679     Remove label properties from data.
4680     
4681     Parameters:
4682     
4683     A variable number of label property strings.
4684
4685     See also:
4686     
4687     <Accessors.removeData>.
4688     */
4689     removeLabelData: function() {
4690       removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4691     }
4692   };
4693 })();
4694
4695 /*
4696      Class: Graph.Node
4697
4698      A <Graph> node.
4699      
4700      Implements:
4701      
4702      <Accessors> methods.
4703      
4704      The following <Graph.Util> methods are implemented by <Graph.Node>
4705      
4706     - <Graph.Util.eachAdjacency>
4707     - <Graph.Util.eachLevel>
4708     - <Graph.Util.eachSubgraph>
4709     - <Graph.Util.eachSubnode>
4710     - <Graph.Util.anySubnode>
4711     - <Graph.Util.getSubnodes>
4712     - <Graph.Util.getParents>
4713     - <Graph.Util.isDescendantOf>     
4714 */
4715 Graph.Node = new Class({
4716     
4717   initialize: function(opt, complex, Node, Edge, Label) {
4718     var innerOptions = {
4719       'id': '',
4720       'name': '',
4721       'data': {},
4722       'startData': {},
4723       'endData': {},
4724       'adjacencies': {},
4725
4726       'selected': false,
4727       'drawn': false,
4728       'exist': false,
4729
4730       'angleSpan': {
4731         'begin': 0,
4732         'end' : 0
4733       },
4734
4735       'pos': (complex && $C(0, 0)) || $P(0, 0),
4736       'startPos': (complex && $C(0, 0)) || $P(0, 0),
4737       'endPos': (complex && $C(0, 0)) || $P(0, 0)
4738     };
4739     
4740     $.extend(this, $.extend(innerOptions, opt));
4741     this.Config = this.Node = Node;
4742     this.Edge = Edge;
4743     this.Label = Label;
4744   },
4745
4746     /*
4747        Method: adjacentTo
4748     
4749        Indicates if the node is adjacent to the node specified by id
4750
4751        Parameters:
4752     
4753           id - (string) A node id.
4754     
4755        Example:
4756        (start code js)
4757         node.adjacentTo('nodeId') == true;
4758        (end code)
4759     */
4760     adjacentTo: function(node) {
4761         return node.id in this.adjacencies;
4762     },
4763
4764     /*
4765        Method: getAdjacency
4766     
4767        Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4768
4769        Parameters:
4770     
4771           id - (string) A node id.
4772     */  
4773     getAdjacency: function(id) {
4774         return this.adjacencies[id];
4775     },
4776
4777     /*
4778       Method: getPos
4779    
4780       Returns the position of the node.
4781   
4782       Parameters:
4783    
4784          type - (string) Default's *current*. Possible values are "start", "end" or "current".
4785    
4786       Returns:
4787    
4788         A <Complex> or <Polar> instance.
4789   
4790       Example:
4791       (start code js)
4792        var pos = node.getPos('end');
4793       (end code)
4794    */
4795    getPos: function(type) {
4796        type = type || "current";
4797        if(type == "current") {
4798          return this.pos;
4799        } else if(type == "end") {
4800          return this.endPos;
4801        } else if(type == "start") {
4802          return this.startPos;
4803        }
4804    },
4805    /*
4806      Method: setPos
4807   
4808      Sets the node's position.
4809   
4810      Parameters:
4811   
4812         value - (object) A <Complex> or <Polar> instance.
4813         type - (string) Default's *current*. Possible values are "start", "end" or "current".
4814   
4815      Example:
4816      (start code js)
4817       node.setPos(new $jit.Complex(0, 0), 'end');
4818      (end code)
4819   */
4820   setPos: function(value, type) {
4821       type = type || "current";
4822       var pos;
4823       if(type == "current") {
4824         pos = this.pos;
4825       } else if(type == "end") {
4826         pos = this.endPos;
4827       } else if(type == "start") {
4828         pos = this.startPos;
4829       }
4830       pos.set(value);
4831   }
4832 });
4833
4834 Graph.Node.implement(Accessors);
4835
4836 /*
4837      Class: Graph.Adjacence
4838
4839      A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4840      
4841      Implements:
4842      
4843      <Accessors> methods.
4844
4845      See also:
4846
4847      <Graph>, <Graph.Node>
4848
4849      Properties:
4850      
4851       nodeFrom - A <Graph.Node> connected by this edge.
4852       nodeTo - Another  <Graph.Node> connected by this edge.
4853       data - Node data property containing a hash (i.e {}) with custom options.
4854 */
4855 Graph.Adjacence = new Class({
4856   
4857   initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4858     this.nodeFrom = nodeFrom;
4859     this.nodeTo = nodeTo;
4860     this.data = data || {};
4861     this.startData = {};
4862     this.endData = {};
4863     this.Config = this.Edge = Edge;
4864     this.Label = Label;
4865   }
4866 });
4867
4868 Graph.Adjacence.implement(Accessors);
4869
4870 /*
4871    Object: Graph.Util
4872
4873    <Graph> traversal and processing utility object.
4874    
4875    Note:
4876    
4877    For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4878 */
4879 Graph.Util = {
4880     /*
4881        filter
4882     
4883        For internal use only. Provides a filtering function based on flags.
4884     */
4885     filter: function(param) {
4886         if(!param || !($.type(param) == 'string')) return function() { return true; };
4887         var props = param.split(" ");
4888         return function(elem) {
4889             for(var i=0; i<props.length; i++) { 
4890               if(elem[props[i]]) { 
4891                 return false; 
4892               }
4893             }
4894             return true;
4895         };
4896     },
4897     /*
4898        Method: getNode
4899     
4900        Returns a <Graph.Node> by *id*.
4901        
4902        Also implemented by:
4903        
4904        <Graph>
4905
4906        Parameters:
4907
4908        graph - (object) A <Graph> instance.
4909        id - (string) A <Graph.Node> id.
4910
4911        Example:
4912
4913        (start code js)
4914          $jit.Graph.Util.getNode(graph, 'nodeid');
4915          //or...
4916          graph.getNode('nodeid');
4917        (end code)
4918     */
4919     getNode: function(graph, id) {
4920         return graph.nodes[id];
4921     },
4922     
4923     /*
4924        Method: eachNode
4925     
4926        Iterates over <Graph> nodes performing an *action*.
4927        
4928        Also implemented by:
4929        
4930        <Graph>.
4931
4932        Parameters:
4933
4934        graph - (object) A <Graph> instance.
4935        action - (function) A callback function having a <Graph.Node> as first formal parameter.
4936
4937        Example:
4938        (start code js)
4939          $jit.Graph.Util.eachNode(graph, function(node) {
4940           alert(node.name);
4941          });
4942          //or...
4943          graph.eachNode(function(node) {
4944            alert(node.name);
4945          });
4946        (end code)
4947     */
4948     eachNode: function(graph, action, flags) {
4949         var filter = this.filter(flags);
4950         for(var i in graph.nodes) {
4951           if(filter(graph.nodes[i])) action(graph.nodes[i]);
4952         } 
4953     },
4954     
4955     /*
4956        Method: eachAdjacency
4957     
4958        Iterates over <Graph.Node> adjacencies applying the *action* function.
4959        
4960        Also implemented by:
4961        
4962        <Graph.Node>.
4963
4964        Parameters:
4965
4966        node - (object) A <Graph.Node>.
4967        action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4968
4969        Example:
4970        (start code js)
4971          $jit.Graph.Util.eachAdjacency(node, function(adj) {
4972           alert(adj.nodeTo.name);
4973          });
4974          //or...
4975          node.eachAdjacency(function(adj) {
4976            alert(adj.nodeTo.name);
4977          });
4978        (end code)
4979     */
4980     eachAdjacency: function(node, action, flags) {
4981         var adj = node.adjacencies, filter = this.filter(flags);
4982         for(var id in adj) {
4983           var a = adj[id];
4984           if(filter(a)) {
4985             if(a.nodeFrom != node) {
4986               var tmp = a.nodeFrom;
4987               a.nodeFrom = a.nodeTo;
4988               a.nodeTo = tmp;
4989             }
4990             action(a, id);
4991           }
4992         }
4993     },
4994
4995      /*
4996        Method: computeLevels
4997     
4998        Performs a BFS traversal setting the correct depth for each node.
4999         
5000        Also implemented by:
5001        
5002        <Graph>.
5003        
5004        Note:
5005        
5006        The depth of each node can then be accessed by 
5007        >node._depth
5008
5009        Parameters:
5010
5011        graph - (object) A <Graph>.
5012        id - (string) A starting node id for the BFS traversal.
5013        startDepth - (optional|number) A minimum depth value. Default's 0.
5014
5015     */
5016     computeLevels: function(graph, id, startDepth, flags) {
5017         startDepth = startDepth || 0;
5018         var filter = this.filter(flags);
5019         this.eachNode(graph, function(elem) {
5020             elem._flag = false;
5021             elem._depth = -1;
5022         }, flags);
5023         var root = graph.getNode(id);
5024         root._depth = startDepth;
5025         var queue = [root];
5026         while(queue.length != 0) {
5027             var node = queue.pop();
5028             node._flag = true;
5029             this.eachAdjacency(node, function(adj) {
5030                 var n = adj.nodeTo;
5031                 if(n._flag == false && filter(n)) {
5032                     if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5033                     queue.unshift(n);
5034                 }
5035             }, flags);
5036         }
5037     },
5038
5039     /*
5040        Method: eachBFS
5041     
5042        Performs a BFS traversal applying *action* to each <Graph.Node>.
5043        
5044        Also implemented by:
5045        
5046        <Graph>.
5047
5048        Parameters:
5049
5050        graph - (object) A <Graph>.
5051        id - (string) A starting node id for the BFS traversal.
5052        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5053
5054        Example:
5055        (start code js)
5056          $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5057           alert(node.name);
5058          });
5059          //or...
5060          graph.eachBFS('mynodeid', function(node) {
5061            alert(node.name);
5062          });
5063        (end code)
5064     */
5065     eachBFS: function(graph, id, action, flags) {
5066         var filter = this.filter(flags);
5067         this.clean(graph);
5068         var queue = [graph.getNode(id)];
5069         while(queue.length != 0) {
5070             var node = queue.pop();
5071             node._flag = true;
5072             action(node, node._depth);
5073             this.eachAdjacency(node, function(adj) {
5074                 var n = adj.nodeTo;
5075                 if(n._flag == false && filter(n)) {
5076                     n._flag = true;
5077                     queue.unshift(n);
5078                 }
5079             }, flags);
5080         }
5081     },
5082     
5083     /*
5084        Method: eachLevel
5085     
5086        Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5087        
5088        Also implemented by:
5089        
5090        <Graph.Node>.
5091
5092        Parameters:
5093        
5094        node - (object) A <Graph.Node>.
5095        levelBegin - (number) A relative level value.
5096        levelEnd - (number) A relative level value.
5097        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5098
5099     */
5100     eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5101         var d = node._depth, filter = this.filter(flags), that = this;
5102         levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5103         (function loopLevel(node, levelBegin, levelEnd) {
5104             var d = node._depth;
5105             if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5106             if(d < levelEnd) {
5107                 that.eachAdjacency(node, function(adj) {
5108                     var n = adj.nodeTo;
5109                     if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5110                 });
5111             }
5112         })(node, levelBegin + d, levelEnd + d);      
5113     },
5114
5115     /*
5116        Method: eachSubgraph
5117     
5118        Iterates over a node's children recursively.
5119        
5120        Also implemented by:
5121        
5122        <Graph.Node>.
5123
5124        Parameters:
5125        node - (object) A <Graph.Node>.
5126        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5127
5128        Example:
5129        (start code js)
5130          $jit.Graph.Util.eachSubgraph(node, function(node) {
5131            alert(node.name);
5132          });
5133          //or...
5134          node.eachSubgraph(function(node) {
5135            alert(node.name);
5136          });
5137        (end code)
5138     */
5139     eachSubgraph: function(node, action, flags) {
5140       this.eachLevel(node, 0, false, action, flags);
5141     },
5142
5143     /*
5144        Method: eachSubnode
5145     
5146        Iterates over a node's children (without deeper recursion).
5147        
5148        Also implemented by:
5149        
5150        <Graph.Node>.
5151        
5152        Parameters:
5153        node - (object) A <Graph.Node>.
5154        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5155
5156        Example:
5157        (start code js)
5158          $jit.Graph.Util.eachSubnode(node, function(node) {
5159           alert(node.name);
5160          });
5161          //or...
5162          node.eachSubnode(function(node) {
5163            alert(node.name);
5164          });
5165        (end code)
5166     */
5167     eachSubnode: function(node, action, flags) {
5168         this.eachLevel(node, 1, 1, action, flags);
5169     },
5170
5171     /*
5172        Method: anySubnode
5173     
5174        Returns *true* if any subnode matches the given condition.
5175        
5176        Also implemented by:
5177        
5178        <Graph.Node>.
5179
5180        Parameters:
5181        node - (object) A <Graph.Node>.
5182        cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5183
5184        Example:
5185        (start code js)
5186          $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5187          //or...
5188          node.anySubnode(function(node) { return node.name == 'mynodename'; });
5189        (end code)
5190     */
5191     anySubnode: function(node, cond, flags) {
5192       var flag = false;
5193       cond = cond || $.lambda(true);
5194       var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5195       this.eachSubnode(node, function(elem) {
5196         if(c(elem)) flag = true;
5197       }, flags);
5198       return flag;
5199     },
5200   
5201     /*
5202        Method: getSubnodes
5203     
5204        Collects all subnodes for a specified node. 
5205        The *level* parameter filters nodes having relative depth of *level* from the root node. 
5206        
5207        Also implemented by:
5208        
5209        <Graph.Node>.
5210
5211        Parameters:
5212        node - (object) A <Graph.Node>.
5213        level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5214
5215        Returns:
5216        An array of nodes.
5217
5218     */
5219     getSubnodes: function(node, level, flags) {
5220         var ans = [], that = this;
5221         level = level || 0;
5222         var levelStart, levelEnd;
5223         if($.type(level) == 'array') {
5224             levelStart = level[0];
5225             levelEnd = level[1];
5226         } else {
5227             levelStart = level;
5228             levelEnd = Number.MAX_VALUE - node._depth;
5229         }
5230         this.eachLevel(node, levelStart, levelEnd, function(n) {
5231             ans.push(n);
5232         }, flags);
5233         return ans;
5234     },
5235   
5236   
5237     /*
5238        Method: getParents
5239     
5240        Returns an Array of <Graph.Nodes> which are parents of the given node.
5241        
5242        Also implemented by:
5243        
5244        <Graph.Node>.
5245
5246        Parameters:
5247        node - (object) A <Graph.Node>.
5248
5249        Returns:
5250        An Array of <Graph.Nodes>.
5251
5252        Example:
5253        (start code js)
5254          var pars = $jit.Graph.Util.getParents(node);
5255          //or...
5256          var pars = node.getParents();
5257          
5258          if(pars.length > 0) {
5259            //do stuff with parents
5260          }
5261        (end code)
5262     */
5263     getParents: function(node) {
5264         var ans = [];
5265         this.eachAdjacency(node, function(adj) {
5266             var n = adj.nodeTo;
5267             if(n._depth < node._depth) ans.push(n);
5268         });
5269         return ans;
5270     },
5271     
5272     /*
5273     Method: isDescendantOf
5274  
5275     Returns a boolean indicating if some node is descendant of the node with the given id. 
5276
5277     Also implemented by:
5278     
5279     <Graph.Node>.
5280     
5281     
5282     Parameters:
5283     node - (object) A <Graph.Node>.
5284     id - (string) A <Graph.Node> id.
5285
5286     Example:
5287     (start code js)
5288       $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5289       //or...
5290       node.isDescendantOf('nodeid');//true|false
5291     (end code)
5292  */
5293  isDescendantOf: function(node, id) {
5294     if(node.id == id) return true;
5295     var pars = this.getParents(node), ans = false;
5296     for ( var i = 0; !ans && i < pars.length; i++) {
5297     ans = ans || this.isDescendantOf(pars[i], id);
5298   }
5299     return ans;
5300  },
5301
5302  /*
5303      Method: clean
5304   
5305      Cleans flags from nodes.
5306
5307      Also implemented by:
5308      
5309      <Graph>.
5310      
5311      Parameters:
5312      graph - A <Graph> instance.
5313   */
5314   clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5315   
5316   /* 
5317     Method: getClosestNodeToOrigin 
5318   
5319     Returns the closest node to the center of canvas.
5320   
5321     Also implemented by:
5322     
5323     <Graph>.
5324     
5325     Parameters:
5326    
5327      graph - (object) A <Graph> instance.
5328      prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5329   
5330   */
5331   getClosestNodeToOrigin: function(graph, prop, flags) {
5332    return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5333   },
5334   
5335   /* 
5336     Method: getClosestNodeToPos
5337   
5338     Returns the closest node to the given position.
5339   
5340     Also implemented by:
5341     
5342     <Graph>.
5343     
5344     Parameters:
5345    
5346      graph - (object) A <Graph> instance.
5347      pos - (object) A <Complex> or <Polar> instance.
5348      prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5349   
5350   */
5351   getClosestNodeToPos: function(graph, pos, prop, flags) {
5352    var node = null;
5353    prop = prop || 'current';
5354    pos = pos && pos.getc(true) || Complex.KER;
5355    var distance = function(a, b) {
5356      var d1 = a.x - b.x, d2 = a.y - b.y;
5357      return d1 * d1 + d2 * d2;
5358    };
5359    this.eachNode(graph, function(elem) {
5360      node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5361          node.getPos(prop).getc(true), pos)) ? elem : node;
5362    }, flags);
5363    return node;
5364   } 
5365 };
5366
5367 //Append graph methods to <Graph>
5368 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5369   Graph.prototype[m] = function() {
5370     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5371   };
5372 });
5373
5374 //Append node methods to <Graph.Node>
5375 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5376   Graph.Node.prototype[m] = function() {
5377     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5378   };
5379 });
5380
5381 /*
5382  * File: Graph.Op.js
5383  *
5384 */
5385
5386 /*
5387    Object: Graph.Op
5388
5389    Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>, 
5390    morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5391
5392 */
5393 Graph.Op = {
5394
5395     options: {
5396       type: 'nothing',
5397       duration: 2000,
5398       hideLabels: true,
5399       fps:30
5400     },
5401     
5402     initialize: function(viz) {
5403       this.viz = viz;
5404     },
5405
5406     /*
5407        Method: removeNode
5408     
5409        Removes one or more <Graph.Nodes> from the visualization. 
5410        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5411
5412        Parameters:
5413     
5414         node - (string|array) The node's id. Can also be an array having many ids.
5415         opt - (object) Animation options. It's an object with optional properties described below
5416         type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5417         duration - Described in <Options.Fx>.
5418         fps - Described in <Options.Fx>.
5419         transition - Described in <Options.Fx>.
5420         hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5421    
5422       Example:
5423       (start code js)
5424         var viz = new $jit.Viz(options);
5425         viz.op.removeNode('nodeId', {
5426           type: 'fade:seq',
5427           duration: 1000,
5428           hideLabels: false,
5429           transition: $jit.Trans.Quart.easeOut
5430         });
5431         //or also
5432         viz.op.removeNode(['someId', 'otherId'], {
5433           type: 'fade:con',
5434           duration: 1500
5435         });
5436       (end code)
5437     */
5438   
5439     removeNode: function(node, opt) {
5440         var viz = this.viz;
5441         var options = $.merge(this.options, viz.controller, opt);
5442         var n = $.splat(node);
5443         var i, that, nodeObj;
5444         switch(options.type) {
5445             case 'nothing':
5446                 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5447                 break;
5448             
5449             case 'replot':
5450                 this.removeNode(n, { type: 'nothing' });
5451                 viz.labels.clearLabels();
5452                 viz.refresh(true);
5453                 break;
5454             
5455             case 'fade:seq': case 'fade':
5456                 that = this;
5457                 //set alpha to 0 for nodes to remove.
5458                 for(i=0; i<n.length; i++) {
5459                     nodeObj = viz.graph.getNode(n[i]);
5460                     nodeObj.setData('alpha', 0, 'end');
5461                 }
5462                 viz.fx.animate($.merge(options, {
5463                     modes: ['node-property:alpha'],
5464                     onComplete: function() {
5465                         that.removeNode(n, { type: 'nothing' });
5466                         viz.labels.clearLabels();
5467                         viz.reposition();
5468                         viz.fx.animate($.merge(options, {
5469                             modes: ['linear']
5470                         }));
5471                     }
5472                 }));
5473                 break;
5474             
5475             case 'fade:con':
5476                 that = this;
5477                 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5478                 for(i=0; i<n.length; i++) {
5479                     nodeObj = viz.graph.getNode(n[i]);
5480                     nodeObj.setData('alpha', 0, 'end');
5481                     nodeObj.ignore = true;
5482                 }
5483                 viz.reposition();
5484                 viz.fx.animate($.merge(options, {
5485                     modes: ['node-property:alpha', 'linear'],
5486                     onComplete: function() {
5487                         that.removeNode(n, { type: 'nothing' });
5488                     }
5489                 }));
5490                 break;
5491             
5492             case 'iter':
5493                 that = this;
5494                 viz.fx.sequence({
5495                     condition: function() { return n.length != 0; },
5496                     step: function() { that.removeNode(n.shift(), { type: 'nothing' });  viz.labels.clearLabels(); },
5497                     onComplete: function() { options.onComplete(); },
5498                     duration: Math.ceil(options.duration / n.length)
5499                 });
5500                 break;
5501                 
5502             default: this.doError();
5503         }
5504     },
5505     
5506     /*
5507        Method: removeEdge
5508     
5509        Removes one or more <Graph.Adjacences> from the visualization. 
5510        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5511
5512        Parameters:
5513     
5514        vertex - (array) An array having two strings which are the ids of the nodes connected by this edge (i.e ['id1', 'id2']). Can also be a two dimensional array holding many edges (i.e [['id1', 'id2'], ['id3', 'id4'], ...]).
5515        opt - (object) Animation options. It's an object with optional properties described below
5516        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5517        duration - Described in <Options.Fx>.
5518        fps - Described in <Options.Fx>.
5519        transition - Described in <Options.Fx>.
5520        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5521    
5522       Example:
5523       (start code js)
5524         var viz = new $jit.Viz(options);
5525         viz.op.removeEdge(['nodeId', 'otherId'], {
5526           type: 'fade:seq',
5527           duration: 1000,
5528           hideLabels: false,
5529           transition: $jit.Trans.Quart.easeOut
5530         });
5531         //or also
5532         viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5533           type: 'fade:con',
5534           duration: 1500
5535         });
5536       (end code)
5537     
5538     */
5539     removeEdge: function(vertex, opt) {
5540         var viz = this.viz;
5541         var options = $.merge(this.options, viz.controller, opt);
5542         var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5543         var i, that, adj;
5544         switch(options.type) {
5545             case 'nothing':
5546                 for(i=0; i<v.length; i++)   viz.graph.removeAdjacence(v[i][0], v[i][1]);
5547                 break;
5548             
5549             case 'replot':
5550                 this.removeEdge(v, { type: 'nothing' });
5551                 viz.refresh(true);
5552                 break;
5553             
5554             case 'fade:seq': case 'fade':
5555                 that = this;
5556                 //set alpha to 0 for edges to remove.
5557                 for(i=0; i<v.length; i++) {
5558                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5559                     if(adj) {
5560                         adj.setData('alpha', 0,'end');
5561                     }
5562                 }
5563                 viz.fx.animate($.merge(options, {
5564                     modes: ['edge-property:alpha'],
5565                     onComplete: function() {
5566                         that.removeEdge(v, { type: 'nothing' });
5567                         viz.reposition();
5568                         viz.fx.animate($.merge(options, {
5569                             modes: ['linear']
5570                         }));
5571                     }
5572                 }));
5573                 break;
5574             
5575             case 'fade:con':
5576                 that = this;
5577                 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5578                 for(i=0; i<v.length; i++) {
5579                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5580                     if(adj) {
5581                         adj.setData('alpha',0 ,'end');
5582                         adj.ignore = true;
5583                     }
5584                 }
5585                 viz.reposition();
5586                 viz.fx.animate($.merge(options, {
5587                     modes: ['edge-property:alpha', 'linear'],
5588                     onComplete: function() {
5589                         that.removeEdge(v, { type: 'nothing' });
5590                     }
5591                 }));
5592                 break;
5593             
5594             case 'iter':
5595                 that = this;
5596                 viz.fx.sequence({
5597                     condition: function() { return v.length != 0; },
5598                     step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5599                     onComplete: function() { options.onComplete(); },
5600                     duration: Math.ceil(options.duration / v.length)
5601                 });
5602                 break;
5603                 
5604             default: this.doError();
5605         }
5606     },
5607     
5608     /*
5609        Method: sum
5610     
5611        Adds a new graph to the visualization. 
5612        The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization. 
5613        The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5614
5615        Parameters:
5616     
5617        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5618        opt - (object) Animation options. It's an object with optional properties described below
5619        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con".
5620        duration - Described in <Options.Fx>.
5621        fps - Described in <Options.Fx>.
5622        transition - Described in <Options.Fx>.
5623        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5624    
5625       Example:
5626       (start code js)
5627         //...json contains a tree or graph structure...
5628
5629         var viz = new $jit.Viz(options);
5630         viz.op.sum(json, {
5631           type: 'fade:seq',
5632           duration: 1000,
5633           hideLabels: false,
5634           transition: $jit.Trans.Quart.easeOut
5635         });
5636         //or also
5637         viz.op.sum(json, {
5638           type: 'fade:con',
5639           duration: 1500
5640         });
5641       (end code)
5642     
5643     */
5644     sum: function(json, opt) {
5645         var viz = this.viz;
5646         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5647         var graph;
5648         viz.root = opt.id || viz.root;
5649         switch(options.type) {
5650             case 'nothing':
5651                 graph = viz.construct(json);
5652                 graph.eachNode(function(elem) {
5653                     elem.eachAdjacency(function(adj) {
5654                         viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5655                     });
5656                 });
5657                 break;
5658             
5659             case 'replot':
5660                 viz.refresh(true);
5661                 this.sum(json, { type: 'nothing' });
5662                 viz.refresh(true);
5663                 break;
5664             
5665             case 'fade:seq': case 'fade': case 'fade:con':
5666                 that = this;
5667                 graph = viz.construct(json);
5668
5669                 //set alpha to 0 for nodes to add.
5670                 var fadeEdges = this.preprocessSum(graph);
5671                 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5672                 viz.reposition();
5673                 if(options.type != 'fade:con') {
5674                     viz.fx.animate($.merge(options, {
5675                         modes: ['linear'],
5676                         onComplete: function() {
5677                             viz.fx.animate($.merge(options, {
5678                                 modes: modes,
5679                                 onComplete: function() {
5680                                     options.onComplete();
5681                                 }
5682                             }));
5683                         }
5684                     }));
5685                 } else {
5686                     viz.graph.eachNode(function(elem) {
5687                         if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5688                           elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5689                         }
5690                     });
5691                     viz.fx.animate($.merge(options, {
5692                         modes: ['linear'].concat(modes)
5693                     }));
5694                 }
5695                 break;
5696
5697             default: this.doError();
5698         }
5699     },
5700     
5701     /*
5702        Method: morph
5703     
5704        This method will transform the current visualized graph into the new JSON representation passed in the method. 
5705        The JSON object must at least have the root node in common with the current visualized graph.
5706
5707        Parameters:
5708     
5709        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5710        opt - (object) Animation options. It's an object with optional properties described below
5711        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5712        duration - Described in <Options.Fx>.
5713        fps - Described in <Options.Fx>.
5714        transition - Described in <Options.Fx>.
5715        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5716        id - (string) The shared <Graph.Node> id between both graphs.
5717        
5718        extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to 
5719                     *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation. 
5720                     For animating these extra-parameters you have to specify an object that has animation groups as keys and animation 
5721                     properties as values, just like specified in <Graph.Plot.animate>.
5722    
5723       Example:
5724       (start code js)
5725         //...json contains a tree or graph structure...
5726
5727         var viz = new $jit.Viz(options);
5728         viz.op.morph(json, {
5729           type: 'fade',
5730           duration: 1000,
5731           hideLabels: false,
5732           transition: $jit.Trans.Quart.easeOut
5733         });
5734         //or also
5735         viz.op.morph(json, {
5736           type: 'fade',
5737           duration: 1500
5738         });
5739         //if the json data contains dollar prefixed params
5740         //like $width or $height these too can be animated
5741         viz.op.morph(json, {
5742           type: 'fade',
5743           duration: 1500
5744         }, {
5745           'node-property': ['width', 'height']
5746         });
5747       (end code)
5748     
5749     */
5750     morph: function(json, opt, extraModes) {
5751         var viz = this.viz;
5752         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5753         var graph;
5754         //TODO(nico) this hack makes morphing work with the Hypertree. 
5755         //Need to check if it has been solved and this can be removed.
5756         viz.root = opt.id || viz.root;
5757         switch(options.type) {
5758             case 'nothing':
5759                 graph = viz.construct(json);
5760                 graph.eachNode(function(elem) {
5761                   var nodeExists = viz.graph.hasNode(elem.id);  
5762                   elem.eachAdjacency(function(adj) {
5763                     var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5764                     viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5765                     //Update data properties if the node existed
5766                     if(adjExists) {
5767                       var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5768                       for(var prop in (adj.data || {})) {
5769                         addedAdj.data[prop] = adj.data[prop];
5770                       }
5771                     }
5772                   });
5773                   //Update data properties if the node existed
5774                   if(nodeExists) {
5775                     var addedNode = viz.graph.getNode(elem.id);
5776                     for(var prop in (elem.data || {})) {
5777                       addedNode.data[prop] = elem.data[prop];
5778                     }
5779                   }
5780                 });
5781                 viz.graph.eachNode(function(elem) {
5782                     elem.eachAdjacency(function(adj) {
5783                         if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5784                             viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5785                         }
5786                     });
5787                     if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5788                 });
5789                 
5790                 break;
5791             
5792             case 'replot':
5793                 viz.labels.clearLabels(true);
5794                 this.morph(json, { type: 'nothing' });
5795                 viz.refresh(true);
5796                 viz.refresh(true);
5797                 break;
5798                 
5799             case 'fade:seq': case 'fade': case 'fade:con':
5800                 that = this;
5801                 graph = viz.construct(json);
5802                 //preprocessing for nodes to delete.
5803                 //get node property modes to interpolate
5804                 var nodeModes = extraModes && ('node-property' in extraModes) 
5805                   && $.map($.splat(extraModes['node-property']), 
5806                       function(n) { return '$' + n; });
5807                 viz.graph.eachNode(function(elem) {
5808                   var graphNode = graph.getNode(elem.id);   
5809                   if(!graphNode) {
5810                       elem.setData('alpha', 1);
5811                       elem.setData('alpha', 1, 'start');
5812                       elem.setData('alpha', 0, 'end');
5813                       elem.ignore = true;
5814                     } else {
5815                       //Update node data information
5816                       var graphNodeData = graphNode.data;
5817                       for(var prop in graphNodeData) {
5818                         if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5819                           elem.endData[prop] = graphNodeData[prop];
5820                         } else {
5821                           elem.data[prop] = graphNodeData[prop];
5822                         }
5823                       }
5824                     }
5825                 }); 
5826                 viz.graph.eachNode(function(elem) {
5827                     if(elem.ignore) return;
5828                     elem.eachAdjacency(function(adj) {
5829                         if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5830                         var nodeFrom = graph.getNode(adj.nodeFrom.id);
5831                         var nodeTo = graph.getNode(adj.nodeTo.id);
5832                         if(!nodeFrom.adjacentTo(nodeTo)) {
5833                             var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5834                             fadeEdges = true;
5835                             adj.setData('alpha', 1);
5836                             adj.setData('alpha', 1, 'start');
5837                             adj.setData('alpha', 0, 'end');
5838                         }
5839                     });
5840                 }); 
5841                 //preprocessing for adding nodes.
5842                 var fadeEdges = this.preprocessSum(graph);
5843
5844                 var modes = !fadeEdges? ['node-property:alpha'] : 
5845                                         ['node-property:alpha', 
5846                                          'edge-property:alpha'];
5847                 //Append extra node-property animations (if any)
5848                 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))? 
5849                     (':' + $.splat(extraModes['node-property']).join(':')) : '');
5850                 //Append extra edge-property animations (if any)
5851                 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))? 
5852                     (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5853                 //Add label-property animations (if any)
5854                 if(extraModes && ('label-property' in extraModes)) {
5855                   modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5856                 }
5857                 viz.reposition();
5858                 viz.graph.eachNode(function(elem) {
5859                     if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5860                       elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5861                     }
5862                 });
5863                 viz.fx.animate($.merge(options, {
5864                     modes: ['polar'].concat(modes),
5865                     onComplete: function() {
5866                         viz.graph.eachNode(function(elem) {
5867                             if(elem.ignore) viz.graph.removeNode(elem.id);
5868                         });
5869                         viz.graph.eachNode(function(elem) {
5870                             elem.eachAdjacency(function(adj) {
5871                                 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5872                             });
5873                         });
5874                         options.onComplete();
5875                     }
5876                 }));
5877                 break;
5878
5879             default:;
5880         }
5881     },
5882
5883     
5884   /*
5885     Method: contract
5886  
5887     Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5888     
5889     Parameters:
5890  
5891     node - (object) A <Graph.Node>.
5892     opt - (object) An object containing options described below
5893     type - (string) Whether to 'replot' or 'animate' the contraction.
5894    
5895     There are also a number of Animation options. For more information see <Options.Fx>.
5896
5897     Example:
5898     (start code js)
5899      var viz = new $jit.Viz(options);
5900      viz.op.contract(node, {
5901        type: 'animate',
5902        duration: 1000,
5903        hideLabels: true,
5904        transition: $jit.Trans.Quart.easeOut
5905      });
5906    (end code)
5907  
5908    */
5909     contract: function(node, opt) {
5910       var viz = this.viz;
5911       if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5912       opt = $.merge(this.options, viz.config, opt || {}, {
5913         'modes': ['node-property:alpha:span', 'linear']
5914       });
5915       node.collapsed = true;
5916       (function subn(n) {
5917         n.eachSubnode(function(ch) {
5918           ch.ignore = true;
5919           ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5920           subn(ch);
5921         });
5922       })(node);
5923       if(opt.type == 'animate') {
5924         viz.compute('end');
5925         if(viz.rotated) {
5926           viz.rotate(viz.rotated, 'none', {
5927             'property':'end'
5928           });
5929         }
5930         (function subn(n) {
5931           n.eachSubnode(function(ch) {
5932             ch.setPos(node.getPos('end'), 'end');
5933             subn(ch);
5934           });
5935         })(node);
5936         viz.fx.animate(opt);
5937       } else if(opt.type == 'replot'){
5938         viz.refresh();
5939       }
5940     },
5941     
5942     /*
5943     Method: expand
5944  
5945     Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5946     
5947     Parameters:
5948  
5949     node - (object) A <Graph.Node>.
5950     opt - (object) An object containing options described below
5951     type - (string) Whether to 'replot' or 'animate'.
5952      
5953     There are also a number of Animation options. For more information see <Options.Fx>.
5954
5955     Example:
5956     (start code js)
5957       var viz = new $jit.Viz(options);
5958       viz.op.expand(node, {
5959         type: 'animate',
5960         duration: 1000,
5961         hideLabels: true,
5962         transition: $jit.Trans.Quart.easeOut
5963       });
5964     (end code)
5965  
5966    */
5967     expand: function(node, opt) {
5968       if(!('collapsed' in node)) return;
5969       var viz = this.viz;
5970       opt = $.merge(this.options, viz.config, opt || {}, {
5971         'modes': ['node-property:alpha:span', 'linear']
5972       });
5973       delete node.collapsed;
5974       (function subn(n) {
5975         n.eachSubnode(function(ch) {
5976           delete ch.ignore;
5977           ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5978           subn(ch);
5979         });
5980       })(node);
5981       if(opt.type == 'animate') {
5982         viz.compute('end');
5983         if(viz.rotated) {
5984           viz.rotate(viz.rotated, 'none', {
5985             'property':'end'
5986           });
5987         }
5988         viz.fx.animate(opt);
5989       } else if(opt.type == 'replot'){
5990         viz.refresh();
5991       }
5992     },
5993
5994     preprocessSum: function(graph) {
5995         var viz = this.viz;
5996         graph.eachNode(function(elem) {
5997             if(!viz.graph.hasNode(elem.id)) {
5998                 viz.graph.addNode(elem);
5999                 var n = viz.graph.getNode(elem.id);
6000                 n.setData('alpha', 0);
6001                 n.setData('alpha', 0, 'start');
6002                 n.setData('alpha', 1, 'end');
6003             }
6004         }); 
6005         var fadeEdges = false;
6006         graph.eachNode(function(elem) {
6007             elem.eachAdjacency(function(adj) {
6008                 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6009                 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6010                 if(!nodeFrom.adjacentTo(nodeTo)) {
6011                     var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6012                     if(nodeFrom.startAlpha == nodeFrom.endAlpha 
6013                     && nodeTo.startAlpha == nodeTo.endAlpha) {
6014                         fadeEdges = true;
6015                         adj.setData('alpha', 0);
6016                         adj.setData('alpha', 0, 'start');
6017                         adj.setData('alpha', 1, 'end');
6018                     } 
6019                 }
6020             });
6021         }); 
6022         return fadeEdges;
6023     }
6024 };
6025
6026
6027
6028 /*
6029    File: Helpers.js
6030  
6031    Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6032    Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6033    position is over the rendered shape.
6034    
6035    Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and 
6036    *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6037    
6038    Example:
6039    (start code js)
6040    //implement a new node type
6041    $jit.Viz.Plot.NodeTypes.implement({
6042      'customNodeType': {
6043        'render': function(node, canvas) {
6044          this.nodeHelper.circle.render ...
6045        },
6046        'contains': function(node, pos) {
6047          this.nodeHelper.circle.contains ...
6048        }
6049      }
6050    });
6051    //implement an edge type
6052    $jit.Viz.Plot.EdgeTypes.implement({
6053      'customNodeType': {
6054        'render': function(node, canvas) {
6055          this.edgeHelper.circle.render ...
6056        },
6057        //optional
6058        'contains': function(node, pos) {
6059          this.edgeHelper.circle.contains ...
6060        }
6061      }
6062    });
6063    (end code)
6064
6065 */
6066
6067 /*
6068    Object: NodeHelper
6069    
6070    Contains rendering and other type of primitives for simple shapes.
6071  */
6072 var NodeHelper = {
6073   'none': {
6074     'render': $.empty,
6075     'contains': $.lambda(false)
6076   },
6077   /*
6078    Object: NodeHelper.circle
6079    */
6080   'circle': {
6081     /*
6082      Method: render
6083      
6084      Renders a circle into the canvas.
6085      
6086      Parameters:
6087      
6088      type - (string) Possible options are 'fill' or 'stroke'.
6089      pos - (object) An *x*, *y* object with the position of the center of the circle.
6090      radius - (number) The radius of the circle to be rendered.
6091      canvas - (object) A <Canvas> instance.
6092      
6093      Example:
6094      (start code js)
6095      NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6096      (end code)
6097      */
6098     'render': function(type, pos, radius, canvas){
6099       var ctx = canvas.getCtx();
6100       ctx.beginPath();
6101       ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6102       ctx.closePath();
6103       ctx[type]();
6104     },
6105     /*
6106     Method: contains
6107     
6108     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6109     
6110     Parameters:
6111     
6112     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6113     pos - (object) An *x*, *y* object with the position to check.
6114     radius - (number) The radius of the rendered circle.
6115     
6116     Example:
6117     (start code js)
6118     NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6119     (end code)
6120     */
6121     'contains': function(npos, pos, radius){
6122       var diffx = npos.x - pos.x, 
6123           diffy = npos.y - pos.y, 
6124           diff = diffx * diffx + diffy * diffy;
6125       return diff <= radius * radius;
6126     }
6127   },
6128   /*
6129   Object: NodeHelper.ellipse
6130   */
6131   'ellipse': {
6132     /*
6133     Method: render
6134     
6135     Renders an ellipse into the canvas.
6136     
6137     Parameters:
6138     
6139     type - (string) Possible options are 'fill' or 'stroke'.
6140     pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6141     width - (number) The width of the ellipse.
6142     height - (number) The height of the ellipse.
6143     canvas - (object) A <Canvas> instance.
6144     
6145     Example:
6146     (start code js)
6147     NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6148     (end code)
6149     */
6150     'render': function(type, pos, width, height, canvas){
6151       var ctx = canvas.getCtx();
6152       height /= 2;
6153       width /= 2;
6154       ctx.save();
6155       ctx.scale(width / height, height / width);
6156       ctx.beginPath();
6157       ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6158           Math.PI * 2, true);
6159       ctx.closePath();
6160       ctx[type]();
6161       ctx.restore();
6162     },
6163     /*
6164     Method: contains
6165     
6166     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6167     
6168     Parameters:
6169     
6170     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6171     pos - (object) An *x*, *y* object with the position to check.
6172     width - (number) The width of the rendered ellipse.
6173     height - (number) The height of the rendered ellipse.
6174     
6175     Example:
6176     (start code js)
6177     NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6178     (end code)
6179     */
6180     'contains': function(npos, pos, width, height){
6181       // TODO(nico): be more precise...
6182       width /= 2; 
6183       height /= 2;
6184       var dist = (width + height) / 2, 
6185           diffx = npos.x - pos.x, 
6186           diffy = npos.y - pos.y, 
6187           diff = diffx * diffx + diffy * diffy;
6188       return diff <= dist * dist;
6189     }
6190   },
6191   /*
6192   Object: NodeHelper.square
6193   */
6194   'square': {
6195     /*
6196     Method: render
6197     
6198     Renders a square into the canvas.
6199     
6200     Parameters:
6201     
6202     type - (string) Possible options are 'fill' or 'stroke'.
6203     pos - (object) An *x*, *y* object with the position of the center of the square.
6204     dim - (number) The radius (or half-diameter) of the square.
6205     canvas - (object) A <Canvas> instance.
6206     
6207     Example:
6208     (start code js)
6209     NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6210     (end code)
6211     */
6212     'render': function(type, pos, dim, canvas){
6213       canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6214     },
6215     /*
6216     Method: contains
6217     
6218     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6219     
6220     Parameters:
6221     
6222     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6223     pos - (object) An *x*, *y* object with the position to check.
6224     dim - (number) The radius (or half-diameter) of the square.
6225     
6226     Example:
6227     (start code js)
6228     NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6229     (end code)
6230     */
6231     'contains': function(npos, pos, dim){
6232       return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6233     }
6234   },
6235   /*
6236   Object: NodeHelper.rectangle
6237   */
6238   'rectangle': {
6239     /*
6240     Method: render
6241     
6242     Renders a rectangle into the canvas.
6243     
6244     Parameters:
6245     
6246     type - (string) Possible options are 'fill' or 'stroke'.
6247     pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6248     width - (number) The width of the rectangle.
6249     height - (number) The height of the rectangle.
6250     canvas - (object) A <Canvas> instance.
6251     
6252     Example:
6253     (start code js)
6254     NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6255     (end code)
6256     */
6257     'render': function(type, pos, width, height, canvas){
6258       canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2, 
6259                                       width, height);
6260     },
6261     /*
6262     Method: contains
6263     
6264     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6265     
6266     Parameters:
6267     
6268     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6269     pos - (object) An *x*, *y* object with the position to check.
6270     width - (number) The width of the rendered rectangle.
6271     height - (number) The height of the rendered rectangle.
6272     
6273     Example:
6274     (start code js)
6275     NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6276     (end code)
6277     */
6278     'contains': function(npos, pos, width, height){
6279       return Math.abs(pos.x - npos.x) <= width / 2
6280           && Math.abs(pos.y - npos.y) <= height / 2;
6281     }
6282   },
6283   /*
6284   Object: NodeHelper.triangle
6285   */
6286   'triangle': {
6287     /*
6288     Method: render
6289     
6290     Renders a triangle into the canvas.
6291     
6292     Parameters:
6293     
6294     type - (string) Possible options are 'fill' or 'stroke'.
6295     pos - (object) An *x*, *y* object with the position of the center of the triangle.
6296     dim - (number) The dimension of the triangle.
6297     canvas - (object) A <Canvas> instance.
6298     
6299     Example:
6300     (start code js)
6301     NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6302     (end code)
6303     */
6304     'render': function(type, pos, dim, canvas){
6305       var ctx = canvas.getCtx(), 
6306           c1x = pos.x, 
6307           c1y = pos.y - dim, 
6308           c2x = c1x - dim, 
6309           c2y = pos.y + dim, 
6310           c3x = c1x + dim, 
6311           c3y = c2y;
6312       ctx.beginPath();
6313       ctx.moveTo(c1x, c1y);
6314       ctx.lineTo(c2x, c2y);
6315       ctx.lineTo(c3x, c3y);
6316       ctx.closePath();
6317       ctx[type]();
6318     },
6319     /*
6320     Method: contains
6321     
6322     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6323     
6324     Parameters:
6325     
6326     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6327     pos - (object) An *x*, *y* object with the position to check.
6328     dim - (number) The dimension of the shape.
6329     
6330     Example:
6331     (start code js)
6332     NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6333     (end code)
6334     */
6335     'contains': function(npos, pos, dim) {
6336       return NodeHelper.circle.contains(npos, pos, dim);
6337     }
6338   },
6339   /*
6340   Object: NodeHelper.star
6341   */
6342   'star': {
6343     /*
6344     Method: render
6345     
6346     Renders a star into the canvas.
6347     
6348     Parameters:
6349     
6350     type - (string) Possible options are 'fill' or 'stroke'.
6351     pos - (object) An *x*, *y* object with the position of the center of the star.
6352     dim - (number) The dimension of the star.
6353     canvas - (object) A <Canvas> instance.
6354     
6355     Example:
6356     (start code js)
6357     NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6358     (end code)
6359     */
6360     'render': function(type, pos, dim, canvas){
6361       var ctx = canvas.getCtx(), 
6362           pi5 = Math.PI / 5;
6363       ctx.save();
6364       ctx.translate(pos.x, pos.y);
6365       ctx.beginPath();
6366       ctx.moveTo(dim, 0);
6367       for (var i = 0; i < 9; i++) {
6368         ctx.rotate(pi5);
6369         if (i % 2 == 0) {
6370           ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6371         } else {
6372           ctx.lineTo(dim, 0);
6373         }
6374       }
6375       ctx.closePath();
6376       ctx[type]();
6377       ctx.restore();
6378     },
6379     /*
6380     Method: contains
6381     
6382     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6383     
6384     Parameters:
6385     
6386     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6387     pos - (object) An *x*, *y* object with the position to check.
6388     dim - (number) The dimension of the shape.
6389     
6390     Example:
6391     (start code js)
6392     NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6393     (end code)
6394     */
6395     'contains': function(npos, pos, dim) {
6396       return NodeHelper.circle.contains(npos, pos, dim);
6397     }
6398   }
6399 };
6400
6401 /*
6402   Object: EdgeHelper
6403   
6404   Contains rendering primitives for simple edge shapes.
6405 */
6406 var EdgeHelper = {
6407   /*
6408     Object: EdgeHelper.line
6409   */
6410   'line': {
6411       /*
6412       Method: render
6413       
6414       Renders a line into the canvas.
6415       
6416       Parameters:
6417       
6418       from - (object) An *x*, *y* object with the starting position of the line.
6419       to - (object) An *x*, *y* object with the ending position of the line.
6420       canvas - (object) A <Canvas> instance.
6421       
6422       Example:
6423       (start code js)
6424       EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6425       (end code)
6426       */
6427       'render': function(from, to, canvas){
6428         var ctx = canvas.getCtx();
6429         ctx.beginPath();
6430         ctx.moveTo(from.x, from.y);
6431         ctx.lineTo(to.x, to.y);
6432         ctx.stroke();
6433       },
6434       /*
6435       Method: contains
6436       
6437       Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6438       
6439       Parameters:
6440       
6441       posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6442       posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6443       pos - (object) An *x*, *y* object with the position to check.
6444       epsilon - (number) The dimension of the shape.
6445       
6446       Example:
6447       (start code js)
6448       EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6449       (end code)
6450       */
6451       'contains': function(posFrom, posTo, pos, epsilon) {
6452         var min = Math.min, 
6453             max = Math.max,
6454             minPosX = min(posFrom.x, posTo.x),
6455             maxPosX = max(posFrom.x, posTo.x),
6456             minPosY = min(posFrom.y, posTo.y),
6457             maxPosY = max(posFrom.y, posTo.y);
6458         
6459         if(pos.x >= minPosX && pos.x <= maxPosX 
6460             && pos.y >= minPosY && pos.y <= maxPosY) {
6461           if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6462             return true;
6463           }
6464           var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6465           return Math.abs(dist - pos.y) <= epsilon;
6466         }
6467         return false;
6468       }
6469     },
6470   /*
6471     Object: EdgeHelper.arrow
6472   */
6473   'arrow': {
6474       /*
6475       Method: render
6476       
6477       Renders an arrow into the canvas.
6478       
6479       Parameters:
6480       
6481       from - (object) An *x*, *y* object with the starting position of the arrow.
6482       to - (object) An *x*, *y* object with the ending position of the arrow.
6483       dim - (number) The dimension of the arrow.
6484       swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6485       canvas - (object) A <Canvas> instance.
6486       
6487       Example:
6488       (start code js)
6489       EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6490       (end code)
6491       */
6492     'render': function(from, to, dim, swap, canvas){
6493         var ctx = canvas.getCtx();
6494         // invert edge direction
6495         if (swap) {
6496           var tmp = from;
6497           from = to;
6498           to = tmp;
6499         }
6500         var vect = new Complex(to.x - from.x, to.y - from.y);
6501         vect.$scale(dim / vect.norm());
6502         var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6503             normal = new Complex(-vect.y / 2, vect.x / 2),
6504             v1 = intermediatePoint.add(normal), 
6505             v2 = intermediatePoint.$add(normal.$scale(-1));
6506         
6507         ctx.beginPath();
6508         ctx.moveTo(from.x, from.y);
6509         ctx.lineTo(to.x, to.y);
6510         ctx.stroke();
6511         ctx.beginPath();
6512         ctx.moveTo(v1.x, v1.y);
6513         ctx.lineTo(v2.x, v2.y);
6514         ctx.lineTo(to.x, to.y);
6515         ctx.closePath();
6516         ctx.fill();
6517     },
6518     /*
6519     Method: contains
6520     
6521     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6522     
6523     Parameters:
6524     
6525     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6526     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6527     pos - (object) An *x*, *y* object with the position to check.
6528     epsilon - (number) The dimension of the shape.
6529     
6530     Example:
6531     (start code js)
6532     EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6533     (end code)
6534     */
6535     'contains': function(posFrom, posTo, pos, epsilon) {
6536       return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6537     }
6538   },
6539   /*
6540     Object: EdgeHelper.hyperline
6541   */
6542   'hyperline': {
6543     /*
6544     Method: render
6545     
6546     Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6547     
6548     Parameters:
6549     
6550     from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6551     to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6552     r - (number) The scaling factor.
6553     canvas - (object) A <Canvas> instance.
6554     
6555     Example:
6556     (start code js)
6557     EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6558     (end code)
6559     */
6560     'render': function(from, to, r, canvas){
6561       var ctx = canvas.getCtx();  
6562       var centerOfCircle = computeArcThroughTwoPoints(from, to);
6563       if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6564           || centerOfCircle.ratio < 0) {
6565         ctx.beginPath();
6566         ctx.moveTo(from.x * r, from.y * r);
6567         ctx.lineTo(to.x * r, to.y * r);
6568         ctx.stroke();
6569       } else {
6570         var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6571             - centerOfCircle.x);
6572         var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6573             - centerOfCircle.x);
6574         var sense = sense(angleBegin, angleEnd);
6575         ctx.beginPath();
6576         ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6577             * r, angleBegin, angleEnd, sense);
6578         ctx.stroke();
6579       }
6580       /*      
6581         Calculates the arc parameters through two points.
6582         
6583         More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane> 
6584       
6585         Parameters:
6586       
6587         p1 - A <Complex> instance.
6588         p2 - A <Complex> instance.
6589         scale - The Disk's diameter.
6590       
6591         Returns:
6592       
6593         An object containing some arc properties.
6594       */
6595       function computeArcThroughTwoPoints(p1, p2){
6596         var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6597         var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6598         // Fall back to a straight line
6599         if (aDen == 0)
6600           return {
6601             x: 0,
6602             y: 0,
6603             ratio: -1
6604           };
6605     
6606         var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6607         var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6608         var x = -a / 2;
6609         var y = -b / 2;
6610         var squaredRatio = (a * a + b * b) / 4 - 1;
6611         // Fall back to a straight line
6612         if (squaredRatio < 0)
6613           return {
6614             x: 0,
6615             y: 0,
6616             ratio: -1
6617           };
6618         var ratio = Math.sqrt(squaredRatio);
6619         var out = {
6620           x: x,
6621           y: y,
6622           ratio: ratio > 1000? -1 : ratio,
6623           a: a,
6624           b: b
6625         };
6626     
6627         return out;
6628       }
6629       /*      
6630         Sets angle direction to clockwise (true) or counterclockwise (false). 
6631          
6632         Parameters: 
6633       
6634            angleBegin - Starting angle for drawing the arc. 
6635            angleEnd - The HyperLine will be drawn from angleBegin to angleEnd. 
6636       
6637         Returns: 
6638       
6639            A Boolean instance describing the sense for drawing the HyperLine. 
6640       */
6641       function sense(angleBegin, angleEnd){
6642         return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6643             : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6644       }
6645     },
6646     /*
6647     Method: contains
6648     
6649     Not Implemented
6650     
6651     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6652     
6653     Parameters:
6654     
6655     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6656     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6657     pos - (object) An *x*, *y* object with the position to check.
6658     epsilon - (number) The dimension of the shape.
6659     
6660     Example:
6661     (start code js)
6662     EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6663     (end code)
6664     */
6665     'contains': $.lambda(false)
6666   }
6667 };
6668
6669
6670 /*
6671  * File: Graph.Plot.js
6672  */
6673
6674 /*
6675    Object: Graph.Plot
6676
6677    <Graph> rendering and animation methods.
6678    
6679    Properties:
6680    
6681    nodeHelper - <NodeHelper> object.
6682    edgeHelper - <EdgeHelper> object.
6683 */
6684 Graph.Plot = {
6685     //Default intializer
6686     initialize: function(viz, klass){
6687       this.viz = viz;
6688       this.config = viz.config;
6689       this.node = viz.config.Node;
6690       this.edge = viz.config.Edge;
6691       this.animation = new Animation;
6692       this.nodeTypes = new klass.Plot.NodeTypes;
6693       this.edgeTypes = new klass.Plot.EdgeTypes;
6694       this.labels = viz.labels;
6695    },
6696
6697     //Add helpers
6698     nodeHelper: NodeHelper,
6699     edgeHelper: EdgeHelper,
6700     
6701     Interpolator: {
6702         //node/edge property parsers
6703         'map': {
6704           'border': 'color',
6705           'color': 'color',
6706           'width': 'number',
6707           'height': 'number',
6708           'dim': 'number',
6709           'alpha': 'number',
6710           'lineWidth': 'number',
6711           'angularWidth':'number',
6712           'span':'number',
6713           'valueArray':'array-number',
6714           'dimArray':'array-number'
6715           //'colorArray':'array-color'
6716         },
6717         
6718         //canvas specific parsers
6719         'canvas': {
6720           'globalAlpha': 'number',
6721           'fillStyle': 'color',
6722           'strokeStyle': 'color',
6723           'lineWidth': 'number',
6724           'shadowBlur': 'number',
6725           'shadowColor': 'color',
6726           'shadowOffsetX': 'number',
6727           'shadowOffsetY': 'number',
6728           'miterLimit': 'number'
6729         },
6730   
6731         //label parsers
6732         'label': {
6733           'size': 'number',
6734           'color': 'color'
6735         },
6736   
6737         //Number interpolator
6738         'compute': function(from, to, delta) {
6739           return from + (to - from) * delta;
6740         },
6741         
6742         //Position interpolators
6743         'moebius': function(elem, props, delta, vector) {
6744           var v = vector.scale(-delta);  
6745           if(v.norm() < 1) {
6746               var x = v.x, y = v.y;
6747               var ans = elem.startPos
6748                 .getc().moebiusTransformation(v);
6749               elem.pos.setc(ans.x, ans.y);
6750               v.x = x; v.y = y;
6751             }           
6752         },
6753
6754         'linear': function(elem, props, delta) {
6755             var from = elem.startPos.getc(true);
6756             var to = elem.endPos.getc(true);
6757             elem.pos.setc(this.compute(from.x, to.x, delta), 
6758                           this.compute(from.y, to.y, delta));
6759         },
6760
6761         'polar': function(elem, props, delta) {
6762           var from = elem.startPos.getp(true);
6763           var to = elem.endPos.getp();
6764           var ans = to.interpolate(from, delta);
6765           elem.pos.setp(ans.theta, ans.rho);
6766         },
6767         
6768         //Graph's Node/Edge interpolators
6769         'number': function(elem, prop, delta, getter, setter) {
6770           var from = elem[getter](prop, 'start');
6771           var to = elem[getter](prop, 'end');
6772           elem[setter](prop, this.compute(from, to, delta));
6773         },
6774
6775         'color': function(elem, prop, delta, getter, setter) {
6776           var from = $.hexToRgb(elem[getter](prop, 'start'));
6777           var to = $.hexToRgb(elem[getter](prop, 'end'));
6778           var comp = this.compute;
6779           var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6780                                 parseInt(comp(from[1], to[1], delta)),
6781                                 parseInt(comp(from[2], to[2], delta))]);
6782           
6783           elem[setter](prop, val);
6784         },
6785         
6786         'array-number': function(elem, prop, delta, getter, setter) {
6787           var from = elem[getter](prop, 'start'),
6788               to = elem[getter](prop, 'end'),
6789               cur = [];
6790           for(var i=0, l=from.length; i<l; i++) {
6791             var fromi = from[i], toi = to[i];
6792             if(fromi.length) {
6793               for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6794                 curi.push(this.compute(fromi[j], toi[j], delta));
6795               }
6796               cur.push(curi);
6797             } else {
6798               cur.push(this.compute(fromi, toi, delta));
6799             }
6800           }
6801           elem[setter](prop, cur);
6802         },
6803         
6804         'node': function(elem, props, delta, map, getter, setter) {
6805           map = this[map];
6806           if(props) {
6807             var len = props.length;
6808             for(var i=0; i<len; i++) {
6809               var pi = props[i];
6810               this[map[pi]](elem, pi, delta, getter, setter);
6811             }
6812           } else {
6813             for(var pi in map) {
6814               this[map[pi]](elem, pi, delta, getter, setter);
6815             }
6816           }
6817         },
6818         
6819         'edge': function(elem, props, delta, mapKey, getter, setter) {
6820             var adjs = elem.adjacencies;
6821             for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6822         },
6823         
6824         'node-property': function(elem, props, delta) {
6825           this['node'](elem, props, delta, 'map', 'getData', 'setData');
6826         },
6827         
6828         'edge-property': function(elem, props, delta) {
6829           this['edge'](elem, props, delta, 'map', 'getData', 'setData');  
6830         },
6831
6832         'label-property': function(elem, props, delta) {
6833           this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6834         },
6835         
6836         'node-style': function(elem, props, delta) {
6837           this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6838         },
6839         
6840         'edge-style': function(elem, props, delta) {
6841           this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');  
6842         }
6843     },
6844     
6845   
6846     /*
6847        sequence
6848     
6849        Iteratively performs an action while refreshing the state of the visualization.
6850
6851        Parameters:
6852
6853        options - (object) An object containing some sequence options described below
6854        condition - (function) A function returning a boolean instance in order to stop iterations.
6855        step - (function) A function to execute on each step of the iteration.
6856        onComplete - (function) A function to execute when the sequence finishes.
6857        duration - (number) Duration (in milliseconds) of each step.
6858
6859       Example:
6860        (start code js)
6861         var rg = new $jit.RGraph(options);
6862         var i = 0;
6863         rg.fx.sequence({
6864           condition: function() {
6865            return i == 10;
6866           },
6867           step: function() {
6868             alert(i++);
6869           },
6870           onComplete: function() {
6871            alert('done!');
6872           }
6873         });
6874        (end code)
6875
6876     */
6877     sequence: function(options) {
6878         var that = this;
6879         options = $.merge({
6880           condition: $.lambda(false),
6881           step: $.empty,
6882           onComplete: $.empty,
6883           duration: 200
6884         }, options || {});
6885
6886         var interval = setInterval(function() {
6887           if(options.condition()) {
6888             options.step();
6889           } else {
6890             clearInterval(interval);
6891             options.onComplete();
6892           }
6893           that.viz.refresh(true);
6894         }, options.duration);
6895     },
6896     
6897     /*
6898       prepare
6899  
6900       Prepare graph position and other attribute values before performing an Animation. 
6901       This method is used internally by the Toolkit.
6902       
6903       See also:
6904        
6905        <Animation>, <Graph.Plot.animate>
6906
6907     */
6908     prepare: function(modes) {
6909       var graph = this.viz.graph,
6910           accessors = {
6911             'node-property': {
6912               'getter': 'getData',
6913               'setter': 'setData'
6914             },
6915             'edge-property': {
6916               'getter': 'getData',
6917               'setter': 'setData'
6918             },
6919             'node-style': {
6920               'getter': 'getCanvasStyle',
6921               'setter': 'setCanvasStyle'
6922             },
6923             'edge-style': {
6924               'getter': 'getCanvasStyle',
6925               'setter': 'setCanvasStyle'
6926             }
6927           };
6928
6929       //parse modes
6930       var m = {};
6931       if($.type(modes) == 'array') {
6932         for(var i=0, len=modes.length; i < len; i++) {
6933           var elems = modes[i].split(':');
6934           m[elems.shift()] = elems;
6935         }
6936       } else {
6937         for(var p in modes) {
6938           if(p == 'position') {
6939             m[modes.position] = [];
6940           } else {
6941             m[p] = $.splat(modes[p]);
6942           }
6943         }
6944       }
6945       
6946       graph.eachNode(function(node) { 
6947         node.startPos.set(node.pos);
6948         $.each(['node-property', 'node-style'], function(p) {
6949           if(p in m) {
6950             var prop = m[p];
6951             for(var i=0, l=prop.length; i < l; i++) {
6952               node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6953             }
6954           }
6955         });
6956         $.each(['edge-property', 'edge-style'], function(p) {
6957           if(p in m) {
6958             var prop = m[p];
6959             node.eachAdjacency(function(adj) {
6960               for(var i=0, l=prop.length; i < l; i++) {
6961                 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6962               }
6963             });
6964           }
6965         });
6966       });
6967       return m;
6968     },
6969     
6970     /*
6971        Method: animate
6972     
6973        Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6974
6975        Parameters:
6976
6977        opt - (object) Animation options. The object properties are described below
6978        duration - (optional) Described in <Options.Fx>.
6979        fps - (optional) Described in <Options.Fx>.
6980        hideLabels - (optional|boolean) Whether to hide labels during the animation.
6981        modes - (required|object) An object with animation modes (described below).
6982
6983        Animation modes:
6984        
6985        Animation modes are strings representing different node/edge and graph properties that you'd like to animate. 
6986        They are represented by an object that has as keys main categories of properties to animate and as values a list 
6987        of these specific properties. The properties are described below
6988        
6989        position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6990        node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6991        edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6992        label-property - Describes which Label properties will be interpolated. These properties can be any of the ones defined in <Options.Label> like color or size.
6993        node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6994        edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6995
6996        Example:
6997        (start code js)
6998        var viz = new $jit.Viz(options);
6999        //...tweak some Data, CanvasStyles or LabelData properties...
7000        viz.fx.animate({
7001          modes: {
7002            'position': 'linear',
7003            'node-property': ['width', 'height'],
7004            'node-style': 'shadowColor',
7005            'label-property': 'size'
7006          },
7007          hideLabels: false
7008        });
7009        //...can also be written like this...
7010        viz.fx.animate({
7011          modes: ['linear',
7012                  'node-property:width:height',
7013                  'node-style:shadowColor',
7014                  'label-property:size'],
7015          hideLabels: false
7016        });
7017        (end code)
7018     */
7019     animate: function(opt, versor) {
7020       opt = $.merge(this.viz.config, opt || {});
7021       var that = this,
7022           viz = this.viz,
7023           graph  = viz.graph,
7024           interp = this.Interpolator,
7025           animation =  opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7026       //prepare graph values
7027       var m = this.prepare(opt.modes);
7028       
7029       //animate
7030       if(opt.hideLabels) this.labels.hideLabels(true);
7031       animation.setOptions($.merge(opt, {
7032         $animating: false,
7033         compute: function(delta) {
7034           graph.eachNode(function(node) { 
7035             for(var p in m) {
7036               interp[p](node, m[p], delta, versor);
7037             }
7038           });
7039           that.plot(opt, this.$animating, delta);
7040           this.$animating = true;
7041         },
7042         complete: function() {
7043           if(opt.hideLabels) that.labels.hideLabels(false);
7044           that.plot(opt);
7045           opt.onComplete();
7046           opt.onAfterCompute();
7047         }       
7048       })).start();
7049     },
7050     
7051     /*
7052       nodeFx
7053    
7054       Apply animation to node properties like color, width, height, dim, etc.
7055   
7056       Parameters:
7057   
7058       options - Animation options. This object properties is described below
7059       elements - The Elements to be transformed. This is an object that has a properties
7060       
7061       (start code js)
7062       'elements': {
7063         //can also be an array of ids
7064         'id': 'id-of-node-to-transform',
7065         //properties to be modified. All properties are optional.
7066         'properties': {
7067           'color': '#ccc', //some color
7068           'width': 10, //some width
7069           'height': 10, //some height
7070           'dim': 20, //some dim
7071           'lineWidth': 10 //some line width
7072         } 
7073       }
7074       (end code)
7075       
7076       - _reposition_ Whether to recalculate positions and add a motion animation. 
7077       This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7078       
7079       - _onComplete_ A method that is called when the animation completes.
7080       
7081       ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7082   
7083       Example:
7084       (start code js)
7085        var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7086        rg.fx.nodeFx({
7087          'elements': {
7088            'id':'mynodeid',
7089            'properties': {
7090              'color':'#ccf'
7091            },
7092            'transition': Trans.Quart.easeOut
7093          }
7094        });
7095       (end code)    
7096    */
7097    nodeFx: function(opt) {
7098      var viz = this.viz,
7099          graph  = viz.graph,
7100          animation = this.nodeFxAnimation,
7101          options = $.merge(this.viz.config, {
7102            'elements': {
7103              'id': false,
7104              'properties': {}
7105            },
7106            'reposition': false
7107          });
7108      opt = $.merge(options, opt || {}, {
7109        onBeforeCompute: $.empty,
7110        onAfterCompute: $.empty
7111      });
7112      //check if an animation is running
7113      animation.stopTimer();
7114      var props = opt.elements.properties;
7115      //set end values for nodes
7116      if(!opt.elements.id) {
7117        graph.eachNode(function(n) {
7118          for(var prop in props) {
7119            n.setData(prop, props[prop], 'end');
7120          }
7121        });
7122      } else {
7123        var ids = $.splat(opt.elements.id);
7124        $.each(ids, function(id) {
7125          var n = graph.getNode(id);
7126          if(n) {
7127            for(var prop in props) {
7128              n.setData(prop, props[prop], 'end');
7129            }
7130          }
7131        });
7132      }
7133      //get keys
7134      var propnames = [];
7135      for(var prop in props) propnames.push(prop);
7136      //add node properties modes
7137      var modes = ['node-property:' + propnames.join(':')];
7138      //set new node positions
7139      if(opt.reposition) {
7140        modes.push('linear');
7141        viz.compute('end');
7142      }
7143      //animate
7144      this.animate($.merge(opt, {
7145        modes: modes,
7146        type: 'nodefx'
7147      }));
7148    },
7149
7150     
7151     /*
7152        Method: plot
7153     
7154        Plots a <Graph>.
7155
7156        Parameters:
7157
7158        opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7159
7160        Example:
7161
7162        (start code js)
7163        var viz = new $jit.Viz(options);
7164        viz.fx.plot(); 
7165        (end code)
7166
7167     */
7168     plot: function(opt, animating) {
7169       var viz = this.viz, 
7170       aGraph = viz.graph, 
7171       canvas = viz.canvas, 
7172       id = viz.root, 
7173       that = this, 
7174       ctx = canvas.getCtx(), 
7175       min = Math.min,
7176       opt = opt || this.viz.controller;
7177       opt.clearCanvas && canvas.clear();
7178         
7179       var root = aGraph.getNode(id);
7180       if(!root) return;
7181       
7182       var T = !!root.visited;
7183       aGraph.eachNode(function(node) {
7184         var nodeAlpha = node.getData('alpha');
7185         node.eachAdjacency(function(adj) {
7186           var nodeTo = adj.nodeTo;
7187           if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7188             !animating && opt.onBeforePlotLine(adj);
7189             ctx.save();
7190             ctx.globalAlpha = min(nodeAlpha, 
7191                 nodeTo.getData('alpha'), 
7192                 adj.getData('alpha'));
7193             that.plotLine(adj, canvas, animating);
7194             ctx.restore();
7195             !animating && opt.onAfterPlotLine(adj);
7196           }
7197         });
7198         ctx.save();
7199         if(node.drawn) {
7200           !animating && opt.onBeforePlotNode(node);
7201           that.plotNode(node, canvas, animating);
7202           !animating && opt.onAfterPlotNode(node);
7203         }
7204         if(!that.labelsHidden && opt.withLabels) {
7205           if(node.drawn && nodeAlpha >= 0.95) {
7206             that.labels.plotLabel(canvas, node, opt);
7207           } else {
7208             that.labels.hideLabel(node, false);
7209           }
7210         }
7211         ctx.restore();
7212         node.visited = !T;
7213       });
7214     },
7215
7216   /*
7217       Plots a Subtree.
7218    */
7219    plotTree: function(node, opt, animating) {
7220        var that = this, 
7221        viz = this.viz, 
7222        canvas = viz.canvas,
7223        config = this.config,
7224        ctx = canvas.getCtx();
7225        var nodeAlpha = node.getData('alpha');
7226        node.eachSubnode(function(elem) {
7227          if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7228              var adj = node.getAdjacency(elem.id);
7229              !animating && opt.onBeforePlotLine(adj);
7230              ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7231              that.plotLine(adj, canvas, animating);
7232              !animating && opt.onAfterPlotLine(adj);
7233              that.plotTree(elem, opt, animating);
7234          }
7235        });
7236        if(node.drawn) {
7237            !animating && opt.onBeforePlotNode(node);
7238            this.plotNode(node, canvas, animating);
7239            !animating && opt.onAfterPlotNode(node);
7240            if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95) 
7241                this.labels.plotLabel(canvas, node, opt);
7242            else 
7243                this.labels.hideLabel(node, false);
7244        } else {
7245            this.labels.hideLabel(node, true);
7246        }
7247    },
7248
7249   /*
7250        Method: plotNode
7251     
7252        Plots a <Graph.Node>.
7253
7254        Parameters:
7255        
7256        node - (object) A <Graph.Node>.
7257        canvas - (object) A <Canvas> element.
7258
7259     */
7260     plotNode: function(node, canvas, animating) {
7261         var f = node.getData('type'), 
7262             ctxObj = this.node.CanvasStyles;
7263         if(f != 'none') {
7264           var width = node.getData('lineWidth'),
7265               color = node.getData('color'),
7266               alpha = node.getData('alpha'),
7267               ctx = canvas.getCtx();
7268           
7269           ctx.lineWidth = width;
7270           ctx.fillStyle = ctx.strokeStyle = color;
7271           ctx.globalAlpha = alpha;
7272           
7273           for(var s in ctxObj) {
7274             ctx[s] = node.getCanvasStyle(s);
7275           }
7276
7277           this.nodeTypes[f].render.call(this, node, canvas, animating);
7278         }
7279     },
7280     
7281     /*
7282        Method: plotLine
7283     
7284        Plots a <Graph.Adjacence>.
7285
7286        Parameters:
7287
7288        adj - (object) A <Graph.Adjacence>.
7289        canvas - (object) A <Canvas> instance.
7290
7291     */
7292     plotLine: function(adj, canvas, animating) {
7293       var f = adj.getData('type'),
7294           ctxObj = this.edge.CanvasStyles;
7295       if(f != 'none') {
7296         var width = adj.getData('lineWidth'),
7297             color = adj.getData('color'),
7298             ctx = canvas.getCtx();
7299         
7300         ctx.lineWidth = width;
7301         ctx.fillStyle = ctx.strokeStyle = color;
7302         
7303         for(var s in ctxObj) {
7304           ctx[s] = adj.getCanvasStyle(s);
7305         }
7306
7307         this.edgeTypes[f].render.call(this, adj, canvas, animating);
7308       }
7309     }    
7310   
7311 };
7312
7313
7314
7315 /*
7316  * File: Graph.Label.js
7317  *
7318 */
7319
7320 /*
7321    Object: Graph.Label
7322
7323    An interface for plotting/hiding/showing labels.
7324
7325    Description:
7326
7327    This is a generic interface for plotting/hiding/showing labels.
7328    The <Graph.Label> interface is implemented in multiple ways to provide
7329    different label types.
7330
7331    For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7332    HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels. 
7333    The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7334    
7335    All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7336 */
7337
7338 Graph.Label = {};
7339
7340 /*
7341    Class: Graph.Label.Native
7342
7343    Implements labels natively, using the Canvas text API.
7344 */
7345 Graph.Label.Native = new Class({
7346     /*
7347        Method: plotLabel
7348
7349        Plots a label for a given node.
7350
7351        Parameters:
7352
7353        canvas - (object) A <Canvas> instance.
7354        node - (object) A <Graph.Node>.
7355        controller - (object) A configuration object.
7356        
7357        Example:
7358        
7359        (start code js)
7360        var viz = new $jit.Viz(options);
7361        var node = viz.graph.getNode('nodeId');
7362        viz.labels.plotLabel(viz.canvas, node, viz.config);
7363        (end code)
7364     */
7365     plotLabel: function(canvas, node, controller) {
7366       var ctx = canvas.getCtx();
7367       var pos = node.pos.getc(true);
7368
7369       ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7370       ctx.textAlign = node.getLabelData('textAlign');
7371       ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7372       ctx.textBaseline = node.getLabelData('textBaseline');
7373
7374       this.renderLabel(canvas, node, controller);
7375     },
7376
7377     /*
7378        renderLabel
7379
7380        Does the actual rendering of the label in the canvas. The default
7381        implementation renders the label close to the position of the node, this
7382        method should be overriden to position the labels differently.
7383
7384        Parameters:
7385
7386        canvas - A <Canvas> instance.
7387        node - A <Graph.Node>.
7388        controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7389     */
7390     renderLabel: function(canvas, node, controller) {
7391       var ctx = canvas.getCtx();
7392       var pos = node.pos.getc(true);
7393       ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7394     },
7395
7396     hideLabel: $.empty,
7397     hideLabels: $.empty
7398 });
7399
7400 /*
7401    Class: Graph.Label.DOM
7402
7403    Abstract Class implementing some DOM label methods.
7404
7405    Implemented by:
7406
7407    <Graph.Label.HTML> and <Graph.Label.SVG>.
7408
7409 */
7410 Graph.Label.DOM = new Class({
7411     //A flag value indicating if node labels are being displayed or not.
7412     labelsHidden: false,
7413     //Label container
7414     labelContainer: false,
7415     //Label elements hash.
7416     labels: {},
7417
7418     /*
7419        Method: getLabelContainer
7420
7421        Lazy fetcher for the label container.
7422
7423        Returns:
7424
7425        The label container DOM element.
7426
7427        Example:
7428
7429       (start code js)
7430         var viz = new $jit.Viz(options);
7431         var labelContainer = viz.labels.getLabelContainer();
7432         alert(labelContainer.innerHTML);
7433       (end code)
7434     */
7435     getLabelContainer: function() {
7436       return this.labelContainer ?
7437         this.labelContainer :
7438         this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7439     },
7440
7441     /*
7442        Method: getLabel
7443
7444        Lazy fetcher for the label element.
7445
7446        Parameters:
7447
7448        id - (string) The label id (which is also a <Graph.Node> id).
7449
7450        Returns:
7451
7452        The label element.
7453
7454        Example:
7455
7456       (start code js)
7457         var viz = new $jit.Viz(options);
7458         var label = viz.labels.getLabel('someid');
7459         alert(label.innerHTML);
7460       (end code)
7461
7462     */
7463     getLabel: function(id) {
7464       return (id in this.labels && this.labels[id] != null) ?
7465         this.labels[id] :
7466         this.labels[id] = document.getElementById(id);
7467     },
7468
7469     /*
7470        Method: hideLabels
7471
7472        Hides all labels (by hiding the label container).
7473
7474        Parameters:
7475
7476        hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7477
7478        Example:
7479        (start code js)
7480         var viz = new $jit.Viz(options);
7481         rg.labels.hideLabels(true);
7482        (end code)
7483
7484     */
7485     hideLabels: function (hide) {
7486       var container = this.getLabelContainer();
7487       if(hide)
7488         container.style.display = 'none';
7489       else
7490         container.style.display = '';
7491       this.labelsHidden = hide;
7492     },
7493
7494     /*
7495        Method: clearLabels
7496
7497        Clears the label container.
7498
7499        Useful when using a new visualization with the same canvas element/widget.
7500
7501        Parameters:
7502
7503        force - (boolean) Forces deletion of all labels.
7504
7505        Example:
7506        (start code js)
7507         var viz = new $jit.Viz(options);
7508         viz.labels.clearLabels();
7509         (end code)
7510     */
7511     clearLabels: function(force) {
7512       for(var id in this.labels) {
7513         if (force || !this.viz.graph.hasNode(id)) {
7514           this.disposeLabel(id);
7515           delete this.labels[id];
7516         }
7517       }
7518     },
7519
7520     /*
7521        Method: disposeLabel
7522
7523        Removes a label.
7524
7525        Parameters:
7526
7527        id - (string) A label id (which generally is also a <Graph.Node> id).
7528
7529        Example:
7530        (start code js)
7531         var viz = new $jit.Viz(options);
7532         viz.labels.disposeLabel('labelid');
7533        (end code)
7534     */
7535     disposeLabel: function(id) {
7536       var elem = this.getLabel(id);
7537       if(elem && elem.parentNode) {
7538         elem.parentNode.removeChild(elem);
7539       }
7540     },
7541
7542     /*
7543        Method: hideLabel
7544
7545        Hides the corresponding <Graph.Node> label.
7546
7547        Parameters:
7548
7549        node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7550        show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7551
7552        Example:
7553        (start code js)
7554         var rg = new $jit.Viz(options);
7555         viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7556        (end code)
7557     */
7558     hideLabel: function(node, show) {
7559       node = $.splat(node);
7560       var st = show ? "" : "none", lab, that = this;
7561       $.each(node, function(n) {
7562         var lab = that.getLabel(n.id);
7563         if (lab) {
7564           lab.style.display = st;
7565         }
7566       });
7567     },
7568
7569     /*
7570        fitsInCanvas
7571
7572        Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7573
7574        Parameters:
7575
7576        pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7577        canvas - A <Canvas> instance.
7578
7579        Returns:
7580
7581        A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7582
7583     */
7584     fitsInCanvas: function(pos, canvas) {
7585       var size = canvas.getSize();
7586       if(pos.x >= size.width || pos.x < 0
7587          || pos.y >= size.height || pos.y < 0) return false;
7588        return true;
7589     }
7590 });
7591
7592 /*
7593    Class: Graph.Label.HTML
7594
7595    Implements HTML labels.
7596
7597    Extends:
7598
7599    All <Graph.Label.DOM> methods.
7600
7601 */
7602 Graph.Label.HTML = new Class({
7603     Implements: Graph.Label.DOM,
7604
7605     /*
7606        Method: plotLabel
7607
7608        Plots a label for a given node.
7609
7610        Parameters:
7611
7612        canvas - (object) A <Canvas> instance.
7613        node - (object) A <Graph.Node>.
7614        controller - (object) A configuration object.
7615        
7616       Example:
7617        
7618        (start code js)
7619        var viz = new $jit.Viz(options);
7620        var node = viz.graph.getNode('nodeId');
7621        viz.labels.plotLabel(viz.canvas, node, viz.config);
7622        (end code)
7623
7624
7625     */
7626     plotLabel: function(canvas, node, controller) {
7627       var id = node.id, tag = this.getLabel(id);
7628
7629       if(!tag && !(tag = document.getElementById(id))) {
7630         tag = document.createElement('div');
7631         var container = this.getLabelContainer();
7632         tag.id = id;
7633         tag.className = 'node';
7634         tag.style.position = 'absolute';
7635         controller.onCreateLabel(tag, node);
7636         container.appendChild(tag);
7637         this.labels[node.id] = tag;
7638       }
7639
7640       this.placeLabel(tag, node, controller);
7641     }
7642 });
7643
7644 /*
7645    Class: Graph.Label.SVG
7646
7647    Implements SVG labels.
7648
7649    Extends:
7650
7651    All <Graph.Label.DOM> methods.
7652 */
7653 Graph.Label.SVG = new Class({
7654     Implements: Graph.Label.DOM,
7655
7656     /*
7657        Method: plotLabel
7658
7659        Plots a label for a given node.
7660
7661        Parameters:
7662
7663        canvas - (object) A <Canvas> instance.
7664        node - (object) A <Graph.Node>.
7665        controller - (object) A configuration object.
7666        
7667        Example:
7668        
7669        (start code js)
7670        var viz = new $jit.Viz(options);
7671        var node = viz.graph.getNode('nodeId');
7672        viz.labels.plotLabel(viz.canvas, node, viz.config);
7673        (end code)
7674
7675
7676     */
7677     plotLabel: function(canvas, node, controller) {
7678       var id = node.id, tag = this.getLabel(id);
7679       if(!tag && !(tag = document.getElementById(id))) {
7680         var ns = 'http://www.w3.org/2000/svg';
7681           tag = document.createElementNS(ns, 'svg:text');
7682         var tspan = document.createElementNS(ns, 'svg:tspan');
7683         tag.appendChild(tspan);
7684         var container = this.getLabelContainer();
7685         tag.setAttribute('id', id);
7686         tag.setAttribute('class', 'node');
7687         container.appendChild(tag);
7688         controller.onCreateLabel(tag, node);
7689         this.labels[node.id] = tag;
7690       }
7691       this.placeLabel(tag, node, controller);
7692     }
7693 });
7694
7695
7696
7697 Graph.Geom = new Class({
7698
7699   initialize: function(viz) {
7700     this.viz = viz;
7701     this.config = viz.config;
7702     this.node = viz.config.Node;
7703     this.edge = viz.config.Edge;
7704   },
7705   /*
7706     Applies a translation to the tree.
7707   
7708     Parameters:
7709   
7710     pos - A <Complex> number specifying translation vector.
7711     prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7712   
7713     Example:
7714   
7715     (start code js)
7716       st.geom.translate(new Complex(300, 100), 'end');
7717     (end code)
7718   */  
7719   translate: function(pos, prop) {
7720      prop = $.splat(prop);
7721      this.viz.graph.eachNode(function(elem) {
7722          $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7723      });
7724   },
7725   /*
7726     Hides levels of the tree until it properly fits in canvas.
7727   */  
7728   setRightLevelToShow: function(node, canvas, callback) {
7729      var level = this.getRightLevelToShow(node, canvas), 
7730          fx = this.viz.labels,
7731          opt = $.merge({
7732            execShow:true,
7733            execHide:true,
7734            onHide: $.empty,
7735            onShow: $.empty
7736          }, callback || {});
7737      node.eachLevel(0, this.config.levelsToShow, function(n) {
7738          var d = n._depth - node._depth;
7739          if(d > level) {
7740              opt.onHide(n);
7741              if(opt.execHide) {
7742                n.drawn = false; 
7743                n.exist = false;
7744                fx.hideLabel(n, false);
7745              }
7746          } else {
7747              opt.onShow(n);
7748              if(opt.execShow) {
7749                n.exist = true;
7750              }
7751          }
7752      });
7753      node.drawn= true;
7754   },
7755   /*
7756     Returns the right level to show for the current tree in order to fit in canvas.
7757   */  
7758   getRightLevelToShow: function(node, canvas) {
7759      var config = this.config;
7760      var level = config.levelsToShow;
7761      var constrained = config.constrained;
7762      if(!constrained) return level;
7763      while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7764      return level;
7765   }
7766 });
7767
7768 /*
7769  * File: Loader.js
7770  * 
7771  */
7772
7773 /*
7774    Object: Loader
7775
7776    Provides methods for loading and serving JSON data.
7777 */
7778 var Loader = {
7779      construct: function(json) {
7780         var isGraph = ($.type(json) == 'array');
7781         var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7782         if(!isGraph) 
7783             //make tree
7784             (function (ans, json) {
7785                 ans.addNode(json);
7786                 if(json.children) {
7787                   for(var i=0, ch = json.children; i<ch.length; i++) {
7788                     ans.addAdjacence(json, ch[i]);
7789                     arguments.callee(ans, ch[i]);
7790                   }
7791                 }
7792             })(ans, json);
7793         else
7794             //make graph
7795             (function (ans, json) {
7796                 var getNode = function(id) {
7797                   for(var i=0, l=json.length; i<l; i++) {
7798                     if(json[i].id == id) {
7799                       return json[i];
7800                     }
7801                   }
7802                   // The node was not defined in the JSON
7803                   // Let's create it
7804                   var newNode = {
7805                                 "id" : id,
7806                                 "name" : id
7807                         };
7808                   return ans.addNode(newNode);
7809                 };
7810
7811                 for(var i=0, l=json.length; i<l; i++) {
7812                   ans.addNode(json[i]);
7813                   var adj = json[i].adjacencies;
7814                   if (adj) {
7815                     for(var j=0, lj=adj.length; j<lj; j++) {
7816                       var node = adj[j], data = {};
7817                       if(typeof adj[j] != 'string') {
7818                         data = $.merge(node.data, {});
7819                         node = node.nodeTo;
7820                       }
7821                       ans.addAdjacence(json[i], getNode(node), data);
7822                     }
7823                   }
7824                 }
7825             })(ans, json);
7826
7827         return ans;
7828     },
7829
7830     /*
7831      Method: loadJSON
7832     
7833      Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7834      
7835       A JSON tree or graph structure consists of nodes, each having as properties
7836        
7837        id - (string) A unique identifier for the node
7838        name - (string) A node's name
7839        data - (object) The data optional property contains a hash (i.e {}) 
7840        where you can store all the information you want about this node.
7841         
7842       For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7843       
7844       Example:
7845
7846       (start code js)
7847         var json = {  
7848           "id": "aUniqueIdentifier",  
7849           "name": "usually a nodes name",  
7850           "data": {
7851             "some key": "some value",
7852             "some other key": "some other value"
7853            },  
7854           "children": [ *other nodes or empty* ]  
7855         };  
7856       (end code)
7857         
7858         JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected. 
7859         For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7860         
7861         There are two types of *Graph* structures, *simple* and *extended* graph structures.
7862         
7863         For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the 
7864         id of the node connected to the main node.
7865         
7866         Example:
7867         
7868         (start code js)
7869         var json = [  
7870           {  
7871             "id": "aUniqueIdentifier",  
7872             "name": "usually a nodes name",  
7873             "data": {
7874               "some key": "some value",
7875               "some other key": "some other value"
7876              },  
7877             "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']  
7878           },
7879
7880           'other nodes go here...' 
7881         ];          
7882         (end code)
7883         
7884         For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7885         
7886         nodeTo - (string) The other node connected by this adjacency.
7887         data - (object) A data property, where we can store custom key/value information.
7888         
7889         Example:
7890         
7891         (start code js)
7892         var json = [  
7893           {  
7894             "id": "aUniqueIdentifier",  
7895             "name": "usually a nodes name",  
7896             "data": {
7897               "some key": "some value",
7898               "some other key": "some other value"
7899              },  
7900             "adjacencies": [  
7901             {  
7902               nodeTo:"aNodeId",  
7903               data: {} //put whatever you want here  
7904             },
7905             'other adjacencies go here...'  
7906           },
7907
7908           'other nodes go here...' 
7909         ];          
7910         (end code)
7911        
7912        About the data property:
7913        
7914        As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*. 
7915        You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and 
7916        have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7917        
7918        For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in 
7919        <Options.Node> will override the general value for that option with that particular value. For this to work 
7920        however, you do have to set *overridable = true* in <Options.Node>.
7921        
7922        The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge> 
7923        if <Options.Edge> has *overridable = true*.
7924        
7925        When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key, 
7926        since this is the value which will be taken into account when creating the layout. 
7927        The same thing goes for the *$color* parameter.
7928        
7929        In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example, 
7930        *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set 
7931        canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer 
7932        to the *shadowBlur* property.
7933        
7934        These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences> 
7935        by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7936        
7937        Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more 
7938        information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7939        
7940        loadJSON Parameters:
7941     
7942         json - A JSON Tree or Graph structure.
7943         i - For Graph structures only. Sets the indexed node as root for the visualization.
7944
7945     */
7946     loadJSON: function(json, i) {
7947       this.json = json;
7948       //if they're canvas labels erase them.
7949       if(this.labels && this.labels.clearLabels) {
7950         this.labels.clearLabels(true);
7951       }
7952       this.graph = this.construct(json);
7953       if($.type(json) != 'array'){
7954         this.root = json.id;
7955       } else {
7956         this.root = json[i? i : 0].id;
7957       }
7958     },
7959     
7960     /*
7961       Method: toJSON
7962    
7963       Returns a JSON tree/graph structure from the visualization's <Graph>. 
7964       See <Loader.loadJSON> for the graph formats available.
7965       
7966       See also:
7967       
7968       <Loader.loadJSON>
7969       
7970       Parameters:
7971       
7972       type - (string) Default's "tree". The type of the JSON structure to be returned. 
7973       Possible options are "tree" or "graph".
7974     */    
7975     toJSON: function(type) {
7976       type = type || "tree";
7977       if(type == 'tree') {
7978         var ans = {};
7979         var rootNode = this.graph.getNode(this.root);
7980         var ans = (function recTree(node) {
7981           var ans = {};
7982           ans.id = node.id;
7983           ans.name = node.name;
7984           ans.data = node.data;
7985           var ch =[];
7986           node.eachSubnode(function(n) {
7987             ch.push(recTree(n));
7988           });
7989           ans.children = ch;
7990           return ans;
7991         })(rootNode);
7992         return ans;
7993       } else {
7994         var ans = [];
7995         var T = !!this.graph.getNode(this.root).visited;
7996         this.graph.eachNode(function(node) {
7997           var ansNode = {};
7998           ansNode.id = node.id;
7999           ansNode.name = node.name;
8000           ansNode.data = node.data;
8001           var adjs = [];
8002           node.eachAdjacency(function(adj) {
8003             var nodeTo = adj.nodeTo;
8004             if(!!nodeTo.visited === T) {
8005               var ansAdj = {};
8006               ansAdj.nodeTo = nodeTo.id;
8007               ansAdj.data = adj.data;
8008               adjs.push(ansAdj);
8009             }
8010           });
8011           ansNode.adjacencies = adjs;
8012           ans.push(ansNode);
8013           node.visited = !T;
8014         });
8015         return ans;
8016       }
8017     }
8018 };
8019
8020
8021
8022 /*
8023  * File: Layouts.js
8024  * 
8025  * Implements base Tree and Graph layouts.
8026  *
8027  * Description:
8028  *
8029  * Implements base Tree and Graph layouts like Radial, Tree, etc.
8030  * 
8031  */
8032
8033 /*
8034  * Object: Layouts
8035  * 
8036  * Parent object for common layouts.
8037  *
8038  */
8039 var Layouts = $jit.Layouts = {};
8040
8041
8042 //Some util shared layout functions are defined here.
8043 var NodeDim = {
8044   label: null,
8045   
8046   compute: function(graph, prop, opt) {
8047     this.initializeLabel(opt);
8048     var label = this.label, style = label.style;
8049     graph.eachNode(function(n) {
8050       var autoWidth  = n.getData('autoWidth'),
8051           autoHeight = n.getData('autoHeight');
8052       if(autoWidth || autoHeight) {
8053         //delete dimensions since these are
8054         //going to be overridden now.
8055         delete n.data.$width;
8056         delete n.data.$height;
8057         delete n.data.$dim;
8058         
8059         var width  = n.getData('width'),
8060             height = n.getData('height');
8061         //reset label dimensions
8062         style.width  = autoWidth? 'auto' : width + 'px';
8063         style.height = autoHeight? 'auto' : height + 'px';
8064         
8065         //TODO(nico) should let the user choose what to insert here.
8066         label.innerHTML = n.name;
8067         
8068         var offsetWidth  = label.offsetWidth,
8069             offsetHeight = label.offsetHeight;
8070         var type = n.getData('type');
8071         if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8072           n.setData('width', offsetWidth);
8073           n.setData('height', offsetHeight);
8074         } else {
8075           var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8076           n.setData('width', dim);
8077           n.setData('height', dim);
8078           n.setData('dim', dim); 
8079         }
8080       }
8081     });
8082   },
8083   
8084   initializeLabel: function(opt) {
8085     if(!this.label) {
8086       this.label = document.createElement('div');
8087       document.body.appendChild(this.label);
8088     }
8089     this.setLabelStyles(opt);
8090   },
8091   
8092   setLabelStyles: function(opt) {
8093     $.extend(this.label.style, {
8094       'visibility': 'hidden',
8095       'position': 'absolute',
8096       'width': 'auto',
8097       'height': 'auto'
8098     });
8099     this.label.className = 'jit-autoadjust-label';
8100   }
8101 };
8102
8103
8104 /*
8105  * Class: Layouts.Tree
8106  * 
8107  * Implements a Tree Layout.
8108  * 
8109  * Implemented By:
8110  * 
8111  * <ST>
8112  * 
8113  * Inspired by:
8114  * 
8115  * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8116  * 
8117  */
8118 Layouts.Tree = (function() {
8119   //Layout functions
8120   var slice = Array.prototype.slice;
8121
8122   /*
8123      Calculates the max width and height nodes for a tree level
8124   */  
8125   function getBoundaries(graph, config, level, orn, prop) {
8126     var dim = config.Node;
8127     var multitree = config.multitree;
8128     if (dim.overridable) {
8129       var w = -1, h = -1;
8130       graph.eachNode(function(n) {
8131         if (n._depth == level
8132             && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8133           var dw = n.getData('width', prop);
8134           var dh = n.getData('height', prop);
8135           w = (w < dw) ? dw : w;
8136           h = (h < dh) ? dh : h;
8137         }
8138       });
8139       return {
8140         'width' : w < 0 ? dim.width : w,
8141         'height' : h < 0 ? dim.height : h
8142       };
8143     } else {
8144       return dim;
8145     }
8146   }
8147
8148
8149   function movetree(node, prop, val, orn) {
8150     var p = (orn == "left" || orn == "right") ? "y" : "x";
8151     node.getPos(prop)[p] += val;
8152   }
8153
8154
8155   function moveextent(extent, val) {
8156     var ans = [];
8157     $.each(extent, function(elem) {
8158       elem = slice.call(elem);
8159       elem[0] += val;
8160       elem[1] += val;
8161       ans.push(elem);
8162     });
8163     return ans;
8164   }
8165
8166
8167   function merge(ps, qs) {
8168     if (ps.length == 0)
8169       return qs;
8170     if (qs.length == 0)
8171       return ps;
8172     var p = ps.shift(), q = qs.shift();
8173     return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8174   }
8175
8176
8177   function mergelist(ls, def) {
8178     def = def || [];
8179     if (ls.length == 0)
8180       return def;
8181     var ps = ls.pop();
8182     return mergelist(ls, merge(ps, def));
8183   }
8184
8185
8186   function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8187     if (ext1.length <= i || ext2.length <= i)
8188       return 0;
8189
8190     var p = ext1[i][1], q = ext2[i][0];
8191     return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8192         + subtreeOffset, p - q + siblingOffset);
8193   }
8194
8195
8196   function fitlistl(es, subtreeOffset, siblingOffset) {
8197     function $fitlistl(acc, es, i) {
8198       if (es.length <= i)
8199         return [];
8200       var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8201       return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8202     }
8203     ;
8204     return $fitlistl( [], es, 0);
8205   }
8206
8207
8208   function fitlistr(es, subtreeOffset, siblingOffset) {
8209     function $fitlistr(acc, es, i) {
8210       if (es.length <= i)
8211         return [];
8212       var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8213       return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8214     }
8215     ;
8216     es = slice.call(es);
8217     var ans = $fitlistr( [], es.reverse(), 0);
8218     return ans.reverse();
8219   }
8220
8221
8222   function fitlist(es, subtreeOffset, siblingOffset, align) {
8223     var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8224         subtreeOffset, siblingOffset);
8225
8226     if (align == "left")
8227       esr = esl;
8228     else if (align == "right")
8229       esl = esr;
8230
8231     for ( var i = 0, ans = []; i < esl.length; i++) {
8232       ans[i] = (esl[i] + esr[i]) / 2;
8233     }
8234     return ans;
8235   }
8236
8237
8238   function design(graph, node, prop, config, orn) {
8239     var multitree = config.multitree;
8240     var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8241     var ind = +(orn == "left" || orn == "right");
8242     var p = auxp[ind], notp = auxp[1 - ind];
8243
8244     var cnode = config.Node;
8245     var s = auxs[ind], nots = auxs[1 - ind];
8246
8247     var siblingOffset = config.siblingOffset;
8248     var subtreeOffset = config.subtreeOffset;
8249     var align = config.align;
8250
8251     function $design(node, maxsize, acum) {
8252       var sval = node.getData(s, prop);
8253       var notsval = maxsize
8254           || (node.getData(nots, prop));
8255
8256       var trees = [], extents = [], chmaxsize = false;
8257       var chacum = notsval + config.levelDistance;
8258       node.eachSubnode(function(n) {
8259             if (n.exist
8260                 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8261
8262               if (!chmaxsize)
8263                 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8264
8265               var s = $design(n, chmaxsize[nots], acum + chacum);
8266               trees.push(s.tree);
8267               extents.push(s.extent);
8268             }
8269           });
8270       var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8271       for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8272         movetree(trees[i], prop, positions[i], orn);
8273         pextents.push(moveextent(extents[i], positions[i]));
8274       }
8275       var resultextent = [ [ -sval / 2, sval / 2 ] ]
8276           .concat(mergelist(pextents));
8277       node.getPos(prop)[p] = 0;
8278
8279       if (orn == "top" || orn == "left") {
8280         node.getPos(prop)[notp] = acum;
8281       } else {
8282         node.getPos(prop)[notp] = -acum;
8283       }
8284
8285       return {
8286         tree : node,
8287         extent : resultextent
8288       };
8289     }
8290
8291     $design(node, false, 0);
8292   }
8293
8294
8295   return new Class({
8296     /*
8297     Method: compute
8298     
8299     Computes nodes' positions.
8300
8301      */
8302     compute : function(property, computeLevels) {
8303       var prop = property || 'start';
8304       var node = this.graph.getNode(this.root);
8305       $.extend(node, {
8306         'drawn' : true,
8307         'exist' : true,
8308         'selected' : true
8309       });
8310       NodeDim.compute(this.graph, prop, this.config);
8311       if (!!computeLevels || !("_depth" in node)) {
8312         this.graph.computeLevels(this.root, 0, "ignore");
8313       }
8314       
8315       this.computePositions(node, prop);
8316     },
8317
8318     computePositions : function(node, prop) {
8319       var config = this.config;
8320       var multitree = config.multitree;
8321       var align = config.align;
8322       var indent = align !== 'center' && config.indent;
8323       var orn = config.orientation;
8324       var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8325       var that = this;
8326       $.each(orns, function(orn) {
8327         //calculate layout
8328           design(that.graph, node, prop, that.config, orn, prop);
8329           var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8330           //absolutize
8331           (function red(node) {
8332             node.eachSubnode(function(n) {
8333               if (n.exist
8334                   && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8335
8336                 n.getPos(prop)[i] += node.getPos(prop)[i];
8337                 if (indent) {
8338                   n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8339                 }
8340                 red(n);
8341               }
8342             });
8343           })(node);
8344         });
8345     }
8346   });
8347   
8348 })();
8349
8350 /*
8351  * File: Spacetree.js
8352  */
8353
8354 /*
8355    Class: ST
8356    
8357   A Tree layout with advanced contraction and expansion animations.
8358      
8359   Inspired by:
8360  
8361   SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson) 
8362   <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8363   
8364   Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8365   
8366   Note:
8367  
8368   This visualization was built and engineered from scratch, taking only the papers as inspiration, and only shares some features with the visualization described in those papers.
8369  
8370   Implements:
8371   
8372   All <Loader> methods
8373   
8374   Constructor Options:
8375   
8376   Inherits options from
8377   
8378   - <Options.Canvas>
8379   - <Options.Controller>
8380   - <Options.Tree>
8381   - <Options.Node>
8382   - <Options.Edge>
8383   - <Options.Label>
8384   - <Options.Events>
8385   - <Options.Tips>
8386   - <Options.NodeStyles>
8387   - <Options.Navigation>
8388   
8389   Additionally, there are other parameters and some default values changed
8390   
8391   constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8392   levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8393   levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8394   Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8395   offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8396   offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8397   duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8398   
8399   Instance Properties:
8400   
8401   canvas - Access a <Canvas> instance.
8402   graph - Access a <Graph> instance.
8403   op - Access a <ST.Op> instance.
8404   fx - Access a <ST.Plot> instance.
8405   labels - Access a <ST.Label> interface implementation.
8406
8407  */
8408
8409 $jit.ST= (function() {
8410     // Define some private methods first...
8411     // Nodes in path
8412     var nodesInPath = [];
8413     // Nodes to contract
8414     function getNodesToHide(node) {
8415       node = node || this.clickedNode;
8416       if(!this.config.constrained) {
8417         return [];
8418       }
8419       var Geom = this.geom;
8420       var graph = this.graph;
8421       var canvas = this.canvas;
8422       var level = node._depth, nodeArray = [];
8423           graph.eachNode(function(n) {
8424           if(n.exist && !n.selected) {
8425               if(n.isDescendantOf(node.id)) {
8426                 if(n._depth <= level) nodeArray.push(n);
8427               } else {
8428                 nodeArray.push(n);
8429               }
8430           }
8431           });
8432           var leafLevel = Geom.getRightLevelToShow(node, canvas);
8433           node.eachLevel(leafLevel, leafLevel, function(n) {
8434           if(n.exist && !n.selected) nodeArray.push(n);
8435           });
8436             
8437           for (var i = 0; i < nodesInPath.length; i++) {
8438             var n = this.graph.getNode(nodesInPath[i]);
8439             if(!n.isDescendantOf(node.id)) {
8440               nodeArray.push(n);
8441             }
8442           } 
8443           return nodeArray;       
8444     };
8445     // Nodes to expand
8446      function getNodesToShow(node) {
8447         var nodeArray = [], config = this.config;
8448         node = node || this.clickedNode;
8449         this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8450             if(config.multitree && !('$orn' in n.data) 
8451                         && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8452                 nodeArray.push(n);
8453             } else if(n.drawn && !n.anySubnode("drawn")) {
8454               nodeArray.push(n);
8455             }
8456         });
8457         return nodeArray;
8458      };
8459     // Now define the actual class.
8460     return new Class({
8461     
8462         Implements: [Loader, Extras, Layouts.Tree],
8463         
8464         initialize: function(controller) {            
8465           var $ST = $jit.ST;
8466           
8467           var config= {
8468                 levelsToShow: 2,
8469                 levelDistance: 30,
8470                 constrained: true,                
8471                 Node: {
8472                   type: 'rectangle'
8473                 },
8474                 duration: 700,
8475                 offsetX: 0,
8476                 offsetY: 0
8477             };
8478             
8479             this.controller = this.config = $.merge(
8480                 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller", 
8481                     "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8482
8483             var canvasConfig = this.config;
8484             if(canvasConfig.useCanvas) {
8485               this.canvas = canvasConfig.useCanvas;
8486               this.config.labelContainer = this.canvas.id + '-label';
8487             } else {
8488               if(canvasConfig.background) {
8489                 canvasConfig.background = $.merge({
8490                   type: 'Fade',
8491                   colorStop1: this.config.colorStop1,
8492                   colorStop2: this.config.colorStop2
8493                 }, canvasConfig.background);
8494               }
8495               this.canvas = new Canvas(this, canvasConfig);
8496               this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8497             }
8498
8499             this.graphOptions = {
8500                 'complex': true
8501             };
8502             this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8503             this.labels = new $ST.Label[canvasConfig.Label.type](this);
8504             this.fx = new $ST.Plot(this, $ST);
8505             this.op = new $ST.Op(this);
8506             this.group = new $ST.Group(this);
8507             this.geom = new $ST.Geom(this);
8508             this.clickedNode=  null;
8509             // initialize extras
8510             this.initializeExtras();
8511         },
8512     
8513         /*
8514          Method: plot
8515         
8516          Plots the <ST>. This is a shortcut to *fx.plot*.
8517
8518         */  
8519         plot: function() { this.fx.plot(this.controller); },
8520     
8521       
8522         /*
8523          Method: switchPosition
8524         
8525          Switches the tree orientation.
8526
8527          Parameters:
8528
8529         pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8530         method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
8531         onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8532
8533          Example:
8534
8535          (start code js)
8536            st.switchPosition("right", "animate", {
8537             onComplete: function() {
8538               alert('completed!');
8539             } 
8540            });
8541          (end code)
8542         */  
8543         switchPosition: function(pos, method, onComplete) {
8544           var Geom = this.geom, Plot = this.fx, that = this;
8545           if(!Plot.busy) {
8546               Plot.busy = true;
8547               this.contract({
8548                   onComplete: function() {
8549                       Geom.switchOrientation(pos);
8550                       that.compute('end', false);
8551                       Plot.busy = false;
8552                       if(method == 'animate') {
8553                           that.onClick(that.clickedNode.id, onComplete);  
8554                       } else if(method == 'replot') {
8555                           that.select(that.clickedNode.id, onComplete);
8556                       }
8557                   }
8558               }, pos);
8559           }
8560         },
8561
8562         /*
8563         Method: switchAlignment
8564        
8565         Switches the tree alignment.
8566
8567         Parameters:
8568
8569        align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8570        method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
8571        onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8572
8573         Example:
8574
8575         (start code js)
8576           st.switchAlignment("right", "animate", {
8577            onComplete: function() {
8578              alert('completed!');
8579            } 
8580           });
8581         (end code)
8582        */  
8583        switchAlignment: function(align, method, onComplete) {
8584         this.config.align = align;
8585         if(method == 'animate') {
8586                 this.select(this.clickedNode.id, onComplete);
8587         } else if(method == 'replot') {
8588                 this.onClick(this.clickedNode.id, onComplete);  
8589         }
8590        },
8591
8592        /*
8593         Method: addNodeInPath
8594        
8595         Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8596         
8597
8598         Parameters:
8599
8600        id - (string) A <Graph.Node> id.
8601
8602         Example:
8603
8604         (start code js)
8605           st.addNodeInPath("nodeId");
8606         (end code)
8607        */  
8608        addNodeInPath: function(id) {
8609            nodesInPath.push(id);
8610            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8611        },       
8612
8613        /*
8614        Method: clearNodesInPath
8615       
8616        Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8617        
8618        See also:
8619        
8620        <ST.addNodeInPath>
8621      
8622        Example:
8623
8624        (start code js)
8625          st.clearNodesInPath();
8626        (end code)
8627       */  
8628        clearNodesInPath: function(id) {
8629            nodesInPath.length = 0;
8630            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8631        },
8632         
8633        /*
8634          Method: refresh
8635         
8636          Computes positions and plots the tree.
8637          
8638        */
8639        refresh: function() {
8640            this.reposition();
8641            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8642        },    
8643
8644        reposition: function() {
8645             this.graph.computeLevels(this.root, 0, "ignore");
8646              this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8647             this.graph.eachNode(function(n) {
8648                 if(n.exist) n.drawn = true;
8649             });
8650             this.compute('end');
8651         },
8652         
8653         requestNodes: function(node, onComplete) {
8654           var handler = $.merge(this.controller, onComplete), 
8655           lev = this.config.levelsToShow;
8656           if(handler.request) {
8657               var leaves = [], d = node._depth;
8658               node.eachLevel(0, lev, function(n) {
8659                   if(n.drawn && 
8660                    !n.anySubnode()) {
8661                    leaves.push(n);
8662                    n._level = lev - (n._depth - d);
8663                   }
8664               });
8665               this.group.requestNodes(leaves, handler);
8666           }
8667             else
8668               handler.onComplete();
8669         },
8670      
8671         contract: function(onComplete, switched) {
8672           var orn  = this.config.orientation;
8673           var Geom = this.geom, Group = this.group;
8674           if(switched) Geom.switchOrientation(switched);
8675           var nodes = getNodesToHide.call(this);
8676           if(switched) Geom.switchOrientation(orn);
8677           Group.contract(nodes, $.merge(this.controller, onComplete));
8678         },
8679       
8680          move: function(node, onComplete) {
8681             this.compute('end', false);
8682             var move = onComplete.Move, offset = {
8683                 'x': move.offsetX,
8684                 'y': move.offsetY 
8685             };
8686             if(move.enable) {
8687                 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8688             }
8689             this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8690          },
8691       
8692         expand: function (node, onComplete) {
8693             var nodeArray = getNodesToShow.call(this, node);
8694             this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8695         },
8696     
8697         selectPath: function(node) {
8698           var that = this;
8699           this.graph.eachNode(function(n) { n.selected = false; }); 
8700           function path(node) {
8701               if(node == null || node.selected) return;
8702               node.selected = true;
8703               $.each(that.group.getSiblings([node])[node.id], 
8704               function(n) { 
8705                    n.exist = true; 
8706                    n.drawn = true; 
8707               });    
8708               var parents = node.getParents();
8709               parents = (parents.length > 0)? parents[0] : null;
8710               path(parents);
8711           };
8712           for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8713               path(this.graph.getNode(ns[i]));
8714           }
8715         },
8716       
8717         /*
8718         Method: setRoot
8719      
8720          Switches the current root node. Changes the topology of the Tree.
8721      
8722         Parameters:
8723            id - (string) The id of the node to be set as root.
8724            method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8725            onComplete - (optional|object) An action to perform after the animation (if any).
8726  
8727         Example:
8728
8729         (start code js)
8730           st.setRoot('nodeId', 'animate', {
8731              onComplete: function() {
8732                alert('complete!');
8733              }
8734           });
8735         (end code)
8736      */
8737      setRoot: function(id, method, onComplete) {
8738                 if(this.busy) return;
8739                 this.busy = true;
8740           var that = this, canvas = this.canvas;
8741                 var rootNode = this.graph.getNode(this.root);
8742                 var clickedNode = this.graph.getNode(id);
8743                 function $setRoot() {
8744                 if(this.config.multitree && clickedNode.data.$orn) {
8745                         var orn = clickedNode.data.$orn;
8746                         var opp = {
8747                                         'left': 'right',
8748                                         'right': 'left',
8749                                         'top': 'bottom',
8750                                         'bottom': 'top'
8751                         }[orn];
8752                         rootNode.data.$orn = opp;
8753                         (function tag(rootNode) {
8754                                 rootNode.eachSubnode(function(n) {
8755                                         if(n.id != id) {
8756                                                 n.data.$orn = opp;
8757                                                 tag(n);
8758                                         }
8759                                 });
8760                         })(rootNode);
8761                         delete clickedNode.data.$orn;
8762                 }
8763                 this.root = id;
8764                 this.clickedNode = clickedNode;
8765                 this.graph.computeLevels(this.root, 0, "ignore");
8766                 this.geom.setRightLevelToShow(clickedNode, canvas, {
8767                   execHide: false,
8768                   onShow: function(node) {
8769                     if(!node.drawn) {
8770                     node.drawn = true;
8771                     node.setData('alpha', 1, 'end');
8772                     node.setData('alpha', 0);
8773                     node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8774                     }
8775                   }
8776                 });
8777               this.compute('end');
8778               this.busy = true;
8779               this.fx.animate({
8780                 modes: ['linear', 'node-property:alpha'],
8781                 onComplete: function() {
8782                   that.busy = false;
8783                   that.onClick(id, {
8784                     onComplete: function() {
8785                       onComplete && onComplete.onComplete();
8786                     }
8787                   });
8788                 }
8789               });
8790                 }
8791
8792                 // delete previous orientations (if any)
8793                 delete rootNode.data.$orns;
8794
8795                 if(method == 'animate') {
8796                   $setRoot.call(this);
8797                   that.selectPath(clickedNode);
8798                 } else if(method == 'replot') {
8799                         $setRoot.call(this);
8800                         this.select(this.root);
8801                 }
8802      },
8803
8804      /*
8805            Method: addSubtree
8806         
8807             Adds a subtree.
8808         
8809            Parameters:
8810               subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8811               method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8812               onComplete - (optional|object) An action to perform after the animation (if any).
8813     
8814            Example:
8815
8816            (start code js)
8817              st.addSubtree(json, 'animate', {
8818                 onComplete: function() {
8819                   alert('complete!');
8820                 }
8821              });
8822            (end code)
8823         */
8824         addSubtree: function(subtree, method, onComplete) {
8825             if(method == 'replot') {
8826                 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8827             } else if (method == 'animate') {
8828                 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8829             }
8830         },
8831     
8832         /*
8833            Method: removeSubtree
8834         
8835             Removes a subtree.
8836         
8837            Parameters:
8838               id - (string) The _id_ of the subtree to be removed.
8839               removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8840               method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
8841               onComplete - (optional|object) An action to perform after the animation (if any).
8842
8843           Example:
8844
8845           (start code js)
8846             st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8847               onComplete: function() {
8848                 alert('complete!');
8849               }
8850             });
8851           (end code)
8852     
8853         */
8854         removeSubtree: function(id, removeRoot, method, onComplete) {
8855             var node = this.graph.getNode(id), subids = [];
8856             node.eachLevel(+!removeRoot, false, function(n) {
8857                 subids.push(n.id);
8858             });
8859             if(method == 'replot') {
8860                 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8861             } else if (method == 'animate') {
8862                 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8863             }
8864         },
8865     
8866         /*
8867            Method: select
8868         
8869             Selects a node in the <ST> without performing an animation. Useful when selecting 
8870             nodes which are currently hidden or deep inside the tree.
8871
8872           Parameters:
8873             id - (string) The id of the node to select.
8874             onComplete - (optional|object) an onComplete callback.
8875
8876           Example:
8877           (start code js)
8878             st.select('mynodeid', {
8879               onComplete: function() {
8880                 alert('complete!');
8881               }
8882             });
8883           (end code)
8884         */
8885         select: function(id, onComplete) {
8886             var group = this.group, geom = this.geom;
8887             var node=  this.graph.getNode(id), canvas = this.canvas;
8888             var root  = this.graph.getNode(this.root);
8889             var complete = $.merge(this.controller, onComplete);
8890             var that = this;
8891     
8892             complete.onBeforeCompute(node);
8893             this.selectPath(node);
8894             this.clickedNode= node;
8895             this.requestNodes(node, {
8896                 onComplete: function(){
8897                     group.hide(group.prepare(getNodesToHide.call(that)), complete);
8898                     geom.setRightLevelToShow(node, canvas);
8899                     that.compute("current");
8900                     that.graph.eachNode(function(n) { 
8901                         var pos = n.pos.getc(true);
8902                         n.startPos.setc(pos.x, pos.y);
8903                         n.endPos.setc(pos.x, pos.y);
8904                         n.visited = false; 
8905                     });
8906                     var offset = { x: complete.offsetX, y: complete.offsetY };
8907                     that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8908                     group.show(getNodesToShow.call(that));              
8909                     that.plot();
8910                     complete.onAfterCompute(that.clickedNode);
8911                     complete.onComplete();
8912                 }
8913             });     
8914         },
8915     
8916       /*
8917          Method: onClick
8918     
8919         Animates the <ST> to center the node specified by *id*.
8920             
8921         Parameters:
8922         
8923         id - (string) A node id.
8924         options - (optional|object) A group of options and callbacks described below.
8925         onComplete - (object) An object callback called when the animation finishes.
8926         Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8927
8928         Example:
8929
8930         (start code js)
8931           st.onClick('mynodeid', {
8932                   Move: {
8933                         enable: true,
8934                     offsetX: 30,
8935                     offsetY: 5
8936                   },
8937                   onComplete: function() {
8938                       alert('yay!');
8939                   }
8940           });
8941         (end code)
8942     
8943         */    
8944       onClick: function (id, options) {
8945         var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8946         var innerController = {
8947             Move: {
8948                     enable: true,
8949               offsetX: config.offsetX || 0,
8950               offsetY: config.offsetY || 0  
8951             },
8952             setRightLevelToShowConfig: false,
8953             onBeforeRequest: $.empty,
8954             onBeforeContract: $.empty,
8955             onBeforeMove: $.empty,
8956             onBeforeExpand: $.empty
8957         };
8958         var complete = $.merge(this.controller, innerController, options);
8959         
8960         if(!this.busy) {
8961             this.busy = true;
8962             var node = this.graph.getNode(id);
8963             this.selectPath(node, this.clickedNode);
8964                 this.clickedNode = node;
8965             complete.onBeforeCompute(node);
8966             complete.onBeforeRequest(node);
8967             this.requestNodes(node, {
8968                 onComplete: function() {
8969                     complete.onBeforeContract(node);
8970                     that.contract({
8971                         onComplete: function() {
8972                             Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8973                             complete.onBeforeMove(node);
8974                             that.move(node, {
8975                                 Move: complete.Move,
8976                                 onComplete: function() {
8977                                     complete.onBeforeExpand(node);
8978                                     that.expand(node, {
8979                                         onComplete: function() {
8980                                             that.busy = false;
8981                                             complete.onAfterCompute(id);
8982                                             complete.onComplete();
8983                                         }
8984                                     }); // expand
8985                                 }
8986                             }); // move
8987                         }
8988                     });// contract
8989                 }
8990             });// request
8991         }
8992       }
8993     });
8994
8995 })();
8996
8997 $jit.ST.$extend = true;
8998
8999 /*
9000    Class: ST.Op
9001     
9002    Custom extension of <Graph.Op>.
9003
9004    Extends:
9005
9006    All <Graph.Op> methods
9007    
9008    See also:
9009    
9010    <Graph.Op>
9011
9012 */
9013 $jit.ST.Op = new Class({
9014
9015   Implements: Graph.Op
9016     
9017 });
9018
9019 /*
9020     
9021      Performs operations on group of nodes.
9022
9023 */
9024 $jit.ST.Group = new Class({
9025     
9026     initialize: function(viz) {
9027         this.viz = viz;
9028         this.canvas = viz.canvas;
9029         this.config = viz.config;
9030         this.animation = new Animation;
9031         this.nodes = null;
9032     },
9033     
9034     /*
9035     
9036        Calls the request method on the controller to request a subtree for each node. 
9037     */
9038     requestNodes: function(nodes, controller) {
9039         var counter = 0, len = nodes.length, nodeSelected = {};
9040         var complete = function() { controller.onComplete(); };
9041         var viz = this.viz;
9042         if(len == 0) complete();
9043         for(var i=0; i<len; i++) {
9044             nodeSelected[nodes[i].id] = nodes[i];
9045             controller.request(nodes[i].id, nodes[i]._level, {
9046                 onComplete: function(nodeId, data) {
9047                     if(data && data.children) {
9048                         data.id = nodeId;
9049                         viz.op.sum(data, { type: 'nothing' });
9050                     }
9051                     if(++counter == len) {
9052                         viz.graph.computeLevels(viz.root, 0);
9053                         complete();
9054                     }
9055                 }
9056             });
9057         }
9058     },
9059     
9060     /*
9061     
9062        Collapses group of nodes. 
9063     */
9064     contract: function(nodes, controller) {
9065         var viz = this.viz;
9066         var that = this;
9067
9068         nodes = this.prepare(nodes);
9069         this.animation.setOptions($.merge(controller, {
9070             $animating: false,
9071             compute: function(delta) {
9072               if(delta == 1) delta = 0.99;
9073               that.plotStep(1 - delta, controller, this.$animating);
9074               this.$animating = 'contract';
9075             },
9076             
9077             complete: function() {
9078                 that.hide(nodes, controller);
9079             }       
9080         })).start();
9081     },
9082     
9083     hide: function(nodes, controller) {
9084         var viz = this.viz;
9085         for(var i=0; i<nodes.length; i++) {
9086             // TODO nodes are requested on demand, but not
9087             // deleted when hidden. Would that be a good feature?
9088             // Currently that feature is buggy, so I'll turn it off
9089             // Actually this feature is buggy because trimming should take
9090             // place onAfterCompute and not right after collapsing nodes.
9091             if (true || !controller || !controller.request) {
9092                 nodes[i].eachLevel(1, false, function(elem){
9093                     if (elem.exist) {
9094                         $.extend(elem, {
9095                             'drawn': false,
9096                             'exist': false
9097                         });
9098                     }
9099                 });
9100             } else {
9101                 var ids = [];
9102                 nodes[i].eachLevel(1, false, function(n) {
9103                     ids.push(n.id);
9104                 });
9105                 viz.op.removeNode(ids, { 'type': 'nothing' });
9106                 viz.labels.clearLabels();
9107             }
9108         }
9109         controller.onComplete();
9110     },    
9111     
9112
9113     /*
9114        Expands group of nodes. 
9115     */
9116     expand: function(nodes, controller) {
9117         var that = this;
9118         this.show(nodes);
9119         this.animation.setOptions($.merge(controller, {
9120             $animating: false,
9121             compute: function(delta) {
9122                 that.plotStep(delta, controller, this.$animating);
9123                 this.$animating = 'expand';
9124             },
9125             
9126             complete: function() {
9127                 that.plotStep(undefined, controller, false);
9128                 controller.onComplete();
9129             }       
9130         })).start();
9131         
9132     },
9133     
9134     show: function(nodes) {
9135         var config = this.config;
9136         this.prepare(nodes);
9137         $.each(nodes, function(n) {
9138                 // check for root nodes if multitree
9139                 if(config.multitree && !('$orn' in n.data)) {
9140                         delete n.data.$orns;
9141                         var orns = ' ';
9142                         n.eachSubnode(function(ch) {
9143                                 if(('$orn' in ch.data) 
9144                                                 && orns.indexOf(ch.data.$orn) < 0 
9145                                                 && ch.exist && !ch.drawn) {
9146                                         orns += ch.data.$orn + ' ';
9147                                 }
9148                         });
9149                         n.data.$orns = orns;
9150                 }
9151             n.eachLevel(0, config.levelsToShow, function(n) {
9152                 if(n.exist) n.drawn = true;
9153             });     
9154         });
9155     },
9156     
9157     prepare: function(nodes) {
9158         this.nodes = this.getNodesWithChildren(nodes);
9159         return this.nodes;
9160     },
9161     
9162     /*
9163        Filters an array of nodes leaving only nodes with children.
9164     */
9165     getNodesWithChildren: function(nodes) {
9166         var ans = [], config = this.config, root = this.viz.root;
9167         nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9168         for(var i=0; i<nodes.length; i++) {
9169             if(nodes[i].anySubnode("exist")) {
9170                 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9171                     if(!config.multitree || '$orn' in nodes[j].data) {
9172                                 desc = desc || nodes[i].isDescendantOf(nodes[j].id);                            
9173                     }
9174                 }
9175                 if(!desc) ans.push(nodes[i]);
9176             }
9177         }
9178         return ans;
9179     },
9180     
9181     plotStep: function(delta, controller, animating) {
9182         var viz = this.viz,
9183         config = this.config,
9184         canvas = viz.canvas, 
9185         ctx = canvas.getCtx(),
9186         nodes = this.nodes;
9187         var i, node;
9188         // hide nodes that are meant to be collapsed/expanded
9189         var nds = {};
9190         for(i=0; i<nodes.length; i++) {
9191           node = nodes[i];
9192           nds[node.id] = [];
9193           var root = config.multitree && !('$orn' in node.data);
9194           var orns = root && node.data.$orns;
9195           node.eachSubgraph(function(n) { 
9196             // TODO(nico): Cleanup
9197                   // special check for root node subnodes when
9198                   // multitree is checked.
9199                   if(root && orns && orns.indexOf(n.data.$orn) > 0 
9200                                   && n.drawn) {
9201                           n.drawn = false;
9202                   nds[node.id].push(n);
9203               } else if((!root || !orns) && n.drawn) {
9204                 n.drawn = false;
9205                 nds[node.id].push(n);
9206               }
9207             }); 
9208             node.drawn = true;
9209         }
9210         // plot the whole (non-scaled) tree
9211         if(nodes.length > 0) viz.fx.plot();
9212         // show nodes that were previously hidden
9213         for(i in nds) {
9214           $.each(nds[i], function(n) { n.drawn = true; });
9215         }
9216         // plot each scaled subtree
9217         for(i=0; i<nodes.length; i++) {
9218           node = nodes[i];
9219           ctx.save();
9220           viz.fx.plotSubtree(node, controller, delta, animating);                
9221           ctx.restore();
9222         }
9223       },
9224
9225       getSiblings: function(nodes) {
9226         var siblings = {};
9227         $.each(nodes, function(n) {
9228             var par = n.getParents();
9229             if (par.length == 0) {
9230                 siblings[n.id] = [n];
9231             } else {
9232                 var ans = [];
9233                 par[0].eachSubnode(function(sn) {
9234                     ans.push(sn);
9235                 });
9236                 siblings[n.id] = ans;
9237             }
9238         });
9239         return siblings;
9240     }
9241 });
9242
9243 /*
9244    ST.Geom
9245
9246    Performs low level geometrical computations.
9247
9248    Access:
9249
9250    This instance can be accessed with the _geom_ parameter of the st instance created.
9251
9252    Example:
9253
9254    (start code js)
9255     var st = new ST(canvas, config);
9256     st.geom.translate //or can also call any other <ST.Geom> method
9257    (end code)
9258
9259 */
9260
9261 $jit.ST.Geom = new Class({
9262     Implements: Graph.Geom,
9263     /*
9264        Changes the tree current orientation to the one specified.
9265
9266        You should usually use <ST.switchPosition> instead.
9267     */  
9268     switchOrientation: function(orn) {
9269         this.config.orientation = orn;
9270     },
9271
9272     /*
9273        Makes a value dispatch according to the current layout
9274        Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9275      */
9276     dispatch: function() {
9277           // TODO(nico) should store Array.prototype.slice.call somewhere.
9278         var args = Array.prototype.slice.call(arguments);
9279         var s = args.shift(), len = args.length;
9280         var val = function(a) { return typeof a == 'function'? a() : a; };
9281         if(len == 2) {
9282             return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9283         } else if(len == 4) {
9284             switch(s) {
9285                 case "top": return val(args[0]);
9286                 case "right": return val(args[1]);
9287                 case "bottom": return val(args[2]);
9288                 case "left": return val(args[3]);
9289             }
9290         }
9291         return undefined;
9292     },
9293
9294     /*
9295        Returns label height or with, depending on the tree current orientation.
9296     */  
9297     getSize: function(n, invert) {
9298         var data = n.data, config = this.config;
9299         var siblingOffset = config.siblingOffset;
9300         var s = (config.multitree 
9301                         && ('$orn' in data) 
9302                         && data.$orn) || config.orientation;
9303         var w = n.getData('width') + siblingOffset;
9304         var h = n.getData('height') + siblingOffset;
9305         if(!invert)
9306             return this.dispatch(s, h, w);
9307         else
9308             return this.dispatch(s, w, h);
9309     },
9310     
9311     /*
9312        Calculates a subtree base size. This is an utility function used by _getBaseSize_
9313     */  
9314     getTreeBaseSize: function(node, level, leaf) {
9315         var size = this.getSize(node, true), baseHeight = 0, that = this;
9316         if(leaf(level, node)) return size;
9317         if(level === 0) return 0;
9318         node.eachSubnode(function(elem) {
9319             baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9320         });
9321         return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9322     },
9323
9324
9325     /*
9326        getEdge
9327        
9328        Returns a Complex instance with the begin or end position of the edge to be plotted.
9329
9330        Parameters:
9331
9332        node - A <Graph.Node> that is connected to this edge.
9333        type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9334
9335        Returns:
9336
9337        A <Complex> number specifying the begin or end position.
9338     */  
9339     getEdge: function(node, type, s) {
9340         var $C = function(a, b) { 
9341           return function(){
9342             return node.pos.add(new Complex(a, b));
9343           }; 
9344         };
9345         var dim = this.node;
9346         var w = node.getData('width');
9347         var h = node.getData('height');
9348
9349         if(type == 'begin') {
9350             if(dim.align == "center") {
9351                 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9352                                      $C(0, -h/2),$C(w/2, 0));
9353             } else if(dim.align == "left") {
9354                 return this.dispatch(s, $C(0, h), $C(0, 0),
9355                                      $C(0, 0), $C(w, 0));
9356             } else if(dim.align == "right") {
9357                 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9358                                      $C(0, -h),$C(0, 0));
9359             } else throw "align: not implemented";
9360             
9361             
9362         } else if(type == 'end') {
9363             if(dim.align == "center") {
9364                 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9365                                      $C(0, h/2),  $C(-w/2, 0));
9366             } else if(dim.align == "left") {
9367                 return this.dispatch(s, $C(0, 0), $C(w, 0),
9368                                      $C(0, h), $C(0, 0));
9369             } else if(dim.align == "right") {
9370                 return this.dispatch(s, $C(0, -h),$C(0, 0),
9371                                      $C(0, 0), $C(-w, 0));
9372             } else throw "align: not implemented";
9373         }
9374     },
9375
9376     /*
9377        Adjusts the tree position due to canvas scaling or translation.
9378     */  
9379     getScaledTreePosition: function(node, scale) {
9380         var dim = this.node;
9381         var w = node.getData('width');
9382         var h = node.getData('height');
9383         var s = (this.config.multitree 
9384                         && ('$orn' in node.data) 
9385                         && node.data.$orn) || this.config.orientation;
9386
9387         var $C = function(a, b) { 
9388           return function(){
9389             return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9390           }; 
9391         };
9392         if(dim.align == "left") {
9393             return this.dispatch(s, $C(0, h), $C(0, 0),
9394                                  $C(0, 0), $C(w, 0));
9395         } else if(dim.align == "center") {
9396             return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9397                                  $C(0, -h / 2),$C(w / 2, 0));
9398         } else if(dim.align == "right") {
9399             return this.dispatch(s, $C(0, 0), $C(-w, 0),
9400                                  $C(0, -h),$C(0, 0));
9401         } else throw "align: not implemented";
9402     },
9403
9404     /*
9405        treeFitsInCanvas
9406        
9407        Returns a Boolean if the current subtree fits in canvas.
9408
9409        Parameters:
9410
9411        node - A <Graph.Node> which is the current root of the subtree.
9412        canvas - The <Canvas> object.
9413        level - The depth of the subtree to be considered.
9414     */  
9415     treeFitsInCanvas: function(node, canvas, level) {
9416         var csize = canvas.getSize();
9417         var s = (this.config.multitree 
9418                         && ('$orn' in node.data) 
9419                         && node.data.$orn) || this.config.orientation;
9420
9421         var size = this.dispatch(s, csize.width, csize.height);
9422         var baseSize = this.getTreeBaseSize(node, level, function(level, node) { 
9423           return level === 0 || !node.anySubnode();
9424         });
9425         return (baseSize < size);
9426     }
9427 });
9428
9429 /*
9430   Class: ST.Plot
9431   
9432   Custom extension of <Graph.Plot>.
9433
9434   Extends:
9435
9436   All <Graph.Plot> methods
9437   
9438   See also:
9439   
9440   <Graph.Plot>
9441
9442 */
9443 $jit.ST.Plot = new Class({
9444     
9445     Implements: Graph.Plot,
9446     
9447     /*
9448        Plots a subtree from the spacetree.
9449     */
9450     plotSubtree: function(node, opt, scale, animating) {
9451         var viz = this.viz, canvas = viz.canvas, config = viz.config;
9452         scale = Math.min(Math.max(0.001, scale), 1);
9453         if(scale >= 0) {
9454             node.drawn = false;     
9455             var ctx = canvas.getCtx();
9456             var diff = viz.geom.getScaledTreePosition(node, scale);
9457             ctx.translate(diff.x, diff.y);
9458             ctx.scale(scale, scale);
9459         }
9460         this.plotTree(node, $.merge(opt, {
9461           'withLabels': true,
9462           'hideLabels': !!scale,
9463           'plotSubtree': function(n, ch) {
9464             var root = config.multitree && !('$orn' in node.data);
9465             var orns = root && node.getData('orns');
9466             return !root || orns.indexOf(elem.getData('orn')) > -1;
9467           }
9468         }), animating);
9469         if(scale >= 0) node.drawn = true;
9470     },   
9471    
9472     /*
9473         Method: getAlignedPos
9474         
9475         Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9476         
9477         Parameters:
9478         
9479         pos - (object) A <Graph.Node> position.
9480         width - (number) The width of the node.
9481         height - (number) The height of the node.
9482         
9483      */
9484     getAlignedPos: function(pos, width, height) {
9485         var nconfig = this.node;
9486         var square, orn;
9487         if(nconfig.align == "center") {
9488             square = {
9489                 x: pos.x - width / 2,
9490                 y: pos.y - height / 2
9491             };
9492         } else if (nconfig.align == "left") {
9493             orn = this.config.orientation;
9494             if(orn == "bottom" || orn == "top") {
9495                 square = {
9496                     x: pos.x - width / 2,
9497                     y: pos.y
9498                 };
9499             } else {
9500                 square = {
9501                     x: pos.x,
9502                     y: pos.y - height / 2
9503                 };
9504             }
9505         } else if(nconfig.align == "right") {
9506             orn = this.config.orientation;
9507             if(orn == "bottom" || orn == "top") {
9508                 square = {
9509                     x: pos.x - width / 2,
9510                     y: pos.y - height
9511                 };
9512             } else {
9513                 square = {
9514                     x: pos.x - width,
9515                     y: pos.y - height / 2
9516                 };
9517             }
9518         } else throw "align: not implemented";
9519         
9520         return square;
9521     },
9522     
9523     getOrientation: function(adj) {
9524         var config = this.config;
9525         var orn = config.orientation;
9526
9527         if(config.multitree) {
9528                 var nodeFrom = adj.nodeFrom;
9529                 var nodeTo = adj.nodeTo;
9530                 orn = (('$orn' in nodeFrom.data) 
9531                         && nodeFrom.data.$orn) 
9532                         || (('$orn' in nodeTo.data) 
9533                         && nodeTo.data.$orn);
9534         }
9535
9536         return orn; 
9537     }
9538 });
9539
9540 /*
9541   Class: ST.Label
9542
9543   Custom extension of <Graph.Label>. 
9544   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9545
9546   Extends:
9547
9548   All <Graph.Label> methods and subclasses.
9549
9550   See also:
9551
9552   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9553  */ 
9554 $jit.ST.Label = {};
9555
9556 /*
9557    ST.Label.Native
9558
9559    Custom extension of <Graph.Label.Native>.
9560
9561    Extends:
9562
9563    All <Graph.Label.Native> methods
9564
9565    See also:
9566
9567    <Graph.Label.Native>
9568 */
9569 $jit.ST.Label.Native = new Class({
9570   Implements: Graph.Label.Native,
9571
9572   renderLabel: function(canvas, node, controller) {
9573     var ctx = canvas.getCtx();
9574     var coord = node.pos.getc(true);
9575     ctx.fillText(node.name, coord.x, coord.y);
9576   }
9577 });
9578
9579 $jit.ST.Label.DOM = new Class({
9580   Implements: Graph.Label.DOM,
9581
9582   /* 
9583       placeLabel
9584
9585       Overrides abstract method placeLabel in <Graph.Plot>.
9586
9587       Parameters:
9588
9589       tag - A DOM label element.
9590       node - A <Graph.Node>.
9591       controller - A configuration/controller object passed to the visualization.
9592      
9593     */
9594     placeLabel: function(tag, node, controller) {
9595         var pos = node.pos.getc(true), 
9596             config = this.viz.config, 
9597             dim = config.Node, 
9598             canvas = this.viz.canvas,
9599             w = node.getData('width'),
9600             h = node.getData('height'),
9601             radius = canvas.getSize(),
9602             labelPos, orn;
9603         
9604         var ox = canvas.translateOffsetX,
9605             oy = canvas.translateOffsetY,
9606             sx = canvas.scaleOffsetX,
9607             sy = canvas.scaleOffsetY,
9608             posx = pos.x * sx + ox,
9609             posy = pos.y * sy + oy;
9610
9611         if(dim.align == "center") {
9612             labelPos= {
9613                 x: Math.round(posx - w / 2 + radius.width/2),
9614                 y: Math.round(posy - h / 2 + radius.height/2)
9615             };
9616         } else if (dim.align == "left") {
9617             orn = config.orientation;
9618             if(orn == "bottom" || orn == "top") {
9619                 labelPos= {
9620                     x: Math.round(posx - w / 2 + radius.width/2),
9621                     y: Math.round(posy + radius.height/2)
9622                 };
9623             } else {
9624                 labelPos= {
9625                     x: Math.round(posx + radius.width/2),
9626                     y: Math.round(posy - h / 2 + radius.height/2)
9627                 };
9628             }
9629         } else if(dim.align == "right") {
9630             orn = config.orientation;
9631             if(orn == "bottom" || orn == "top") {
9632                 labelPos= {
9633                     x: Math.round(posx - w / 2 + radius.width/2),
9634                     y: Math.round(posy - h + radius.height/2)
9635                 };
9636             } else {
9637                 labelPos= {
9638                     x: Math.round(posx - w + radius.width/2),
9639                     y: Math.round(posy - h / 2 + radius.height/2)
9640                 };
9641             }
9642         } else throw "align: not implemented";
9643
9644         var style = tag.style;
9645         style.left = labelPos.x + 'px';
9646         style.top  = labelPos.y + 'px';
9647         style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9648         controller.onPlaceLabel(tag, node);
9649     }
9650 });
9651
9652 /*
9653   ST.Label.SVG
9654
9655   Custom extension of <Graph.Label.SVG>.
9656
9657   Extends:
9658
9659   All <Graph.Label.SVG> methods
9660
9661   See also:
9662
9663   <Graph.Label.SVG>
9664 */
9665 $jit.ST.Label.SVG = new Class({
9666   Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9667
9668   initialize: function(viz) {
9669     this.viz = viz;
9670   }
9671 });
9672
9673 /*
9674    ST.Label.HTML
9675
9676    Custom extension of <Graph.Label.HTML>.
9677
9678    Extends:
9679
9680    All <Graph.Label.HTML> methods.
9681
9682    See also:
9683
9684    <Graph.Label.HTML>
9685
9686 */
9687 $jit.ST.Label.HTML = new Class({
9688   Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9689
9690   initialize: function(viz) {
9691     this.viz = viz;
9692   }
9693 });
9694
9695
9696 /*
9697   Class: ST.Plot.NodeTypes
9698
9699   This class contains a list of <Graph.Node> built-in types. 
9700   Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9701
9702   You can add your custom node types, customizing your visualization to the extreme.
9703
9704   Example:
9705
9706   (start code js)
9707     ST.Plot.NodeTypes.implement({
9708       'mySpecialType': {
9709         'render': function(node, canvas) {
9710           //print your custom node to canvas
9711         },
9712         //optional
9713         'contains': function(node, pos) {
9714           //return true if pos is inside the node or false otherwise
9715         }
9716       }
9717     });
9718   (end code)
9719
9720 */
9721 $jit.ST.Plot.NodeTypes = new Class({
9722   'none': {
9723     'render': $.empty,
9724     'contains': $.lambda(false)
9725   },
9726   'circle': {
9727     'render': function(node, canvas) {
9728       var dim  = node.getData('dim'),
9729           pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9730           dim2 = dim/2;
9731       this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9732     },
9733     'contains': function(node, pos) {
9734       var dim  = node.getData('dim'),
9735           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9736           dim2 = dim/2;
9737       this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9738     }
9739   },
9740   'square': {
9741     'render': function(node, canvas) {
9742       var dim  = node.getData('dim'),
9743           dim2 = dim/2,
9744           pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9745       this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9746     },
9747     'contains': function(node, pos) {
9748       var dim  = node.getData('dim'),
9749           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9750           dim2 = dim/2;
9751       this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9752     }
9753   },
9754   'ellipse': {
9755     'render': function(node, canvas) {
9756       var width = node.getData('width'),
9757           height = node.getData('height'),
9758           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9759       this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9760     },
9761     'contains': function(node, pos) {
9762       var width = node.getData('width'),
9763           height = node.getData('height'),
9764           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9765       this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9766     }
9767   },
9768   'rectangle': {
9769     'render': function(node, canvas) {
9770       var width = node.getData('width'),
9771           height = node.getData('height'),
9772           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9773       this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9774     },
9775     'contains': function(node, pos) {
9776       var width = node.getData('width'),
9777           height = node.getData('height'),
9778           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9779       this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9780     }
9781   }
9782 });
9783
9784 /*
9785   Class: ST.Plot.EdgeTypes
9786
9787   This class contains a list of <Graph.Adjacence> built-in types. 
9788   Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9789
9790   You can add your custom edge types, customizing your visualization to the extreme.
9791
9792   Example:
9793
9794   (start code js)
9795     ST.Plot.EdgeTypes.implement({
9796       'mySpecialType': {
9797         'render': function(adj, canvas) {
9798           //print your custom edge to canvas
9799         },
9800         //optional
9801         'contains': function(adj, pos) {
9802           //return true if pos is inside the arc or false otherwise
9803         }
9804       }
9805     });
9806   (end code)
9807
9808 */
9809 $jit.ST.Plot.EdgeTypes = new Class({
9810     'none': $.empty,
9811     'line': {
9812       'render': function(adj, canvas) {
9813         var orn = this.getOrientation(adj),
9814             nodeFrom = adj.nodeFrom, 
9815             nodeTo = adj.nodeTo,
9816             rel = nodeFrom._depth < nodeTo._depth,
9817             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9818             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9819         this.edgeHelper.line.render(from, to, canvas);
9820       },
9821       'contains': function(adj, pos) {
9822         var orn = this.getOrientation(adj),
9823             nodeFrom = adj.nodeFrom, 
9824             nodeTo = adj.nodeTo,
9825             rel = nodeFrom._depth < nodeTo._depth,
9826             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9827             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9828         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9829       }
9830     },
9831      'arrow': {
9832        'render': function(adj, canvas) {
9833          var orn = this.getOrientation(adj),
9834              node = adj.nodeFrom, 
9835              child = adj.nodeTo,
9836              dim = adj.getData('dim'),
9837              from = this.viz.geom.getEdge(node, 'begin', orn),
9838              to = this.viz.geom.getEdge(child, 'end', orn),
9839              direction = adj.data.$direction,
9840              inv = (direction && direction.length>1 && direction[0] != node.id);
9841          this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9842        },
9843        'contains': function(adj, pos) {
9844          var orn = this.getOrientation(adj),
9845              nodeFrom = adj.nodeFrom, 
9846              nodeTo = adj.nodeTo,
9847              rel = nodeFrom._depth < nodeTo._depth,
9848              from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9849              to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9850          return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9851        }
9852      },
9853     'quadratic:begin': {
9854        'render': function(adj, canvas) {
9855           var orn = this.getOrientation(adj);
9856           var nodeFrom = adj.nodeFrom, 
9857               nodeTo = adj.nodeTo,
9858               rel = nodeFrom._depth < nodeTo._depth,
9859               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9860               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9861               dim = adj.getData('dim'),
9862               ctx = canvas.getCtx();
9863           ctx.beginPath();
9864           ctx.moveTo(begin.x, begin.y);
9865           switch(orn) {
9866             case "left":
9867               ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9868               break;
9869             case "right":
9870               ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9871               break;
9872             case "top":
9873               ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9874               break;
9875             case "bottom":
9876               ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9877               break;
9878           }
9879           ctx.stroke();
9880         }
9881      },
9882     'quadratic:end': {
9883        'render': function(adj, canvas) {
9884           var orn = this.getOrientation(adj);
9885           var nodeFrom = adj.nodeFrom, 
9886               nodeTo = adj.nodeTo,
9887               rel = nodeFrom._depth < nodeTo._depth,
9888               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9889               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9890               dim = adj.getData('dim'),
9891               ctx = canvas.getCtx();
9892           ctx.beginPath();
9893           ctx.moveTo(begin.x, begin.y);
9894           switch(orn) {
9895             case "left":
9896               ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9897               break;
9898             case "right":
9899               ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9900               break;
9901             case "top":
9902               ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9903               break;
9904             case "bottom":
9905               ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9906               break;
9907           }
9908           ctx.stroke();
9909        }
9910      },
9911     'bezier': {
9912        'render': function(adj, canvas) {
9913          var orn = this.getOrientation(adj),
9914              nodeFrom = adj.nodeFrom, 
9915              nodeTo = adj.nodeTo,
9916              rel = nodeFrom._depth < nodeTo._depth,
9917              begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9918              end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9919              dim = adj.getData('dim'),
9920              ctx = canvas.getCtx();
9921          ctx.beginPath();
9922          ctx.moveTo(begin.x, begin.y);
9923          switch(orn) {
9924            case "left":
9925              ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9926              break;
9927            case "right":
9928              ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9929              break;
9930            case "top":
9931              ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9932              break;
9933            case "bottom":
9934              ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9935              break;
9936          }
9937          ctx.stroke();
9938        }
9939     }
9940 });
9941
9942
9943 Options.LineChart = {
9944   $extend: true,
9945
9946   animate: false,
9947   labelOffset: 3, // label offset
9948   type: 'basic', // gradient
9949   dataPointSize: 10,
9950   Tips: {
9951     enable: false,
9952     onShow: $.empty,
9953     onHide: $.empty
9954   },
9955   Ticks: {
9956         enable: false,
9957         segments: 4,
9958         color: '#000000'
9959   },
9960   Events: {
9961     enable: false,
9962     onClick: $.empty
9963   },
9964   selectOnHover: true,
9965   showAggregates: true,
9966   showLabels: true,
9967   filterOnClick: false,
9968   restoreOnRightClick: false
9969 };
9970
9971
9972 /*
9973  * File: LineChart.js
9974  *
9975 */
9976
9977 $jit.ST.Plot.NodeTypes.implement({
9978   'linechart-basic' : {
9979     'render' : function(node, canvas) {
9980       var pos = node.pos.getc(true), 
9981           width = node.getData('width'),
9982           height = node.getData('height'),
9983           algnPos = this.getAlignedPos(pos, width, height),
9984           x = algnPos.x + width/2 , y = algnPos.y,
9985           stringArray = node.getData('stringArray'),
9986           lastNode = node.getData('lastNode'),
9987           dimArray = node.getData('dimArray'),
9988           valArray = node.getData('valueArray'),
9989           colorArray = node.getData('colorArray'),
9990           colorLength = colorArray.length,
9991           config = node.getData('config'),
9992           gradient = node.getData('gradient'),
9993           showLabels = config.showLabels,
9994           aggregates = config.showAggregates,
9995           label = config.Label,
9996           prev = node.getData('prev'),
9997           dataPointSize = config.dataPointSize;
9998
9999       var ctx = canvas.getCtx(), border = node.getData('border');
10000       if (colorArray && dimArray && stringArray) {
10001         
10002                for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10003                 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10004                         ctx.lineWidth = 4;
10005                         ctx.lineCap = "round";
10006                   if(!lastNode) {
10007
10008                           ctx.save();
10009                                   //render line segment, dimarray[i][0] is the curent datapoint, dimarrya[i][1] is the next datapoint, we need both in the current iteration to draw the line segment
10010                           ctx.beginPath();
10011                           ctx.moveTo(x, y  - dimArray[i][0]); 
10012                           ctx.lineTo(x + width, y - dimArray[i][1]);
10013                           ctx.stroke();
10014                           ctx.restore();
10015                   }
10016                   //render data point
10017                   ctx.fillRect(x - (dataPointSize/2), y  - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10018                 }
10019         
10020
10021           if(label.type == 'Native' && showLabels) {
10022           //bottom labels
10023           ctx.fillStyle = ctx.strokeStyle = label.color;
10024           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10025           ctx.textAlign = 'center';
10026           ctx.textBaseline = 'middle';
10027           ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10028           }
10029           
10030
10031       }
10032     },
10033     'contains': function(node, mpos) {
10034       var pos = node.pos.getc(true), 
10035           width = node.getData('width'),
10036           height = node.getData('height'),
10037           config = node.getData('config'),
10038           dataPointSize = config.dataPointSize,
10039           dataPointMidPoint = dataPointSize/2,
10040           algnPos = this.getAlignedPos(pos, width, height),
10041           x = algnPos.x + width/2, y = algnPos.y,
10042           dimArray = node.getData('dimArray');
10043       //bounding box check
10044       if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10045         return false;
10046       }
10047       //deep check
10048       for(var i=0, l=dimArray.length; i<l; i++) {
10049         var dimi = dimArray[i];
10050                 var url = Url.decode(node.getData('linkArray')[i]);
10051           if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10052                 var valArrayCur = node.getData('valArrayCur');
10053           var results = array_match(valArrayCur[i],valArrayCur);
10054           var matches = results[0];
10055           var indexValues = results[1];
10056           if(matches > 1) {
10057                         var names = new Array(),
10058                                 values = new Array(),
10059                                 percentages = new Array(),
10060                                 linksArr = new Array();
10061                                 for(var j=0, il=indexValues.length; j<il; j++) {
10062                                         names[j] = node.getData('stringArray')[indexValues[j]];
10063                                         values[j] = valArrayCur[indexValues[j]];
10064                                         percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10065                                         linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10066                                         
10067                                 }       
10068                         return {
10069                             'name': names,
10070                             'color': node.getData('colorArray')[i],
10071                             'value': values,
10072                             'percentage': percentages,
10073                             'link': false,
10074                             'collision': true
10075                         };
10076                 }
10077           else {
10078                   return {
10079                     'name': node.getData('stringArray')[i],
10080                     'color': node.getData('colorArray')[i],
10081                     'value': node.getData('valueArray')[i][0],
10082         //            'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10083                     'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10084                     'link': url,
10085                     'collision': false
10086                   };
10087           }
10088         }
10089       }
10090       return false;
10091     }
10092   }
10093 });
10094
10095 /*
10096   Class: Line
10097   
10098   A visualization that displays line charts.
10099   
10100   Constructor Options:
10101   
10102   See <Options.Line>.
10103
10104 */
10105 $jit.LineChart = new Class({
10106   st: null,
10107   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10108   selected: {},
10109   busy: false,
10110   
10111   initialize: function(opt) {
10112     this.controller = this.config = 
10113       $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10114         Label: { type: 'Native' }
10115       }, opt);
10116     //set functions for showLabels and showAggregates
10117     var showLabels = this.config.showLabels,
10118         typeLabels = $.type(showLabels),
10119         showAggregates = this.config.showAggregates,
10120         typeAggregates = $.type(showAggregates);
10121     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10122     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10123     Options.Fx.clearCanvas = false;
10124     this.initializeViz();
10125   },
10126   
10127   initializeViz: function() {
10128     var config = this.config,
10129         that = this,
10130         nodeType = config.type.split(":")[0],
10131         nodeLabels = {};
10132
10133     var st = new $jit.ST({
10134       injectInto: config.injectInto,
10135       orientation: "bottom",
10136       backgroundColor: config.backgroundColor,
10137       renderBackground: config.renderBackground,
10138       levelDistance: 0,
10139       siblingOffset: 0,
10140       subtreeOffset: 0,
10141       withLabels: config.Label.type != 'Native',
10142       useCanvas: config.useCanvas,
10143       Label: {
10144         type: config.Label.type
10145       },
10146       Node: {
10147         overridable: true,
10148         type: 'linechart-' + nodeType,
10149         align: 'left',
10150         width: 1,
10151         height: 1
10152       },
10153       Edge: {
10154         type: 'none'
10155       },
10156       Tips: {
10157         enable: config.Tips.enable,
10158         type: 'Native',
10159         force: true,
10160         onShow: function(tip, node, contains) {
10161           var elem = contains;
10162           config.Tips.onShow(tip, elem, node);
10163         }
10164       },
10165       Events: {
10166         enable: true,
10167         type: 'Native',
10168         onClick: function(node, eventInfo, evt) {
10169           if(!config.filterOnClick && !config.Events.enable) return;
10170           var elem = eventInfo.getContains();
10171           if(elem) config.filterOnClick && that.filter(elem.name);
10172           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10173         },
10174         onRightClick: function(node, eventInfo, evt) {
10175           if(!config.restoreOnRightClick) return;
10176           that.restore();
10177         },
10178         onMouseMove: function(node, eventInfo, evt) {
10179           if(!config.selectOnHover) return;
10180           if(node) {
10181             var elem = eventInfo.getContains();
10182             that.select(node.id, elem.name, elem.index);
10183           } else {
10184             that.select(false, false, false);
10185           }
10186         }
10187       },
10188       onCreateLabel: function(domElement, node) {
10189         var labelConf = config.Label,
10190             valueArray = node.getData('valueArray'),
10191             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10192             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10193         if(node.getData('prev')) {
10194           var nlbs = {
10195             wrapper: document.createElement('div'),
10196             aggregate: document.createElement('div'),
10197             label: document.createElement('div')
10198           };
10199           var wrapper = nlbs.wrapper,
10200               label = nlbs.label,
10201               aggregate = nlbs.aggregate,
10202               wrapperStyle = wrapper.style,
10203               labelStyle = label.style,
10204               aggregateStyle = aggregate.style;
10205           //store node labels
10206           nodeLabels[node.id] = nlbs;
10207           //append labels
10208           wrapper.appendChild(label);
10209           wrapper.appendChild(aggregate);
10210           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10211             label.style.display = 'none';
10212           }
10213           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10214             aggregate.style.display = 'none';
10215           }
10216           wrapperStyle.position = 'relative';
10217           wrapperStyle.overflow = 'visible';
10218           wrapperStyle.fontSize = labelConf.size + 'px';
10219           wrapperStyle.fontFamily = labelConf.family;
10220           wrapperStyle.color = labelConf.color;
10221           wrapperStyle.textAlign = 'center';
10222           aggregateStyle.position = labelStyle.position = 'absolute';
10223           
10224           domElement.style.width = node.getData('width') + 'px';
10225           domElement.style.height = node.getData('height') + 'px';
10226           label.innerHTML = node.name;
10227           
10228           domElement.appendChild(wrapper);
10229         }
10230       },
10231       onPlaceLabel: function(domElement, node) {
10232         if(!node.getData('prev')) return;
10233         var labels = nodeLabels[node.id],
10234             wrapperStyle = labels.wrapper.style,
10235             labelStyle = labels.label.style,
10236             aggregateStyle = labels.aggregate.style,
10237             width = node.getData('width'),
10238             height = node.getData('height'),
10239             dimArray = node.getData('dimArray'),
10240             valArray = node.getData('valueArray'),
10241             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10242             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10243             font = parseInt(wrapperStyle.fontSize, 10),
10244             domStyle = domElement.style;
10245         
10246         if(dimArray && valArray) {
10247           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10248             labelStyle.display = '';
10249           } else {
10250             labelStyle.display = 'none';
10251           }
10252           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10253             aggregateStyle.display = '';
10254           } else {
10255             aggregateStyle.display = 'none';
10256           }
10257           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10258           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10259           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10260             if(dimArray[i][0] > 0) {
10261               acum+= valArray[i][0];
10262               leftAcum+= dimArray[i][0];
10263             }
10264           }
10265           aggregateStyle.top = (-font - config.labelOffset) + 'px';
10266           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10267           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10268           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10269           labels.aggregate.innerHTML = acum;
10270         }
10271       }
10272     });
10273     
10274     var size = st.canvas.getSize(),
10275         margin = config.Margin;
10276     st.config.offsetY = -size.height/2 + margin.bottom 
10277       + (config.showLabels && (config.labelOffset + config.Label.size));
10278     st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10279     this.st = st;
10280     this.canvas = this.st.canvas;
10281   },
10282   
10283     renderTitle: function() {
10284         var canvas = this.canvas,
10285         size = canvas.getSize(),
10286         config = this.config,
10287         margin = config.Margin,
10288         label = config.Label,
10289         title = config.Title;
10290         ctx = canvas.getCtx();
10291         ctx.fillStyle = title.color;
10292         ctx.textAlign = 'left';
10293         ctx.textBaseline = 'top';
10294         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10295         if(label.type == 'Native') {
10296                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10297         }
10298   },  
10299   
10300     renderTicks: function() {
10301
10302         var canvas = this.canvas,
10303         size = canvas.getSize(),
10304         config = this.config,
10305         margin = config.Margin,
10306         ticks = config.Ticks,
10307         title = config.Title,
10308         subtitle = config.Subtitle,
10309         label = config.Label,
10310         maxValue = this.maxValue,
10311         maxTickValue = Math.ceil(maxValue*.1)*10;
10312         if(maxTickValue == maxValue) {
10313                 var length = maxTickValue.toString().length;
10314                 maxTickValue = maxTickValue + parseInt(pad(1,length));
10315         }
10316
10317
10318         labelValue = 0,
10319         labelIncrement = maxTickValue/ticks.segments,
10320         ctx = canvas.getCtx();
10321         ctx.strokeStyle = ticks.color;
10322     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10323         ctx.textAlign = 'center';
10324         ctx.textBaseline = 'middle';
10325         
10326         idLabel = canvas.id + "-label";
10327         labelDim = 100;
10328         container = document.getElementById(idLabel);
10329                   
10330                   
10331                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10332                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10333                 grid = -size.height+(margin.bottom+config.labelOffset+label.size+margin.top+(title.text? title.size+title.offset:0)+(subtitle.text? subtitle.size+subtitle.offset:0)),
10334                 segmentLength = grid/ticks.segments;
10335                 ctx.fillStyle = ticks.color;
10336                 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size-1, -(size.height/2)+margin.top+(title.text? title.size+title.offset:0),1,size.height-margin.top-margin.bottom-label.size-config.labelOffset-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0));
10337
10338                 while(axis>=grid) {
10339                         ctx.save();
10340                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10341                         ctx.rotate(Math.PI / 2);
10342                         ctx.fillStyle = label.color;
10343                         if(config.showLabels) {
10344                                 if(label.type == 'Native') { 
10345                                         ctx.fillText(labelValue, 0, 0);
10346                                 } else {
10347                                         //html labels on y axis
10348                                         labelDiv = document.createElement('div');
10349                                         labelDiv.innerHTML = labelValue;
10350                                         labelDiv.className = "rotatedLabel";
10351 //                                      labelDiv.class = "rotatedLabel";
10352                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10353                                         labelDiv.style.left = margin.left + "px";
10354                                         labelDiv.style.width = labelDim + "px";
10355                                         labelDiv.style.height = labelDim + "px";
10356                                         labelDiv.style.textAlign = "center";
10357                                         labelDiv.style.verticalAlign = "middle";
10358                                         labelDiv.style.position = "absolute";
10359                                         container.appendChild(labelDiv);
10360                                 }
10361                         }
10362                         ctx.restore();
10363                         ctx.fillStyle = ticks.color;
10364                         ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size, Math.round(axis), size.width-margin.right-margin.left-config.labelOffset-label.size,1 );
10365                         htmlOrigin += segmentLength;
10366                         axis += segmentLength;
10367                         labelValue += labelIncrement;
10368                 }
10369         
10370
10371         
10372         
10373         
10374
10375   },
10376   
10377   renderBackground: function() {
10378                 var canvas = this.canvas,
10379                 config = this.config,
10380                 backgroundColor = config.backgroundColor,
10381                 size = canvas.getSize(),
10382                 ctx = canvas.getCtx();
10383                 //ctx.globalCompositeOperation = "destination-over";
10384             ctx.fillStyle = backgroundColor;
10385             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
10386   },
10387   clear: function() {
10388         var canvas = this.canvas;
10389         var ctx = canvas.getCtx(),
10390         size = canvas.getSize();
10391         ctx.fillStyle = "rgba(255,255,255,0)";
10392         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10393         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10394  },
10395   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
10396         var canvas = this.canvas,
10397         size = canvas.getSize(),
10398         config = this.config,
10399         orgHeight = size.height,
10400         margin = config.Margin,
10401         st = this.st,
10402         horz = config.orientation == 'horizontal';
10403         
10404
10405         var newWindowWidth = document.body.offsetWidth;
10406         var diff = newWindowWidth - orgWindowWidth;     
10407         var newWidth = orgContainerDivWidth + (diff/cols);
10408         canvas.resize(newWidth,orgHeight);
10409         if(typeof FlashCanvas == "undefined") {
10410                 canvas.clear();
10411         } else {
10412                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10413         }
10414         this.loadJSON(json);
10415
10416         },
10417  /*
10418   Method: loadJSON
10419  
10420   Loads JSON data into the visualization. 
10421   
10422   Parameters:
10423   
10424   json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
10425   
10426   Example:
10427   (start code js)
10428   var areaChart = new $jit.AreaChart(options);
10429   areaChart.loadJSON(json);
10430   (end code)
10431  */  
10432   loadJSON: function(json) {
10433     var prefix = $.time(), 
10434         ch = [], 
10435         st = this.st,
10436         name = $.splat(json.label), 
10437         color = $.splat(json.color || this.colors),
10438         config = this.config,
10439         ticks = config.Ticks,
10440         renderBackground = config.renderBackground,
10441         gradient = !!config.type.split(":")[1],
10442         animate = config.animate,
10443         title = config.Title,
10444         groupTotalValue = 0;
10445     
10446     var valArrayAll = new Array();
10447
10448     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10449         var val = values[i];
10450         var valArray = $.splat(val.values);
10451         for (var j=0, len=valArray.length; j<len; j++) {
10452                 valArrayAll.push(parseInt(valArray[j]));
10453         }
10454         groupTotalValue += parseInt(valArray.sum());
10455     }
10456     
10457     this.maxValue =  Math.max.apply(null, valArrayAll);
10458     
10459     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10460       var val = values[i], prev = values[i-1];
10461
10462       var next = (i+1 < l) ? values[i+1] : 0;
10463       var valLeft = $.splat(values[i].values);
10464       var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10465       var valArray = $.zip(valLeft, valRight);
10466       var valArrayCur = $.splat(values[i].values);
10467       var linkArray = $.splat(values[i].links);
10468       var acumLeft = 0, acumRight = 0;
10469       var lastNode = (l-1 == i) ? true : false; 
10470       ch.push({
10471         'id': prefix + val.label,
10472         'name': val.label,
10473         'data': {
10474           'value': valArray,
10475           '$valueArray': valArray,
10476           '$valArrayCur': valArrayCur,
10477           '$colorArray': color,
10478           '$linkArray': linkArray,
10479           '$stringArray': name,
10480           '$next': next? next.label:false,
10481           '$prev': prev? prev.label:false,
10482           '$config': config,
10483           '$lastNode': lastNode,
10484           '$groupTotalValue': groupTotalValue,
10485           '$gradient': gradient
10486         },
10487         'children': []
10488       });
10489     }
10490     var root = {
10491       'id': prefix + '$root',
10492       'name': '',
10493       'data': {
10494         '$type': 'none',
10495         '$width': 1,
10496         '$height': 1
10497       },
10498       'children': ch
10499     };
10500     st.loadJSON(root);
10501     
10502     this.normalizeDims();
10503     
10504     if(renderBackground) {
10505         this.renderBackground();        
10506     }
10507     
10508     if(!animate && ticks.enable) {
10509                 this.renderTicks();
10510         }
10511         
10512         
10513         if(title.text) {
10514                 this.renderTitle();     
10515         }
10516         
10517     st.compute();
10518     st.select(st.root);
10519     if(animate) {
10520       st.fx.animate({
10521         modes: ['node-property:height:dimArray'],
10522         duration:1500
10523       });
10524     }
10525   },
10526   
10527  /*
10528   Method: updateJSON
10529  
10530   Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
10531   
10532   Parameters:
10533   
10534   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10535   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10536   
10537   Example:
10538   
10539   (start code js)
10540   areaChart.updateJSON(json, {
10541     onComplete: function() {
10542       alert('update complete!');
10543     }
10544   });
10545   (end code)
10546  */  
10547   updateJSON: function(json, onComplete) {
10548     if(this.busy) return;
10549     this.busy = true;
10550     
10551     var st = this.st,
10552         graph = st.graph,
10553         labels = json.label && $.splat(json.label),
10554         values = json.values,
10555         animate = this.config.animate,
10556         that = this;
10557     $.each(values, function(v) {
10558       var n = graph.getByName(v.label);
10559       if(n) {
10560         v.values = $.splat(v.values);
10561         var stringArray = n.getData('stringArray'),
10562             valArray = n.getData('valueArray');
10563         $.each(valArray, function(a, i) {
10564           a[0] = v.values[i];
10565           if(labels) stringArray[i] = labels[i];
10566         });
10567         n.setData('valueArray', valArray);
10568         var prev = n.getData('prev'),
10569             next = n.getData('next'),
10570             nextNode = graph.getByName(next);
10571         if(prev) {
10572           var p = graph.getByName(prev);
10573           if(p) {
10574             var valArray = p.getData('valueArray');
10575             $.each(valArray, function(a, i) {
10576               a[1] = v.values[i];
10577             });
10578           }
10579         }
10580         if(!nextNode) {
10581           var valArray = n.getData('valueArray');
10582           $.each(valArray, function(a, i) {
10583             a[1] = v.values[i];
10584           });
10585         }
10586       }
10587     });
10588     this.normalizeDims();
10589     st.compute();
10590     
10591     st.select(st.root);
10592     if(animate) {
10593       st.fx.animate({
10594         modes: ['node-property:height:dimArray'],
10595         duration:1500,
10596         onComplete: function() {
10597           that.busy = false;
10598           onComplete && onComplete.onComplete();
10599         }
10600       });
10601     }
10602   },
10603   
10604 /*
10605   Method: filter
10606  
10607   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10608   
10609   Parameters:
10610   
10611   Variable strings arguments with the name of the stacks.
10612   
10613   Example:
10614   
10615   (start code js)
10616   areaChart.filter('label A', 'label C');
10617   (end code)
10618   
10619   See also:
10620   
10621   <AreaChart.restore>.
10622  */  
10623   filter: function() {
10624     if(this.busy) return;
10625     this.busy = true;
10626     if(this.config.Tips.enable) this.st.tips.hide();
10627     this.select(false, false, false);
10628     var args = Array.prototype.slice.call(arguments);
10629     var rt = this.st.graph.getNode(this.st.root);
10630     var that = this;
10631     rt.eachAdjacency(function(adj) {
10632       var n = adj.nodeTo, 
10633           dimArray = n.getData('dimArray'),
10634           stringArray = n.getData('stringArray');
10635       n.setData('dimArray', $.map(dimArray, function(d, i) {
10636         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10637       }), 'end');
10638     });
10639     this.st.fx.animate({
10640       modes: ['node-property:dimArray'],
10641       duration:1500,
10642       onComplete: function() {
10643         that.busy = false;
10644       }
10645     });
10646   },
10647   
10648   /*
10649   Method: restore
10650  
10651   Sets all stacks that could have been filtered visible.
10652   
10653   Example:
10654   
10655   (start code js)
10656   areaChart.restore();
10657   (end code)
10658   
10659   See also:
10660   
10661   <AreaChart.filter>.
10662  */  
10663   restore: function() {
10664     if(this.busy) return;
10665     this.busy = true;
10666     if(this.config.Tips.enable) this.st.tips.hide();
10667     this.select(false, false, false);
10668     this.normalizeDims();
10669     var that = this;
10670     this.st.fx.animate({
10671       modes: ['node-property:height:dimArray'],
10672       duration:1500,
10673       onComplete: function() {
10674         that.busy = false;
10675       }
10676     });
10677   },
10678   //adds the little brown bar when hovering the node
10679   select: function(id, name, index) {
10680     if(!this.config.selectOnHover) return;
10681     var s = this.selected;
10682     if(s.id != id || s.name != name 
10683         || s.index != index) {
10684       s.id = id;
10685       s.name = name;
10686       s.index = index;
10687       this.st.graph.eachNode(function(n) {
10688         n.setData('border', false);
10689       });
10690       if(id) {
10691         var n = this.st.graph.getNode(id);
10692         n.setData('border', s);
10693         var link = index === 0? 'prev':'next';
10694         link = n.getData(link);
10695         if(link) {
10696           n = this.st.graph.getByName(link);
10697           if(n) {
10698             n.setData('border', {
10699               name: name,
10700               index: 1-index
10701             });
10702           }
10703         }
10704       }
10705       this.st.plot();
10706     }
10707   },
10708   
10709   /*
10710     Method: getLegend
10711    
10712     Returns an object containing as keys the legend names and as values hex strings with color values.
10713     
10714     Example:
10715     
10716     (start code js)
10717     var legend = areaChart.getLegend();
10718     (end code)
10719  */  
10720   getLegend: function() {
10721     var legend = new Array();
10722     var name = new Array();
10723     var color = new Array();
10724     var n;
10725     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10726       n = adj.nodeTo;
10727     });
10728     var colors = n.getData('colorArray'),
10729         len = colors.length;
10730     $.each(n.getData('stringArray'), function(s, i) {
10731       color[i] = colors[i % len];
10732       name[i] = s;
10733     });
10734         legend['name'] = name;
10735         legend['color'] = color;
10736     return legend;
10737   },
10738   
10739   /*
10740     Method: getMaxValue
10741    
10742     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10743     
10744     Example:
10745     
10746     (start code js)
10747     var ans = areaChart.getMaxValue();
10748     (end code)
10749     
10750     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10751     
10752     Example:
10753     
10754     (start code js)
10755     //will return 100 for all AreaChart instances,
10756     //displaying all of them with the same scale
10757     $jit.AreaChart.implement({
10758       'getMaxValue': function() {
10759         return 100;
10760       }
10761     });
10762     (end code)
10763     
10764 */  
10765
10766   normalizeDims: function() {
10767     //number of elements
10768     var root = this.st.graph.getNode(this.st.root), l=0;
10769     root.eachAdjacency(function() {
10770       l++;
10771     });
10772     
10773
10774     var maxValue = this.maxValue || 1,
10775         size = this.st.canvas.getSize(),
10776         config = this.config,
10777         margin = config.Margin,
10778         labelOffset = config.labelOffset + config.Label.size,
10779         fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10780         animate = config.animate,
10781         ticks = config.Ticks,
10782         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
10783           - (config.showLabels && labelOffset);
10784           
10785           
10786         var maxTickValue = Math.ceil(maxValue*.1)*10;
10787                 if(maxTickValue == maxValue) {
10788                         var length = maxTickValue.toString().length;
10789                         maxTickValue = maxTickValue + parseInt(pad(1,length));
10790                 }
10791                 
10792                 
10793                 
10794     this.st.graph.eachNode(function(n) {
10795       var acumLeft = 0, acumRight = 0, animateValue = [];
10796       $.each(n.getData('valueArray'), function(v) {
10797         acumLeft += +v[0];
10798         acumRight += +v[1];
10799         animateValue.push([0, 0]);
10800       });
10801       var acum = acumRight>acumLeft? acumRight:acumLeft;
10802       
10803       n.setData('width', fixedDim);
10804       if(animate) {
10805         n.setData('height', acum * height / maxValue, 'end');
10806         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10807           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10808         }), 'end');
10809         var dimArray = n.getData('dimArray');
10810         if(!dimArray) {
10811           n.setData('dimArray', animateValue);
10812         }
10813       } else {
10814         
10815         if(ticks.enable) {
10816                 n.setData('height', acum * height / maxValue);
10817                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10818                   return [n[0] * height / maxTickValue, n[1] * height / maxTickValue]; 
10819                 }));
10820         } else {
10821                 n.setData('height', acum * height / maxValue);
10822                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10823                   return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10824                 }));
10825         }
10826         
10827         
10828       }
10829     });
10830   }
10831 });
10832
10833
10834
10835
10836
10837 /*
10838  * File: AreaChart.js
10839  *
10840 */
10841
10842 $jit.ST.Plot.NodeTypes.implement({
10843   'areachart-stacked' : {
10844     'render' : function(node, canvas) {
10845       var pos = node.pos.getc(true), 
10846           width = node.getData('width'),
10847           height = node.getData('height'),
10848           algnPos = this.getAlignedPos(pos, width, height),
10849           x = algnPos.x, y = algnPos.y,
10850           stringArray = node.getData('stringArray'),
10851           dimArray = node.getData('dimArray'),
10852           valArray = node.getData('valueArray'),
10853           valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10854           valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10855           colorArray = node.getData('colorArray'),
10856           colorLength = colorArray.length,
10857           config = node.getData('config'),
10858           gradient = node.getData('gradient'),
10859           showLabels = config.showLabels,
10860           aggregates = config.showAggregates,
10861           label = config.Label,
10862           prev = node.getData('prev');
10863
10864       var ctx = canvas.getCtx(), border = node.getData('border');
10865       if (colorArray && dimArray && stringArray) {
10866         for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10867           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10868           ctx.save();
10869           if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10870             var h1 = acumLeft + dimArray[i][0],
10871                 h2 = acumRight + dimArray[i][1],
10872                 alpha = Math.atan((h2 - h1) / width),
10873                 delta = 55;
10874             var linear = ctx.createLinearGradient(x + width/2, 
10875                 y - (h1 + h2)/2,
10876                 x + width/2 + delta * Math.sin(alpha),
10877                 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10878             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10879                 function(v) { return (v * 0.85) >> 0; }));
10880             linear.addColorStop(0, colorArray[i % colorLength]);
10881             linear.addColorStop(1, color);
10882             ctx.fillStyle = linear;
10883           }
10884           ctx.beginPath();
10885           ctx.moveTo(x, y - acumLeft);
10886           ctx.lineTo(x + width, y - acumRight);
10887           ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10888           ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10889           ctx.lineTo(x, y - acumLeft);
10890           ctx.fill();
10891           ctx.restore();
10892           if(border) {
10893             var strong = border.name == stringArray[i];
10894             var perc = strong? 0.7 : 0.8;
10895             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10896                 function(v) { return (v * perc) >> 0; }));
10897             ctx.strokeStyle = color;
10898             ctx.lineWidth = strong? 4 : 1;
10899             ctx.save();
10900             ctx.beginPath();
10901             if(border.index === 0) {
10902               ctx.moveTo(x, y - acumLeft);
10903               ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10904             } else {
10905               ctx.moveTo(x + width, y - acumRight);
10906               ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10907             }
10908             ctx.stroke();
10909             ctx.restore();
10910           }
10911           acumLeft += (dimArray[i][0] || 0);
10912           acumRight += (dimArray[i][1] || 0);
10913           
10914           if(dimArray[i][0] > 0)
10915             valAcum += (valArray[i][0] || 0);
10916         }
10917         if(prev && label.type == 'Native') {
10918           ctx.save();
10919           ctx.beginPath();
10920           ctx.fillStyle = ctx.strokeStyle = label.color;
10921           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10922           ctx.textAlign = 'center';
10923           ctx.textBaseline = 'middle';
10924           if(aggregates(node.name, valLeft, valRight, node)) {
10925             ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10926           }
10927           if(showLabels(node.name, valLeft, valRight, node)) {
10928             ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10929           }
10930           ctx.restore();
10931         }
10932       }
10933     },
10934     'contains': function(node, mpos) {
10935       var pos = node.pos.getc(true), 
10936           width = node.getData('width'),
10937           height = node.getData('height'),
10938           algnPos = this.getAlignedPos(pos, width, height),
10939           x = algnPos.x, y = algnPos.y,
10940           dimArray = node.getData('dimArray'),
10941           rx = mpos.x - x;
10942       //bounding box check
10943       if(mpos.x < x || mpos.x > x + width
10944         || mpos.y > y || mpos.y < y - height) {
10945         return false;
10946       }
10947       //deep check
10948       for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10949         var dimi = dimArray[i];
10950         lAcum -= dimi[0];
10951         rAcum -= dimi[1];
10952         var intersec = lAcum + (rAcum - lAcum) * rx / width;
10953         if(mpos.y >= intersec) {
10954           var index = +(rx > width/2);
10955           return {
10956             'name': node.getData('stringArray')[i],
10957             'color': node.getData('colorArray')[i],
10958             'value': node.getData('valueArray')[i][index],
10959             'index': index
10960           };
10961         }
10962       }
10963       return false;
10964     }
10965   }
10966 });
10967
10968 /*
10969   Class: AreaChart
10970   
10971   A visualization that displays stacked area charts.
10972   
10973   Constructor Options:
10974   
10975   See <Options.AreaChart>.
10976
10977 */
10978 $jit.AreaChart = new Class({
10979   st: null,
10980   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10981   selected: {},
10982   busy: false,
10983   
10984   initialize: function(opt) {
10985     this.controller = this.config = 
10986       $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10987         Label: { type: 'Native' }
10988       }, opt);
10989     //set functions for showLabels and showAggregates
10990     var showLabels = this.config.showLabels,
10991         typeLabels = $.type(showLabels),
10992         showAggregates = this.config.showAggregates,
10993         typeAggregates = $.type(showAggregates);
10994     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10995     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10996     
10997     this.initializeViz();
10998   },
10999   
11000   initializeViz: function() {
11001     var config = this.config,
11002         that = this,
11003         nodeType = config.type.split(":")[0],
11004         nodeLabels = {};
11005
11006     var st = new $jit.ST({
11007       injectInto: config.injectInto,
11008       orientation: "bottom",
11009       levelDistance: 0,
11010       siblingOffset: 0,
11011       subtreeOffset: 0,
11012       withLabels: config.Label.type != 'Native',
11013       useCanvas: config.useCanvas,
11014       Label: {
11015         type: config.Label.type
11016       },
11017       Node: {
11018         overridable: true,
11019         type: 'areachart-' + nodeType,
11020         align: 'left',
11021         width: 1,
11022         height: 1
11023       },
11024       Edge: {
11025         type: 'none'
11026       },
11027       Tips: {
11028         enable: config.Tips.enable,
11029         type: 'Native',
11030         force: true,
11031         onShow: function(tip, node, contains) {
11032           var elem = contains;
11033           config.Tips.onShow(tip, elem, node);
11034         }
11035       },
11036       Events: {
11037         enable: true,
11038         type: 'Native',
11039         onClick: function(node, eventInfo, evt) {
11040           if(!config.filterOnClick && !config.Events.enable) return;
11041           var elem = eventInfo.getContains();
11042           if(elem) config.filterOnClick && that.filter(elem.name);
11043           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11044         },
11045         onRightClick: function(node, eventInfo, evt) {
11046           if(!config.restoreOnRightClick) return;
11047           that.restore();
11048         },
11049         onMouseMove: function(node, eventInfo, evt) {
11050           if(!config.selectOnHover) return;
11051           if(node) {
11052             var elem = eventInfo.getContains();
11053             that.select(node.id, elem.name, elem.index);
11054           } else {
11055             that.select(false, false, false);
11056           }
11057         }
11058       },
11059       onCreateLabel: function(domElement, node) {
11060         var labelConf = config.Label,
11061             valueArray = node.getData('valueArray'),
11062             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11063             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11064         if(node.getData('prev')) {
11065           var nlbs = {
11066             wrapper: document.createElement('div'),
11067             aggregate: document.createElement('div'),
11068             label: document.createElement('div')
11069           };
11070           var wrapper = nlbs.wrapper,
11071               label = nlbs.label,
11072               aggregate = nlbs.aggregate,
11073               wrapperStyle = wrapper.style,
11074               labelStyle = label.style,
11075               aggregateStyle = aggregate.style;
11076           //store node labels
11077           nodeLabels[node.id] = nlbs;
11078           //append labels
11079           wrapper.appendChild(label);
11080           wrapper.appendChild(aggregate);
11081           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11082             label.style.display = 'none';
11083           }
11084           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11085             aggregate.style.display = 'none';
11086           }
11087           wrapperStyle.position = 'relative';
11088           wrapperStyle.overflow = 'visible';
11089           wrapperStyle.fontSize = labelConf.size + 'px';
11090           wrapperStyle.fontFamily = labelConf.family;
11091           wrapperStyle.color = labelConf.color;
11092           wrapperStyle.textAlign = 'center';
11093           aggregateStyle.position = labelStyle.position = 'absolute';
11094           
11095           domElement.style.width = node.getData('width') + 'px';
11096           domElement.style.height = node.getData('height') + 'px';
11097           label.innerHTML = node.name;
11098           
11099           domElement.appendChild(wrapper);
11100         }
11101       },
11102       onPlaceLabel: function(domElement, node) {
11103         if(!node.getData('prev')) return;
11104         var labels = nodeLabels[node.id],
11105             wrapperStyle = labels.wrapper.style,
11106             labelStyle = labels.label.style,
11107             aggregateStyle = labels.aggregate.style,
11108             width = node.getData('width'),
11109             height = node.getData('height'),
11110             dimArray = node.getData('dimArray'),
11111             valArray = node.getData('valueArray'),
11112             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11113             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11114             font = parseInt(wrapperStyle.fontSize, 10),
11115             domStyle = domElement.style;
11116         
11117         if(dimArray && valArray) {
11118           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11119             labelStyle.display = '';
11120           } else {
11121             labelStyle.display = 'none';
11122           }
11123           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11124             aggregateStyle.display = '';
11125           } else {
11126             aggregateStyle.display = 'none';
11127           }
11128           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11129           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11130           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11131             if(dimArray[i][0] > 0) {
11132               acum+= valArray[i][0];
11133               leftAcum+= dimArray[i][0];
11134             }
11135           }
11136           aggregateStyle.top = (-font - config.labelOffset) + 'px';
11137           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11138           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11139           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11140           labels.aggregate.innerHTML = acum;
11141         }
11142       }
11143     });
11144     
11145     var size = st.canvas.getSize(),
11146         margin = config.Margin;
11147     st.config.offsetY = -size.height/2 + margin.bottom 
11148       + (config.showLabels && (config.labelOffset + config.Label.size));
11149     st.config.offsetX = (margin.right - margin.left)/2;
11150     this.st = st;
11151     this.canvas = this.st.canvas;
11152   },
11153   
11154  /*
11155   Method: loadJSON
11156  
11157   Loads JSON data into the visualization. 
11158   
11159   Parameters:
11160   
11161   json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
11162   
11163   Example:
11164   (start code js)
11165   var areaChart = new $jit.AreaChart(options);
11166   areaChart.loadJSON(json);
11167   (end code)
11168  */  
11169   loadJSON: function(json) {
11170     var prefix = $.time(), 
11171         ch = [], 
11172         st = this.st,
11173         name = $.splat(json.label), 
11174         color = $.splat(json.color || this.colors),
11175         config = this.config,
11176         gradient = !!config.type.split(":")[1],
11177         animate = config.animate;
11178     
11179     for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11180       var val = values[i], prev = values[i-1], next = values[i+1];
11181       var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11182       var valArray = $.zip(valLeft, valRight);
11183       var acumLeft = 0, acumRight = 0;
11184       ch.push({
11185         'id': prefix + val.label,
11186         'name': val.label,
11187         'data': {
11188           'value': valArray,
11189           '$valueArray': valArray,
11190           '$colorArray': color,
11191           '$stringArray': name,
11192           '$next': next.label,
11193           '$prev': prev? prev.label:false,
11194           '$config': config,
11195           '$gradient': gradient
11196         },
11197         'children': []
11198       });
11199     }
11200     var root = {
11201       'id': prefix + '$root',
11202       'name': '',
11203       'data': {
11204         '$type': 'none',
11205         '$width': 1,
11206         '$height': 1
11207       },
11208       'children': ch
11209     };
11210     st.loadJSON(root);
11211     
11212     this.normalizeDims();
11213     st.compute();
11214     st.select(st.root);
11215     if(animate) {
11216       st.fx.animate({
11217         modes: ['node-property:height:dimArray'],
11218         duration:1500
11219       });
11220     }
11221   },
11222   
11223  /*
11224   Method: updateJSON
11225  
11226   Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
11227   
11228   Parameters:
11229   
11230   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11231   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11232   
11233   Example:
11234   
11235   (start code js)
11236   areaChart.updateJSON(json, {
11237     onComplete: function() {
11238       alert('update complete!');
11239     }
11240   });
11241   (end code)
11242  */  
11243   updateJSON: function(json, onComplete) {
11244     if(this.busy) return;
11245     this.busy = true;
11246     
11247     var st = this.st,
11248         graph = st.graph,
11249         labels = json.label && $.splat(json.label),
11250         values = json.values,
11251         animate = this.config.animate,
11252         that = this;
11253     $.each(values, function(v) {
11254       var n = graph.getByName(v.label);
11255       if(n) {
11256         v.values = $.splat(v.values);
11257         var stringArray = n.getData('stringArray'),
11258             valArray = n.getData('valueArray');
11259         $.each(valArray, function(a, i) {
11260           a[0] = v.values[i];
11261           if(labels) stringArray[i] = labels[i];
11262         });
11263         n.setData('valueArray', valArray);
11264         var prev = n.getData('prev'),
11265             next = n.getData('next'),
11266             nextNode = graph.getByName(next);
11267         if(prev) {
11268           var p = graph.getByName(prev);
11269           if(p) {
11270             var valArray = p.getData('valueArray');
11271             $.each(valArray, function(a, i) {
11272               a[1] = v.values[i];
11273             });
11274           }
11275         }
11276         if(!nextNode) {
11277           var valArray = n.getData('valueArray');
11278           $.each(valArray, function(a, i) {
11279             a[1] = v.values[i];
11280           });
11281         }
11282       }
11283     });
11284     this.normalizeDims();
11285     st.compute();
11286     st.select(st.root);
11287     if(animate) {
11288       st.fx.animate({
11289         modes: ['node-property:height:dimArray'],
11290         duration:1500,
11291         onComplete: function() {
11292           that.busy = false;
11293           onComplete && onComplete.onComplete();
11294         }
11295       });
11296     }
11297   },
11298   
11299 /*
11300   Method: filter
11301  
11302   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11303   
11304   Parameters:
11305   
11306   Variable strings arguments with the name of the stacks.
11307   
11308   Example:
11309   
11310   (start code js)
11311   areaChart.filter('label A', 'label C');
11312   (end code)
11313   
11314   See also:
11315   
11316   <AreaChart.restore>.
11317  */  
11318   filter: function() {
11319     if(this.busy) return;
11320     this.busy = true;
11321     if(this.config.Tips.enable) this.st.tips.hide();
11322     this.select(false, false, false);
11323     var args = Array.prototype.slice.call(arguments);
11324     var rt = this.st.graph.getNode(this.st.root);
11325     var that = this;
11326     rt.eachAdjacency(function(adj) {
11327       var n = adj.nodeTo, 
11328           dimArray = n.getData('dimArray'),
11329           stringArray = n.getData('stringArray');
11330       n.setData('dimArray', $.map(dimArray, function(d, i) {
11331         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11332       }), 'end');
11333     });
11334     this.st.fx.animate({
11335       modes: ['node-property:dimArray'],
11336       duration:1500,
11337       onComplete: function() {
11338         that.busy = false;
11339       }
11340     });
11341   },
11342   
11343   /*
11344   Method: restore
11345  
11346   Sets all stacks that could have been filtered visible.
11347   
11348   Example:
11349   
11350   (start code js)
11351   areaChart.restore();
11352   (end code)
11353   
11354   See also:
11355   
11356   <AreaChart.filter>.
11357  */  
11358   restore: function() {
11359     if(this.busy) return;
11360     this.busy = true;
11361     if(this.config.Tips.enable) this.st.tips.hide();
11362     this.select(false, false, false);
11363     this.normalizeDims();
11364     var that = this;
11365     this.st.fx.animate({
11366       modes: ['node-property:height:dimArray'],
11367       duration:1500,
11368       onComplete: function() {
11369         that.busy = false;
11370       }
11371     });
11372   },
11373   //adds the little brown bar when hovering the node
11374   select: function(id, name, index) {
11375     if(!this.config.selectOnHover) return;
11376     var s = this.selected;
11377     if(s.id != id || s.name != name 
11378         || s.index != index) {
11379       s.id = id;
11380       s.name = name;
11381       s.index = index;
11382       this.st.graph.eachNode(function(n) {
11383         n.setData('border', false);
11384       });
11385       if(id) {
11386         var n = this.st.graph.getNode(id);
11387         n.setData('border', s);
11388         var link = index === 0? 'prev':'next';
11389         link = n.getData(link);
11390         if(link) {
11391           n = this.st.graph.getByName(link);
11392           if(n) {
11393             n.setData('border', {
11394               name: name,
11395               index: 1-index
11396             });
11397           }
11398         }
11399       }
11400       this.st.plot();
11401     }
11402   },
11403   
11404   /*
11405     Method: getLegend
11406    
11407     Returns an object containing as keys the legend names and as values hex strings with color values.
11408     
11409     Example:
11410     
11411     (start code js)
11412     var legend = areaChart.getLegend();
11413     (end code)
11414  */  
11415   getLegend: function() {
11416     var legend = {};
11417     var n;
11418     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11419       n = adj.nodeTo;
11420     });
11421     var colors = n.getData('colorArray'),
11422         len = colors.length;
11423     $.each(n.getData('stringArray'), function(s, i) {
11424       legend[s] = colors[i % len];
11425     });
11426     return legend;
11427   },
11428   
11429   /*
11430     Method: getMaxValue
11431    
11432     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11433     
11434     Example:
11435     
11436     (start code js)
11437     var ans = areaChart.getMaxValue();
11438     (end code)
11439     
11440     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11441     
11442     Example:
11443     
11444     (start code js)
11445     //will return 100 for all AreaChart instances,
11446     //displaying all of them with the same scale
11447     $jit.AreaChart.implement({
11448       'getMaxValue': function() {
11449         return 100;
11450       }
11451     });
11452     (end code)
11453     
11454 */  
11455   getMaxValue: function() {
11456     var maxValue = 0;
11457     this.st.graph.eachNode(function(n) {
11458       var valArray = n.getData('valueArray'),
11459           acumLeft = 0, acumRight = 0;
11460       $.each(valArray, function(v) { 
11461         acumLeft += +v[0];
11462         acumRight += +v[1];
11463       });
11464       var acum = acumRight>acumLeft? acumRight:acumLeft;
11465       maxValue = maxValue>acum? maxValue:acum;
11466     });
11467     return maxValue;
11468   },
11469   
11470   normalizeDims: function() {
11471     //number of elements
11472     var root = this.st.graph.getNode(this.st.root), l=0;
11473     root.eachAdjacency(function() {
11474       l++;
11475     });
11476     var maxValue = this.getMaxValue() || 1,
11477         size = this.st.canvas.getSize(),
11478         config = this.config,
11479         margin = config.Margin,
11480         labelOffset = config.labelOffset + config.Label.size,
11481         fixedDim = (size.width - (margin.left + margin.right)) / l,
11482         animate = config.animate,
11483         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
11484           - (config.showLabels && labelOffset);
11485     this.st.graph.eachNode(function(n) {
11486       var acumLeft = 0, acumRight = 0, animateValue = [];
11487       $.each(n.getData('valueArray'), function(v) {
11488         acumLeft += +v[0];
11489         acumRight += +v[1];
11490         animateValue.push([0, 0]);
11491       });
11492       var acum = acumRight>acumLeft? acumRight:acumLeft;
11493       n.setData('width', fixedDim);
11494       if(animate) {
11495         n.setData('height', acum * height / maxValue, 'end');
11496         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11497           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11498         }), 'end');
11499         var dimArray = n.getData('dimArray');
11500         if(!dimArray) {
11501           n.setData('dimArray', animateValue);
11502         }
11503       } else {
11504         n.setData('height', acum * height / maxValue);
11505         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11506           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11507         }));
11508       }
11509     });
11510   }
11511 });
11512
11513 /*
11514  * File: Options.BarChart.js
11515  *
11516 */
11517
11518 /*
11519   Object: Options.BarChart
11520   
11521   <BarChart> options. 
11522   Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11523   
11524   Syntax:
11525   
11526   (start code js)
11527
11528   Options.BarChart = {
11529     animate: true,
11530     labelOffset: 3,
11531     barsOffset: 0,
11532     type: 'stacked',
11533     hoveredColor: '#9fd4ff',
11534     orientation: 'horizontal',
11535     showAggregates: true,
11536     showLabels: true
11537   };
11538   
11539   (end code)
11540   
11541   Example:
11542   
11543   (start code js)
11544
11545   var barChart = new $jit.BarChart({
11546     animate: true,
11547     barsOffset: 10,
11548     type: 'stacked:gradient'
11549   });
11550   
11551   (end code)
11552
11553   Parameters:
11554   
11555   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11556   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11557   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11558   barsOffset - (number) Default's *0*. Separation between bars.
11559   type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11560   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11561   orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11562   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11563   showLabels - (boolean) Default's *true*. Display the name of the slots.
11564   
11565 */
11566
11567 Options.BarChart = {
11568   $extend: true,
11569   
11570   animate: true,
11571   type: 'stacked', //stacked, grouped, : gradient
11572   labelOffset: 3, //label offset
11573   barsOffset: 0, //distance between bars
11574   nodeCount: 0, //number of bars
11575   hoveredColor: '#9fd4ff',
11576   background: false,
11577   renderBackground: false,
11578   orientation: 'horizontal',
11579   showAggregates: true,
11580   showLabels: true,
11581   Ticks: {
11582         enable: false,
11583         segments: 4,
11584         color: '#000000'
11585   },
11586   Tips: {
11587     enable: false,
11588     onShow: $.empty,
11589     onHide: $.empty
11590   },
11591   Events: {
11592     enable: false,
11593     onClick: $.empty
11594   }
11595 };
11596
11597 /*
11598  * File: BarChart.js
11599  *
11600 */
11601
11602 $jit.ST.Plot.NodeTypes.implement({
11603   'barchart-stacked' : {
11604     'render' : function(node, canvas) {
11605       var pos = node.pos.getc(true), 
11606           width = node.getData('width'),
11607           height = node.getData('height'),
11608           algnPos = this.getAlignedPos(pos, width, height),
11609           x = algnPos.x, y = algnPos.y,
11610           dimArray = node.getData('dimArray'),
11611           valueArray = node.getData('valueArray'),
11612           stringArray = node.getData('stringArray'),
11613           linkArray = node.getData('linkArray'),
11614           gvl = node.getData('gvl'),
11615           colorArray = node.getData('colorArray'),
11616           colorLength = colorArray.length,
11617           nodeCount = node.getData('nodeCount');
11618       var ctx = canvas.getCtx(),
11619           canvasSize = canvas.getSize(),
11620           opt = {},
11621           border = node.getData('border'),
11622           gradient = node.getData('gradient'),
11623           config = node.getData('config'),
11624           horz = config.orientation == 'horizontal',
11625           aggregates = config.showAggregates,
11626           showLabels = config.showLabels,
11627           label = config.Label,
11628           margin = config.Margin;
11629           
11630           
11631       if (colorArray && dimArray && stringArray) {
11632         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11633                 acum += (dimArray[i] || 0);
11634         }
11635       }
11636       
11637        //drop shadow 
11638        if(config.shadow.enable) {
11639        shadowThickness = config.shadow.size;
11640        ctx.fillStyle = "rgba(0,0,0,.2)";
11641           if(horz) {
11642             ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11643           } else {
11644             ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11645           }
11646        }
11647        
11648       if (colorArray && dimArray && stringArray) {
11649         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11650           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11651           if(gradient) {
11652             var linear;
11653             
11654
11655           
11656             if(horz) {
11657               linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y, 
11658                   x + acum + dimArray[i]/2, y + height);
11659             } else {
11660               linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2, 
11661                   x + width, y - acum- dimArray[i]/2);
11662             }
11663             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11664                 function(v) { return (v * 0.8) >> 0; }));
11665             linear.addColorStop(0, color);
11666             linear.addColorStop(0.3, colorArray[i % colorLength]);
11667             linear.addColorStop(0.7, colorArray[i % colorLength]);
11668             linear.addColorStop(1, color);
11669             ctx.fillStyle = linear;
11670           }
11671           if(horz) {
11672             ctx.fillRect(x + acum, y, dimArray[i], height);
11673           } else {
11674             ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
11675           }
11676           if(border && border.name == stringArray[i]) {
11677             opt.acum = acum;
11678             opt.dimValue = dimArray[i];
11679           }
11680           acum += (dimArray[i] || 0);
11681           valAcum += (valueArray[i] || 0);
11682         }
11683         if(border) {
11684           ctx.save();
11685           ctx.lineWidth = 2;
11686           ctx.strokeStyle = border.color;
11687           if(horz) {
11688             ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11689           } else {
11690             ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11691           }
11692           ctx.restore();
11693         }
11694         if(label.type == 'Native') {
11695           ctx.save();
11696           ctx.fillStyle = ctx.strokeStyle = label.color;
11697           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11698           ctx.textBaseline = 'middle';
11699                         if(gvl) {
11700                                 acumValueLabel = gvl;
11701                         } else {
11702                                 acumValueLabel = valAcum;
11703                         }
11704           if(aggregates(node.name, valAcum)) {
11705             if(!horz) {
11706                           ctx.textAlign = 'center';
11707                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11708                           //background box
11709                           ctx.save();
11710                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11711                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11712                                  (label ? label.size + config.labelOffset : 0));
11713                           mtxt = ctx.measureText(acumValueLabel);
11714                           boxWidth = mtxt.width+10;
11715                           inset = 10;
11716                           boxHeight = label.size+6;
11717                           
11718                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11719                                 bottomPadding = acum - config.labelOffset - boxHeight;
11720                           } else {
11721                                 bottomPadding = acum + config.labelOffset + inset;
11722                           }
11723                         
11724                         
11725                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11726                           cornerRadius = 4;     
11727                           boxX = -inset/2;
11728                           boxY = -boxHeight/2;
11729                           
11730                           ctx.rotate(0 * Math.PI / 180);
11731                           ctx.fillStyle = "rgba(255,255,255,.8)";
11732                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11733                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11734                           }
11735                           //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11736                           ctx.fillStyle = ctx.strokeStyle = label.color;
11737                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11738                           ctx.restore();
11739
11740             }
11741           }
11742           if(showLabels(node.name, valAcum, node)) {
11743             if(horz) {
11744
11745
11746                 //background box
11747                 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11748                                 inset = 10;
11749                                 
11750                                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11751                 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11752                 boxWidth = mtxt.width+10;
11753                 inset = 10;
11754                 
11755                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11756                         leftPadding = acum - config.labelOffset - boxWidth - inset;
11757                 } else {
11758                         leftPadding = acum + config.labelOffset;
11759                 }
11760                 
11761                 
11762                                 ctx.textAlign = 'left';
11763                                 ctx.translate(x + inset + leftPadding, y + height/2);
11764                                 boxHeight = label.size+6;
11765                                 boxX = -inset/2;
11766                                 boxY = -boxHeight/2;
11767                                 ctx.fillStyle = "rgba(255,255,255,.8)";
11768                                 cornerRadius = 4;
11769                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {  
11770                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11771                                 }
11772                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11773                                 
11774                           ctx.fillStyle = label.color;
11775               ctx.rotate(0 * Math.PI / 180);
11776               ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11777
11778
11779             } else {
11780               //if the number of nodes greater than 8 rotate labels 45 degrees
11781               if(nodeCount > 8) {
11782                                 ctx.textAlign = 'left';
11783                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11784                                 ctx.rotate(45* Math.PI / 180);
11785                                 ctx.fillText(node.name, 0, 0);
11786                           } else {
11787                                 ctx.textAlign = 'center';
11788                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11789                           }
11790             }
11791           }
11792           ctx.restore();
11793         }
11794       }
11795     },
11796     'contains': function(node, mpos) {
11797       var pos = node.pos.getc(true), 
11798           width = node.getData('width'),
11799           height = node.getData('height'),
11800           algnPos = this.getAlignedPos(pos, width, height),
11801           x = algnPos.x, y = algnPos.y,
11802           dimArray = node.getData('dimArray'),
11803           config = node.getData('config'),
11804           rx = mpos.x - x,
11805           horz = config.orientation == 'horizontal';
11806       //bounding box check
11807       if(horz) {
11808         if(mpos.x < x || mpos.x > x + width
11809             || mpos.y > y + height || mpos.y < y) {
11810             return false;
11811           }
11812       } else {
11813         if(mpos.x < x || mpos.x > x + width
11814             || mpos.y > y || mpos.y < y - height) {
11815             return false;
11816           }
11817       }
11818       //deep check
11819       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11820         var dimi = dimArray[i];
11821                 var url = Url.decode(node.getData('linkArray')[i]);
11822         if(horz) {
11823           acum += dimi;
11824           var intersec = acum;
11825           if(mpos.x <= intersec) {
11826             return {
11827               'name': node.getData('stringArray')[i],
11828               'color': node.getData('colorArray')[i],
11829               'value': node.getData('valueArray')[i],
11830               'valuelabel': node.getData('valuelabelArray')[i],
11831                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11832                           'link': url,
11833               'label': node.name
11834             };
11835           }
11836         } else {
11837           acum -= dimi;
11838           var intersec = acum;
11839           if(mpos.y >= intersec) {
11840             return {
11841               'name': node.getData('stringArray')[i],
11842               'color': node.getData('colorArray')[i],
11843               'value': node.getData('valueArray')[i],
11844                           'valuelabel': node.getData('valuelabelArray')[i],
11845                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11846               'link': url,
11847               'label': node.name
11848             };
11849           }
11850         }
11851       }
11852       return false;
11853     }
11854   },
11855   'barchart-grouped' : {
11856     'render' : function(node, canvas) {
11857       var pos = node.pos.getc(true), 
11858           width = node.getData('width'),
11859           height = node.getData('height'),
11860           algnPos = this.getAlignedPos(pos, width, height),
11861           x = algnPos.x, y = algnPos.y,
11862           dimArray = node.getData('dimArray'),
11863           valueArray = node.getData('valueArray'),
11864           valuelabelArray = node.getData('valuelabelArray'),
11865           linkArray = node.getData('linkArray'),
11866           valueLength = valueArray.length,
11867           colorArray = node.getData('colorArray'),
11868           colorLength = colorArray.length,
11869           stringArray = node.getData('stringArray'); 
11870
11871       var ctx = canvas.getCtx(),
11872           canvasSize = canvas.getSize(),
11873           opt = {},
11874           border = node.getData('border'),
11875           gradient = node.getData('gradient'),
11876           config = node.getData('config'),
11877           horz = config.orientation == 'horizontal',
11878           aggregates = config.showAggregates,
11879           showLabels = config.showLabels,
11880           label = config.Label,
11881           shadow = config.shadow,
11882           margin = config.Margin,
11883           fixedDim = (horz? height : width) / valueLength;
11884       
11885       //drop shadow
11886       
11887        maxValue = Math.max.apply(null, dimArray);
11888        
11889        
11890           
11891            ctx.fillStyle = "rgba(0,0,0,.2)";
11892       if (colorArray && dimArray && stringArray && shadow.enable) {
11893                  shadowThickness = shadow.size;
11894
11895         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11896                 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11897                 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11898                 if(horz) {
11899                                     
11900                         ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11901                                         
11902                 } else {
11903                         
11904                         if(i == 0) {
11905                                 if(nextBar && nextBar > dimArray[i]) {
11906                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11907                                 } else if (nextBar && nextBar < dimArray[i]){
11908                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11909                                 } else {
11910                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11911                                 }
11912                         } else if (i> 0 && i<l-1) {
11913                                 if(nextBar && nextBar > dimArray[i]) {
11914                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11915                                 } else if (nextBar && nextBar < dimArray[i]){
11916                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11917                                 } else {
11918                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11919                                 }
11920                         } else if (i == l-1) {
11921                                 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11922                         }
11923                         
11924                         
11925                 }
11926         }
11927
11928       } 
11929                         
11930       
11931       if (colorArray && dimArray && stringArray) {
11932         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11933           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11934           if(gradient) {
11935             var linear;
11936             if(horz) {
11937               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
11938                   x + dimArray[i]/2, y + fixedDim * (i + 1));
11939             } else {
11940               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
11941                   x + fixedDim * (i + 1), y - dimArray[i]/2);
11942             }
11943             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11944                 function(v) { return (v * 0.8) >> 0; }));
11945             linear.addColorStop(0, color);
11946             linear.addColorStop(0.3, colorArray[i % colorLength]);
11947             linear.addColorStop(0.7, colorArray[i % colorLength]);
11948             linear.addColorStop(1, color);
11949             ctx.fillStyle = linear;
11950           }
11951           if(horz) {
11952             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11953           } else {
11954             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11955           }
11956           if(border && border.name == stringArray[i]) {
11957             opt.acum = fixedDim * i;
11958             opt.dimValue = dimArray[i];
11959           }
11960           acum += (dimArray[i] || 0);
11961           valAcum += (valueArray[i] || 0);
11962                   ctx.fillStyle = ctx.strokeStyle = label.color;
11963           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11964           
11965           inset = 10;
11966                   if(aggregates(node.name, valAcum) && label.type == 'Native') {
11967                                 if(valuelabelArray[i]) {
11968                                         acumValueLabel = valuelabelArray[i];
11969                                 } else {
11970                                         acumValueLabel = valueArray[i];
11971                                 }
11972                            if(horz) {
11973                                   ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11974                                   ctx.textAlign = 'left';
11975                                   ctx.textBaseline = 'top';
11976                                   ctx.fillStyle = "rgba(255,255,255,.8)";
11977                                   //background box
11978                                   gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
11979                                   mtxt = ctx.measureText(acumValueLabel);
11980                                   boxWidth = mtxt.width+10;
11981                                   
11982                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11983                                         leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
11984                                   } else {
11985                                         leftPadding = dimArray[i] + config.labelOffset + inset;
11986                                   }
11987                               boxHeight = label.size+6;
11988                                   boxX = x + leftPadding;
11989                                   boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
11990                                   cornerRadius = 4;     
11991                                   
11992                                   
11993                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11994                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11995                                   }
11996                                 //  $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11997                                   
11998                                   ctx.fillStyle = ctx.strokeStyle = label.color;
11999                                   ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12000                                   
12001
12002                                         
12003                                         
12004                                 } else {
12005                                   
12006                                         ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12007                                         ctx.save();
12008                                         ctx.textAlign = 'center';
12009                                         
12010                                         //background box
12011                                         gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12012                                          (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12013                                          (label ? label.size + config.labelOffset : 0));
12014                                         
12015                                         mtxt = ctx.measureText(acumValueLabel);
12016                                         boxWidth = mtxt.width+10;
12017                                         boxHeight = label.size+6;
12018                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12019                                                 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12020                                         } else {
12021                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12022                                         }
12023                                                                                                 
12024                                         
12025                                         ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12026                                         
12027                                         boxX = -boxWidth/2;
12028                                         boxY = -boxHeight/2;
12029                                         ctx.fillStyle = "rgba(255,255,255,.8)";
12030                                         
12031                                         cornerRadius = 4;       
12032
12033                                         //ctx.rotate(270* Math.PI / 180);
12034                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12035                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12036                                         }
12037                                         //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12038                                         
12039                                         ctx.fillStyle = ctx.strokeStyle = label.color;
12040                                         ctx.fillText(acumValueLabel, 0,0);
12041                                         ctx.restore();
12042
12043                                 }
12044                         }
12045         }
12046         if(border) {
12047           ctx.save();
12048           ctx.lineWidth = 2;
12049           ctx.strokeStyle = border.color;
12050           if(horz) {
12051             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12052           } else {
12053             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12054           }
12055           ctx.restore();
12056         }
12057         if(label.type == 'Native') {
12058           ctx.save();
12059           ctx.fillStyle = ctx.strokeStyle = label.color;
12060           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12061           ctx.textBaseline = 'middle';
12062
12063           if(showLabels(node.name, valAcum, node)) {
12064             if(horz) {
12065               ctx.textAlign = 'center';
12066               ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12067               ctx.rotate(Math.PI / 2);
12068               ctx.fillText(node.name, 0, 0);
12069             } else {
12070               ctx.textAlign = 'center';
12071               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12072             }
12073           }
12074           ctx.restore();
12075         }
12076       }
12077     },
12078     'contains': function(node, mpos) {
12079       var pos = node.pos.getc(true), 
12080           width = node.getData('width'),
12081           height = node.getData('height'),
12082           algnPos = this.getAlignedPos(pos, width, height),
12083           x = algnPos.x, y = algnPos.y,
12084           dimArray = node.getData('dimArray'),
12085           len = dimArray.length,
12086           config = node.getData('config'),
12087           rx = mpos.x - x,
12088           horz = config.orientation == 'horizontal',
12089           fixedDim = (horz? height : width) / len;
12090       //bounding box check
12091       if(horz) {
12092         if(mpos.x < x || mpos.x > x + width
12093             || mpos.y > y + height || mpos.y < y) {
12094             return false;
12095           }
12096       } else {
12097         if(mpos.x < x || mpos.x > x + width
12098             || mpos.y > y || mpos.y < y - height) {
12099             return false;
12100           }
12101       }
12102       //deep check
12103       for(var i=0, l=dimArray.length; i<l; i++) {
12104         var dimi = dimArray[i];
12105                 var url = Url.decode(node.getData('linkArray')[i]);
12106         if(horz) {
12107           var limit = y + fixedDim * i;
12108           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12109             return {
12110               'name': node.getData('stringArray')[i],
12111               'color': node.getData('colorArray')[i],
12112               'value': node.getData('valueArray')[i],
12113                           'valuelabel': node.getData('valuelabelArray')[i],
12114               'title': node.getData('titleArray')[i],
12115                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12116               'link': url,
12117               'label': node.name
12118             };
12119           }
12120         } else {
12121           var limit = x + fixedDim * i;
12122           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12123             return {
12124               'name': node.getData('stringArray')[i],
12125               'color': node.getData('colorArray')[i],
12126               'value': node.getData('valueArray')[i],
12127                           'valuelabel': node.getData('valuelabelArray')[i],
12128               'title': node.getData('titleArray')[i],
12129                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12130               'link': url,
12131               'label': node.name
12132             };
12133           }
12134         }
12135       }
12136       return false;
12137     }
12138   },
12139   'barchart-basic' : {
12140     'render' : function(node, canvas) {
12141       var pos = node.pos.getc(true), 
12142           width = node.getData('width'),
12143           height = node.getData('height'),
12144           algnPos = this.getAlignedPos(pos, width, height),
12145           x = algnPos.x, y = algnPos.y,
12146           dimArray = node.getData('dimArray'),
12147           valueArray = node.getData('valueArray'),
12148                   valuelabelArray = node.getData('valuelabelArray'),
12149           linkArray = node.getData('linkArray'),
12150           valueLength = valueArray.length,
12151           colorArray = node.getData('colorMono'),
12152           colorLength = colorArray.length,
12153           stringArray = node.getData('stringArray'); 
12154
12155       var ctx = canvas.getCtx(),
12156           canvasSize = canvas.getSize(),
12157           opt = {},
12158           border = node.getData('border'),
12159           gradient = node.getData('gradient'),
12160           config = node.getData('config'),
12161           horz = config.orientation == 'horizontal',
12162           aggregates = config.showAggregates,
12163           showLabels = config.showLabels,
12164           label = config.Label,
12165           fixedDim = (horz? height : width) / valueLength,
12166           margin = config.Margin;
12167       
12168       if (colorArray && dimArray && stringArray) {
12169         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12170           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12171
12172           if(gradient) {
12173             var linear;
12174             if(horz) {
12175               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12176                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12177             } else {
12178               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12179                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12180             }
12181             //drop shadow 
12182            if(config.shadow.size) {
12183                   shadowThickness = config.shadow.size;
12184                   ctx.fillStyle = "rgba(0,0,0,.2)";
12185                   if(horz) {
12186                     ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12187                   } else {
12188                     ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12189                   }
12190           }
12191           
12192             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12193                 function(v) { return (v * 0.8) >> 0; }));
12194             linear.addColorStop(0, color);
12195             linear.addColorStop(0.3, colorArray[i % colorLength]);
12196             linear.addColorStop(0.7, colorArray[i % colorLength]);
12197             linear.addColorStop(1, color);
12198             ctx.fillStyle = linear;
12199           }
12200           if(horz) {
12201             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12202           } else {
12203             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12204           }
12205           if(border && border.name == stringArray[i]) {
12206             opt.acum = fixedDim * i;
12207             opt.dimValue = dimArray[i];
12208           }
12209           acum += (dimArray[i] || 0);
12210           valAcum += (valueArray[i] || 0);
12211                   
12212               if(label.type == 'Native') {
12213                                  ctx.fillStyle = ctx.strokeStyle = label.color;
12214                                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12215                                  if(aggregates(node.name, valAcum)) {
12216                                         if(valuelabelArray[i]) {
12217                                                 acumValueLabel = valuelabelArray[i];
12218                                           } else {
12219                                                 acumValueLabel = valueArray[i];
12220                                           }
12221                                          if(!horz) {
12222                                           ctx.textAlign = 'center';
12223                                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12224                                           //background box
12225                                           ctx.save();
12226                                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12227                                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12228                                                  (label ? label.size + config.labelOffset : 0));
12229                           mtxt = ctx.measureText(acumValueLabel);
12230                                           boxWidth = mtxt.width+10;
12231                                           inset = 10;
12232                                           boxHeight = label.size+6;
12233                                           
12234                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12235                                                 bottomPadding = dimArray[i] - config.labelOffset  - inset;
12236                                           } else {
12237                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12238                                           }
12239                                         
12240                                         
12241                                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12242                                           cornerRadius = 4;     
12243                                           boxX = -inset/2;
12244                                           boxY = -boxHeight/2;
12245                                           
12246                                           //ctx.rotate(270* Math.PI / 180);
12247                                           ctx.fillStyle = "rgba(255,255,255,.6)";
12248                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12249                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12250                                           }
12251                                          // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12252                                           ctx.fillStyle = ctx.strokeStyle = label.color;
12253                                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12254                                           ctx.restore();
12255                                         }
12256                                 }
12257                 }
12258         }
12259         if(border) {
12260           ctx.save();
12261           ctx.lineWidth = 2;
12262           ctx.strokeStyle = border.color;
12263           if(horz) {
12264             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12265           } else {
12266             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12267           }
12268           ctx.restore();
12269         }
12270         if(label.type == 'Native') {
12271           ctx.save();
12272           ctx.fillStyle = ctx.strokeStyle = label.color;
12273           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12274           ctx.textBaseline = 'middle';
12275           if(showLabels(node.name, valAcum, node)) {
12276             if(horz) {
12277                 
12278                 //background box
12279                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12280                 mtxt = ctx.measureText(node.name + ": " + valAcum);
12281                 boxWidth = mtxt.width+10;
12282                 inset = 10;
12283                 
12284                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12285                         leftPadding = acum - config.labelOffset - boxWidth - inset;
12286                 } else {
12287                         leftPadding = acum + config.labelOffset;
12288                 }
12289                 
12290                                 
12291                                 ctx.textAlign = 'left';
12292                                 ctx.translate(x + inset + leftPadding, y + height/2);
12293                                 boxHeight = label.size+6;
12294                                 boxX = -inset/2;
12295                                 boxY = -boxHeight/2;
12296                                 ctx.fillStyle = "rgba(255,255,255,.8)";
12297                                 
12298                                 cornerRadius = 4;       
12299                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12300                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12301                                 }
12302                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12303                 
12304                                 
12305                                 ctx.fillStyle = label.color;
12306                                 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12307
12308             } else {
12309               
12310                           if(stringArray.length > 8) {
12311                                 ctx.textAlign = 'left';
12312                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12313                                 ctx.rotate(45* Math.PI / 180);
12314                                 ctx.fillText(node.name, 0, 0);
12315                           } else {
12316                                 ctx.textAlign = 'center';
12317                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12318                           }
12319               
12320             }
12321           }
12322           ctx.restore();
12323         }
12324       }
12325     },
12326     'contains': function(node, mpos) {
12327       var pos = node.pos.getc(true), 
12328           width = node.getData('width'),
12329           height = node.getData('height'),
12330           config = node.getData('config'),
12331           algnPos = this.getAlignedPos(pos, width, height),
12332           x = algnPos.x, y = algnPos.y ,
12333           dimArray = node.getData('dimArray'),
12334           len = dimArray.length,
12335           rx = mpos.x - x,
12336           horz = config.orientation == 'horizontal',
12337           fixedDim = (horz? height : width) / len;
12338
12339       //bounding box check
12340       if(horz) {
12341         if(mpos.x < x || mpos.x > x + width
12342             || mpos.y > y + height || mpos.y < y) {
12343             return false;
12344           }
12345       } else {
12346         if(mpos.x < x || mpos.x > x + width
12347             || mpos.y > y || mpos.y < y - height) {
12348             return false;
12349           }
12350       }
12351       //deep check
12352       for(var i=0, l=dimArray.length; i<l; i++) {
12353         var dimi = dimArray[i];
12354                 var url = Url.decode(node.getData('linkArray')[i]);
12355         if(horz) {
12356           var limit = y + fixedDim * i;
12357           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12358             return {
12359               'name': node.getData('stringArray')[i],
12360               'color': node.getData('colorArray')[i],
12361               'value': node.getData('valueArray')[i],
12362                           'valuelabel': node.getData('valuelabelArray')[i],
12363                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12364               'link': url,
12365               'label': node.name
12366             };
12367           }
12368         } else {
12369           var limit = x + fixedDim * i;
12370           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12371             return {
12372               'name': node.getData('stringArray')[i],
12373               'color': node.getData('colorArray')[i],
12374               'value': node.getData('valueArray')[i],
12375                           'valuelabel': node.getData('valuelabelArray')[i],
12376                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12377               'link': url,
12378               'label': node.name
12379             };
12380           }
12381         }
12382       }
12383       return false;
12384     }
12385   }
12386 });
12387
12388 /*
12389   Class: BarChart
12390   
12391   A visualization that displays stacked bar charts.
12392   
12393   Constructor Options:
12394   
12395   See <Options.BarChart>.
12396
12397 */
12398 $jit.BarChart = new Class({
12399   st: null,
12400   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12401   selected: {},
12402   busy: false,
12403   
12404   initialize: function(opt) {
12405     this.controller = this.config = 
12406       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12407         Label: { type: 'Native' }
12408       }, opt);
12409     //set functions for showLabels and showAggregates
12410     var showLabels = this.config.showLabels,
12411         typeLabels = $.type(showLabels),
12412         showAggregates = this.config.showAggregates,
12413         typeAggregates = $.type(showAggregates);
12414     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12415     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12416     Options.Fx.clearCanvas = false;
12417     this.initializeViz();
12418   },
12419   
12420   initializeViz: function() {
12421     var config = this.config, that = this;
12422     var nodeType = config.type.split(":")[0],
12423         horz = config.orientation == 'horizontal',
12424         nodeLabels = {};
12425     var st = new $jit.ST({
12426       injectInto: config.injectInto,
12427       orientation: horz? 'left' : 'bottom',
12428       background: config.background,
12429       renderBackground: config.renderBackground,
12430       backgroundColor: config.backgroundColor,
12431       colorStop1: config.colorStop1,
12432       colorStop2: config.colorStop2,
12433       levelDistance: 0,
12434       nodeCount: config.nodeCount,
12435       siblingOffset: config.barsOffset,
12436       subtreeOffset: 0,
12437       withLabels: config.Label.type != 'Native',      
12438       useCanvas: config.useCanvas,
12439       Label: {
12440         type: config.Label.type
12441       },
12442       Node: {
12443         overridable: true,
12444         type: 'barchart-' + nodeType,
12445         align: 'left',
12446         width: 1,
12447         height: 1
12448       },
12449       Edge: {
12450         type: 'none'
12451       },
12452       Tips: {
12453         enable: config.Tips.enable,
12454         type: 'Native',
12455         force: true,
12456         onShow: function(tip, node, contains) {
12457           var elem = contains;
12458           config.Tips.onShow(tip, elem, node);
12459                           if(elem.link != 'undefined' && elem.link != '') {
12460                                 document.body.style.cursor = 'pointer';
12461                           }
12462         },
12463                 onHide: function(call) {
12464                         document.body.style.cursor = 'default';
12465
12466         }
12467       },
12468       Events: {
12469         enable: true,
12470         type: 'Native',
12471         onClick: function(node, eventInfo, evt) {
12472           if(!config.Events.enable) return;
12473           var elem = eventInfo.getContains();
12474           config.Events.onClick(elem, eventInfo, evt);
12475         },
12476         onMouseMove: function(node, eventInfo, evt) {
12477           if(!config.hoveredColor) return;
12478           if(node) {
12479             var elem = eventInfo.getContains();
12480             that.select(node.id, elem.name, elem.index);
12481           } else {
12482             that.select(false, false, false);
12483           }
12484         }
12485       },
12486       onCreateLabel: function(domElement, node) {
12487         var labelConf = config.Label,
12488             valueArray = node.getData('valueArray'),
12489             acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12490             grouped = config.type.split(':')[0] == 'grouped',
12491             horz = config.orientation == 'horizontal';
12492         var nlbs = {
12493           wrapper: document.createElement('div'),
12494           aggregate: document.createElement('div'),
12495           label: document.createElement('div')
12496         };
12497         
12498         var wrapper = nlbs.wrapper,
12499             label = nlbs.label,
12500             aggregate = nlbs.aggregate,
12501             wrapperStyle = wrapper.style,
12502             labelStyle = label.style,
12503             aggregateStyle = aggregate.style;
12504         //store node labels
12505         nodeLabels[node.id] = nlbs;
12506         //append labels
12507         wrapper.appendChild(label);
12508         wrapper.appendChild(aggregate);
12509         if(!config.showLabels(node.name, acum, node)) {
12510           labelStyle.display = 'none';
12511         }
12512         if(!config.showAggregates(node.name, acum, node)) {
12513           aggregateStyle.display = 'none';
12514         }
12515         wrapperStyle.position = 'relative';
12516         wrapperStyle.overflow = 'visible';
12517         wrapperStyle.fontSize = labelConf.size + 'px';
12518         wrapperStyle.fontFamily = labelConf.family;
12519         wrapperStyle.color = labelConf.color;
12520         wrapperStyle.textAlign = 'center';
12521         aggregateStyle.position = labelStyle.position = 'absolute';
12522         
12523         domElement.style.width = node.getData('width') + 'px';
12524         domElement.style.height = node.getData('height') + 'px';
12525         aggregateStyle.left = "0px";
12526         labelStyle.left =  config.labelOffset + 'px';
12527         labelStyle.whiteSpace =  "nowrap";
12528                 label.innerHTML = node.name;       
12529         
12530         domElement.appendChild(wrapper);
12531       },
12532       onPlaceLabel: function(domElement, node) {
12533         if(!nodeLabels[node.id]) return;
12534         var labels = nodeLabels[node.id],
12535             wrapperStyle = labels.wrapper.style,
12536             labelStyle = labels.label.style,
12537             aggregateStyle = labels.aggregate.style,
12538             grouped = config.type.split(':')[0] == 'grouped',
12539             horz = config.orientation == 'horizontal',
12540             dimArray = node.getData('dimArray'),
12541             valArray = node.getData('valueArray'),
12542             nodeCount = node.getData('nodeCount'),
12543             valueLength = valArray.length;
12544             valuelabelArray = node.getData('valuelabelArray'),
12545             stringArray = node.getData('stringArray'),
12546             width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12547             height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12548             font = parseInt(wrapperStyle.fontSize, 10),
12549             domStyle = domElement.style,
12550             fixedDim = (horz? height : width) / valueLength;
12551             
12552         
12553         if(dimArray && valArray) {
12554           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12555           
12556           aggregateStyle.width = width  - config.labelOffset + "px";
12557           for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12558             if(dimArray[i] > 0) {
12559               acum+= valArray[i];
12560             }
12561           }
12562           if(config.showLabels(node.name, acum, node)) {
12563             labelStyle.display = '';
12564           } else {
12565             labelStyle.display = 'none';
12566           }
12567           if(config.showAggregates(node.name, acum, node)) {
12568             aggregateStyle.display = '';
12569           } else {
12570             aggregateStyle.display = 'none';
12571           }
12572           if(config.orientation == 'horizontal') {
12573             aggregateStyle.textAlign = 'right';
12574             labelStyle.textAlign = 'left';
12575             labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12576             aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12577             domElement.style.height = wrapperStyle.height = height + 'px';
12578           } else {
12579             aggregateStyle.top = (-font - config.labelOffset) + 'px';
12580             labelStyle.top = (config.labelOffset + height) + 'px';
12581             domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12582             domElement.style.height = wrapperStyle.height = height + 'px';
12583             if(stringArray.length > 8) {
12584                 labels.label.className = "rotatedLabelReverse";
12585                 labelStyle.textAlign = "left";
12586                 labelStyle.top = config.labelOffset + height + width/2 + "px";
12587             }
12588           }
12589           
12590           if(horz) {
12591
12592                         labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12593                         labels.aggregate.innerHTML = "";
12594
12595           } else {
12596                 
12597                         if(grouped) {
12598                                 maxValue = Math.max.apply(null,dimArray);
12599                                 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12600                                         valueLabelDim = 50;
12601                                         valueLabel = document.createElement('div');
12602                                         valueLabel.innerHTML =  valuelabelArray[i];
12603 //                                      valueLabel.class = "rotatedLabel";
12604                                         valueLabel.className = "rotatedLabel";
12605                                         valueLabel.style.position = "absolute";
12606                                                 valueLabel.style.textAlign = "left";
12607                                                 valueLabel.style.verticalAlign = "middle";
12608                                         valueLabel.style.height = valueLabelDim + "px";
12609                                         valueLabel.style.width = valueLabelDim + "px";
12610                                         valueLabel.style.top =  (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12611                                         valueLabel.style.left = (fixedDim * i) + "px";
12612                                         labels.wrapper.appendChild(valueLabel);
12613                                 }
12614                         } else {
12615                                 labels.aggregate.innerHTML = acum;
12616                         }
12617           }
12618         }
12619       }
12620     });
12621
12622     var size = st.canvas.getSize(),
12623         l = config.nodeCount,
12624         margin = config.Margin;
12625         title = config.Title;
12626         subtitle = config.Subtitle,
12627         grouped = config.type.split(':')[0] == 'grouped',
12628         margin = config.Margin,
12629         ticks = config.Ticks,
12630         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12631         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12632         horz = config.orientation == 'horizontal',
12633         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12634         fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12635         whiteSpace = size.width - (marginWidth + (fixedDim * l));
12636         //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
12637         if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12638         location.reload();
12639         //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12640         if(!grouped && !horz) {
12641                 st.config.siblingOffset = whiteSpace/(l+1);
12642         }
12643         
12644         
12645         
12646         //Bars offset
12647     if(horz) {
12648       st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);    
12649           if(config.Ticks.enable)       {
12650                 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;
12651           } else {
12652                 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12653           }
12654     } else {
12655       st.config.offsetY = -size.height/2 + margin.bottom 
12656         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12657           if(config.Ticks.enable)       {
12658                 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12659           } else {
12660                 st.config.offsetX = (margin.right - margin.left)/2;
12661           }
12662     }
12663     this.st = st;
12664     this.canvas = this.st.canvas;
12665   },
12666   
12667  
12668   
12669   renderTitle: function() {
12670         var canvas = this.canvas,
12671         size = canvas.getSize(),
12672         config = this.config,
12673         margin = config.Margin,
12674         label = config.Label,
12675         title = config.Title;
12676         ctx = canvas.getCtx();
12677         ctx.fillStyle = title.color;
12678         ctx.textAlign = 'left';
12679         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12680         if(label.type == 'Native') {
12681                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12682         }
12683   },  
12684   
12685   renderSubtitle: function() {
12686         var canvas = this.canvas,
12687         size = canvas.getSize(),
12688         config = this.config,
12689         margin = config.Margin,
12690         label = config.Label,
12691         subtitle = config.Subtitle,
12692         nodeCount = config.nodeCount,
12693         horz = config.orientation == 'horizontal' ? true : false,
12694         ctx = canvas.getCtx();
12695         ctx.fillStyle = title.color;
12696         ctx.textAlign = 'left';
12697         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12698         if(label.type == 'Native') {
12699                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12700         }
12701   },
12702   
12703   renderScrollNote: function() {
12704         var canvas = this.canvas,
12705         size = canvas.getSize(),
12706         config = this.config,
12707         margin = config.Margin,
12708         label = config.Label,
12709         note = config.ScrollNote;
12710         ctx = canvas.getCtx();
12711         ctx.fillStyle = title.color;
12712         title = config.Title;
12713         ctx.textAlign = 'center';
12714         ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12715         if(label.type == 'Native') {
12716                 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12717         }
12718   },  
12719   
12720   renderTicks: function() {
12721
12722         var canvas = this.canvas,
12723         size = canvas.getSize(),
12724         config = this.config,
12725         margin = config.Margin,
12726         ticks = config.Ticks,
12727         title = config.Title,
12728         subtitle = config.Subtitle,
12729         label = config.Label,
12730         shadow = config.shadow;
12731         horz = config.orientation == 'horizontal',
12732         maxValue = this.getMaxValue(),
12733         maxTickValue = Math.ceil(maxValue*.1)*10;
12734         if(maxTickValue == maxValue) {
12735                 var length = maxTickValue.toString().length;
12736                 maxTickValue = maxTickValue + parseInt(pad(1,length));
12737         }
12738         grouped = config.type.split(':')[0] == 'grouped',
12739         labelValue = 0,
12740         labelIncrement = maxTickValue/ticks.segments,
12741         ctx = canvas.getCtx();
12742         ctx.strokeStyle = ticks.color;
12743     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12744
12745         ctx.textAlign = 'center';
12746         ctx.textBaseline = 'middle';
12747         
12748         idLabel = canvas.id + "-label";
12749         labelDim = 100;
12750         container = document.getElementById(idLabel);
12751                   
12752                   
12753         if(horz) {
12754                 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12755                 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12756                 segmentLength = grid/ticks.segments;
12757                 ctx.fillStyle = ticks.color;
12758                 ctx.fillRect(axis,
12759                  (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12760                  size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12761                  1);
12762                 while(axis<=grid) {
12763                         ctx.fillStyle = ticks.color;
12764                         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);
12765                         ctx.fillRect(Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0) - (shadow.enable ? shadow.size : 0), 1, lineHeight + (shadow.enable ? shadow.size * 2: 0));
12766                         ctx.fillStyle = label.color;
12767                         
12768                         if(label.type == 'Native' && config.showLabels) {            
12769                  ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12770                         }
12771                         axis += segmentLength;
12772                         labelValue += labelIncrement;
12773                 }
12774         
12775         } else {
12776         
12777                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12778                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12779                 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)),
12780                 segmentLength = grid/ticks.segments;
12781                 ctx.fillStyle = ticks.color;
12782                 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));
12783
12784                 while(axis>=grid) {
12785                         ctx.save();
12786                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12787                         ctx.rotate(0 * Math.PI / 180 );
12788                         ctx.fillStyle = label.color;
12789                         if(config.showLabels) {
12790                                 if(label.type == 'Native') { 
12791                                         ctx.fillText(labelValue, 0, 0);
12792                                 } else {
12793                                         //html labels on y axis
12794                                         labelDiv = document.createElement('div');
12795                                         labelDiv.innerHTML = labelValue;
12796                                         labelDiv.className = "rotatedLabel";
12797 //                                      labelDiv.class = "rotatedLabel";
12798                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12799                                         labelDiv.style.left = margin.left + "px";
12800                                         labelDiv.style.width = labelDim + "px";
12801                                         labelDiv.style.height = labelDim + "px";
12802                                         labelDiv.style.textAlign = "center";
12803                                         labelDiv.style.verticalAlign = "middle";
12804                                         labelDiv.style.position = "absolute";
12805                                         container.appendChild(labelDiv);
12806                                 }
12807                         }
12808                         ctx.restore();
12809                         ctx.fillStyle = ticks.color;
12810                         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 );
12811                         htmlOrigin += segmentLength;
12812                         axis += segmentLength;
12813                         labelValue += labelIncrement;
12814                 }
12815         }
12816         
12817         
12818         
12819
12820   },
12821   
12822   renderBackground: function() {
12823                 var canvas = this.canvas,
12824                 config = this.config,
12825                 backgroundColor = config.backgroundColor,
12826                 size = canvas.getSize(),
12827                 ctx = canvas.getCtx();
12828             ctx.fillStyle = backgroundColor;
12829             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
12830   },
12831   
12832   clear: function() {
12833         var canvas = this.canvas;
12834         var ctx = canvas.getCtx(),
12835         size = canvas.getSize();
12836         ctx.fillStyle = "rgba(255,255,255,0)";
12837         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12838         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12839  },
12840   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
12841         var canvas = this.canvas,
12842         size = canvas.getSize(),
12843         config = this.config,
12844         orgHeight = size.height,
12845         margin = config.Margin,
12846         st = this.st,
12847         grouped = config.type.split(':')[0] == 'grouped',
12848         horz = config.orientation == 'horizontal',
12849                 ctx = canvas.getCtx();
12850         
12851         var newWindowWidth = document.body.offsetWidth;
12852         var diff = newWindowWidth - orgWindowWidth;     
12853         var newWidth = orgContainerDivWidth + (diff/cols);
12854         var scale = newWidth/orgContainerDivWidth;
12855         canvas.resize(newWidth,orgHeight);
12856         if(typeof FlashCanvas == "undefined") {
12857                 canvas.clear();
12858         } else {
12859                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12860         }
12861         if(horz) {
12862                 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12863         }
12864         
12865         this.loadJSON(json);
12866
12867         
12868         },
12869   /*
12870     Method: loadJSON
12871    
12872     Loads JSON data into the visualization. 
12873     
12874     Parameters:
12875     
12876     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>.
12877     
12878     Example:
12879     (start code js)
12880     var barChart = new $jit.BarChart(options);
12881     barChart.loadJSON(json);
12882     (end code)
12883  */  
12884   loadJSON: function(json) {
12885     if(this.busy) return;
12886     this.busy = true;
12887     
12888     var prefix = $.time(), 
12889         ch = [], 
12890         st = this.st,
12891         name = $.splat(json.label), 
12892         color = $.splat(json.color || this.colors),
12893         config = this.config,
12894         gradient = !!config.type.split(":")[1],
12895         renderBackground = config.renderBackground,
12896         animate = config.animate,
12897         ticks = config.Ticks,
12898         title = config.Title,
12899         note = config.ScrollNote,
12900         subtitle = config.Subtitle,
12901         horz = config.orientation == 'horizontal',
12902         that = this,
12903                 colorLength = color.length,
12904                 nameLength = name.length;
12905         groupTotalValue = 0;
12906     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12907         var val = values[i];
12908         var valArray = $.splat(val.values);
12909         groupTotalValue += parseFloat(valArray.sum());
12910     }
12911
12912     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12913       var val = values[i];
12914       var valArray = $.splat(values[i].values);
12915       var valuelabelArray = $.splat(values[i].valuelabels);
12916       var linkArray = $.splat(values[i].links);
12917       var titleArray = $.splat(values[i].titles);
12918       var barTotalValue = valArray.sum();
12919       var acum = 0;
12920       ch.push({
12921         'id': prefix + val.label,
12922         'name': val.label,
12923         
12924         'data': {
12925           'value': valArray,
12926           '$linkArray': linkArray,
12927                   '$gvl': val.gvaluelabel,
12928           '$titleArray': titleArray,
12929           '$valueArray': valArray,
12930           '$valuelabelArray': valuelabelArray,
12931           '$colorArray': color,
12932           '$colorMono': $.splat(color[i % colorLength]),
12933           '$stringArray': name,
12934           '$barTotalValue': barTotalValue,
12935           '$groupTotalValue': groupTotalValue,
12936           '$nodeCount': values.length,
12937           '$gradient': gradient,
12938           '$config': config
12939         },
12940         'children': []
12941       });
12942     }
12943     var root = {
12944       'id': prefix + '$root',
12945       'name': '',
12946       'data': {
12947         '$type': 'none',
12948         '$width': 1,
12949         '$height': 1
12950       },
12951       'children': ch
12952     };
12953     st.loadJSON(root);
12954     
12955     this.normalizeDims();
12956     
12957     if(renderBackground) {
12958                 this.renderBackground();
12959     }
12960         
12961         if(!animate && ticks.enable) {
12962                 this.renderTicks();
12963         }
12964         if(!animate && note.text) {
12965                 this.renderScrollNote();
12966         }
12967         if(!animate && title.text) {
12968                 this.renderTitle();
12969         }
12970         if(!animate && subtitle.text) {
12971                 this.renderSubtitle();
12972         }
12973
12974     st.compute();
12975     st.select(st.root);
12976     if(animate) {
12977       if(horz) {
12978         st.fx.animate({
12979           modes: ['node-property:width:dimArray'],
12980           duration:1500,
12981           onComplete: function() {
12982             that.busy = false;
12983           }
12984         });
12985       } else {
12986         st.fx.animate({
12987           modes: ['node-property:height:dimArray'],
12988           duration:1500,
12989           onComplete: function() {
12990             that.busy = false;
12991           }
12992         });
12993       }
12994     } else {
12995       this.busy = false;
12996     }
12997   },
12998   
12999   /*
13000     Method: updateJSON
13001    
13002     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.
13003     
13004     Parameters:
13005     
13006     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13007     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13008     
13009     Example:
13010     
13011     (start code js)
13012     barChart.updateJSON(json, {
13013       onComplete: function() {
13014         alert('update complete!');
13015       }
13016     });
13017     (end code)
13018  */  
13019   updateJSON: function(json, onComplete) {
13020     if(this.busy) return;
13021     this.busy = true;
13022     
13023     var st = this.st;
13024     var graph = st.graph;
13025     var values = json.values;
13026     var animate = this.config.animate;
13027     var that = this;
13028     var horz = this.config.orientation == 'horizontal';
13029     $.each(values, function(v) {
13030       var n = graph.getByName(v.label);
13031       if(n) {
13032         n.setData('valueArray', $.splat(v.values));
13033         if(json.label) {
13034           n.setData('stringArray', $.splat(json.label));
13035         }
13036       }
13037     });
13038     this.normalizeDims();
13039     st.compute();
13040     st.select(st.root);
13041     if(animate) {
13042       if(horz) {
13043         st.fx.animate({
13044           modes: ['node-property:width:dimArray'],
13045           duration:1500,
13046           onComplete: function() {
13047             that.busy = false;
13048             onComplete && onComplete.onComplete();
13049           }
13050         });
13051       } else {
13052         st.fx.animate({
13053           modes: ['node-property:height:dimArray'],
13054           duration:1500,
13055           onComplete: function() {
13056             that.busy = false;
13057             onComplete && onComplete.onComplete();
13058           }
13059         });
13060       }
13061     }
13062   },
13063   
13064   //adds the little brown bar when hovering the node
13065   select: function(id, name) {
13066
13067     if(!this.config.hoveredColor) return;
13068     var s = this.selected;
13069     if(s.id != id || s.name != name) {
13070       s.id = id;
13071       s.name = name;
13072       s.color = this.config.hoveredColor;
13073       this.st.graph.eachNode(function(n) {
13074         if(id == n.id) {
13075           n.setData('border', s);
13076         } else {
13077           n.setData('border', false);
13078         }
13079       });
13080       this.st.plot();
13081     }
13082   },
13083   
13084   /*
13085     Method: getLegend
13086    
13087     Returns an object containing as keys the legend names and as values hex strings with color values.
13088     
13089     Example:
13090     
13091     (start code js)
13092     var legend = barChart.getLegend();
13093     (end code)
13094   */  
13095   getLegend: function() {
13096     var legend = new Array();
13097     var name = new Array();
13098     var color = new Array();
13099     var n;
13100     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13101       n = adj.nodeTo;
13102     });
13103     var colors = n.getData('colorArray'),
13104         len = colors.length;
13105     $.each(n.getData('stringArray'), function(s, i) {
13106       color[i] = colors[i % len];
13107       name[i] = s;
13108     });
13109         legend['name'] = name;
13110         legend['color'] = color;
13111     return legend;
13112   },
13113   
13114   /*
13115     Method: getMaxValue
13116    
13117     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13118     
13119     Example:
13120     
13121     (start code js)
13122     var ans = barChart.getMaxValue();
13123     (end code)
13124     
13125     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13126     
13127     Example:
13128     
13129     (start code js)
13130     //will return 100 for all BarChart instances,
13131     //displaying all of them with the same scale
13132     $jit.BarChart.implement({
13133       'getMaxValue': function() {
13134         return 100;
13135       }
13136     });
13137     (end code)
13138     
13139   */  
13140   getMaxValue: function() {
13141     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13142     this.st.graph.eachNode(function(n) {
13143       var valArray = n.getData('valueArray'),
13144           acum = 0;
13145       if(!valArray) return;
13146       if(stacked) {
13147         $.each(valArray, function(v) { 
13148           acum += +v;
13149         });
13150       } else {
13151         acum = Math.max.apply(null, valArray);
13152       }
13153       maxValue = maxValue>acum? maxValue:acum;
13154     });
13155     return maxValue;
13156   },
13157   
13158   setBarType: function(type) {
13159     this.config.type = type;
13160     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13161   },
13162   
13163   normalizeDims: function() {
13164     //number of elements
13165     var root = this.st.graph.getNode(this.st.root), l=0;
13166     root.eachAdjacency(function() {
13167       l++;
13168     });
13169     var maxValue = this.getMaxValue() || 1,
13170         size = this.st.canvas.getSize(),
13171         config = this.config,
13172         margin = config.Margin,
13173         ticks = config.Ticks,
13174         title = config.Title,
13175         subtitle = config.Subtitle,
13176         grouped = config.type.split(':')[0] == 'grouped',
13177         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13178         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13179         horz = config.orientation == 'horizontal',
13180         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13181         animate = config.animate,
13182         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13183
13184           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13185         dim1 = horz? 'height':'width',
13186         dim2 = horz? 'width':'height',
13187         basic = config.type.split(':')[0] == 'basic';
13188         
13189         
13190                 var maxTickValue = Math.ceil(maxValue*.1)*10;
13191                 if(maxTickValue == maxValue) {
13192                         var length = maxTickValue.toString().length;
13193                         maxTickValue = maxTickValue + parseInt(pad(1,length));
13194                 }
13195
13196                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13197
13198                 
13199     this.st.graph.eachNode(function(n) {
13200       var acum = 0, animateValue = [];
13201       $.each(n.getData('valueArray'), function(v) {
13202         acum += +v;
13203         animateValue.push(0);
13204       });
13205       
13206       if(grouped) {
13207         fixedDim = animateValue.length * 40;
13208       }
13209       n.setData(dim1, fixedDim);
13210       
13211       
13212       if(animate) {
13213         n.setData(dim2, acum * height / maxValue, 'end');
13214         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13215           return n * height / maxValue; 
13216         }), 'end');
13217         var dimArray = n.getData('dimArray');
13218         if(!dimArray) {
13219           n.setData('dimArray', animateValue);
13220         }
13221       } else {
13222         
13223
13224                 if(ticks.enable) {
13225                         n.setData(dim2, acum * height / maxTickValue);
13226                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13227                           return n * height / maxTickValue; 
13228                         }));
13229                 } else {
13230                         n.setData(dim2, acum * height / maxValue);
13231                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13232                           return n * height / maxValue; 
13233                         }));
13234                 }
13235       }
13236     });
13237   }
13238 });
13239
13240 //funnel chart options
13241
13242
13243 Options.FunnelChart = {
13244   $extend: true,
13245   
13246   animate: true,
13247   type: 'stacked', //stacked, grouped, : gradient
13248   labelOffset: 3, //label offset
13249   barsOffset: 0, //distance between bars
13250   hoveredColor: '#9fd4ff',
13251   orientation: 'vertical',
13252   showAggregates: true,
13253   showLabels: true,
13254   Tips: {
13255     enable: false,
13256     onShow: $.empty,
13257     onHide: $.empty
13258   },
13259   Events: {
13260     enable: false,
13261     onClick: $.empty
13262   }
13263 };
13264
13265 $jit.ST.Plot.NodeTypes.implement({
13266   'funnelchart-basic' : {
13267     'render' : function(node, canvas) {
13268       var pos = node.pos.getc(true), 
13269           width  = node.getData('width'),
13270           height = node.getData('height'),
13271           algnPos = this.getAlignedPos(pos, width, height),
13272           x = algnPos.x, y = algnPos.y,
13273           dimArray = node.getData('dimArray'),
13274           valueArray = node.getData('valueArray'),
13275           valuelabelArray = node.getData('valuelabelArray'),
13276           linkArray = node.getData('linkArray'),
13277           colorArray = node.getData('colorArray'),
13278           colorLength = colorArray.length,
13279           stringArray = node.getData('stringArray');
13280       var ctx = canvas.getCtx(),
13281           opt = {},
13282           border = node.getData('border'),
13283           gradient = node.getData('gradient'),
13284           config = node.getData('config'),
13285           horz = config.orientation == 'horizontal',
13286           aggregates = config.showAggregates,
13287           showLabels = config.showLabels,
13288           label = config.Label,
13289           size = canvas.getSize(),
13290           labelOffset = config.labelOffset + 10;
13291           minWidth =  width * .25;
13292           ratio = .65;
13293
13294       if (colorArray && dimArray && stringArray) {
13295         
13296         
13297         // horizontal lines
13298         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13299         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13300
13301         if(label.type == 'Native') {      
13302        if(showLabels(node.name, valAcum, node)) {
13303                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13304                  var stringValue = stringArray[i];
13305                  var valueLabel = String(valuelabelArray[i]);
13306              var mV = ctx.measureText(stringValue);
13307              var mVL = ctx.measureText(valueLabel);
13308                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13309                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13310                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13311                  var bottomWidth = minWidth + ((acum) * ratio);  
13312                  var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);  
13313                          var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13314                          var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13315 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13316
13317                         //right lines
13318                         ctx.beginPath();
13319                         ctx.moveTo(bottomWidth/2,y - acum); //
13320                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13321                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight);  // bottom right
13322                         ctx.stroke();
13323                         //left lines
13324                         ctx.beginPath();
13325                         ctx.moveTo(-bottomWidth/2,y - acum); //
13326                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13327                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight);  // bottom right
13328                         ctx.stroke();
13329        }
13330         }
13331
13332                 acum += (dimArray[i] || 0);
13333           valAcum += (valueArray[i] || 0);
13334           
13335           
13336                 }
13337                 
13338  
13339   
13340         //funnel segments and labels
13341         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13342           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13343                           var colori = colorArray[i % colorLength];
13344                           if(label.type == 'Native') { 
13345                                   var stringValue = stringArray[i];
13346                           var valueLabel = String(valuelabelArray[i]);
13347                               var mV = ctx.measureText(stringValue);
13348                       var mVL = ctx.measureText(valueLabel);
13349                           } else {
13350                                   var mV = 10;
13351                       var mVL = 10;     
13352                           }
13353                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13354                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13355                       var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13356                       var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13357                       
13358           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13359           var bottomWidth = minWidth + ((acum) * ratio);
13360           var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13361           
13362
13363           if(gradient) {
13364             var linear;
13365               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13366                         var colorRgb = $.hexToRgb(colori);
13367             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13368                 function(v) { return (v * .5) >> 0; });
13369             linear.addColorStop(0, 'rgba('+color+',1)');
13370             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13371             linear.addColorStop(1, 'rgba('+color+',1)');
13372             ctx.fillStyle = linear;
13373           }
13374           
13375                         ctx.beginPath();
13376                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13377                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13378                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13379                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13380                         ctx.closePath(); 
13381                         ctx.fill();
13382                 
13383           
13384           if(border && border.name == stringArray[i]) {
13385             opt.acum = acum;
13386             opt.dimValue = dimArray[i];
13387           }
13388           
13389           
13390         if(border) {
13391           ctx.save();
13392           ctx.lineWidth = 2;
13393           ctx.strokeStyle = border.color;
13394
13395             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13396          
13397           ctx.restore();
13398         }
13399         if(label.type == 'Native') {
13400           ctx.save();
13401           ctx.fillStyle = ctx.strokeStyle = label.color;
13402           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13403           ctx.textBaseline = 'middle';
13404
13405                                 acumValueLabel = valAcum;
13406
13407           if(showLabels(node.name, valAcum, node)) {
13408
13409                       
13410               ctx.textAlign = 'left';
13411               ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13412               ctx.textAlign = 'right';
13413               ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13414               }
13415           ctx.restore();
13416         }
13417
13418           acum += (dimArray[i] || 0);
13419           valAcum += (valueArray[i] || 0);
13420           
13421         }
13422
13423       }
13424     },
13425     'contains': function(node, mpos) {
13426       var pos = node.pos.getc(true), 
13427           width = node.getData('width'),
13428           height = node.getData('height'),
13429           algnPos = this.getAlignedPos(pos, width, height),
13430           x = algnPos.x, y = algnPos.y,
13431           dimArray = node.getData('dimArray'),
13432           config = node.getData('config'),
13433           st = node.getData('st'),
13434           rx = mpos.x - x,
13435           horz = config.orientation == 'horizontal',
13436            minWidth =  width * .25;
13437           ratio = .65,
13438           canvas = node.getData('canvas'),
13439           size = canvas.getSize(),
13440           offsetY = st.config.offsetY;
13441       //bounding box check
13442
13443         if(mpos.y > y || mpos.y < y - height) {
13444             return false;
13445           }
13446           
13447          var newY = Math.abs(mpos.y + offsetY);
13448         var bound = minWidth + (newY * ratio);
13449         var boundLeft = -bound/2;
13450         var boundRight = bound/2;
13451          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13452             return false;
13453           }
13454
13455       
13456       //deep check
13457       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13458         var dimi = dimArray[i];
13459
13460           
13461           
13462                 var url = Url.decode(node.getData('linkArray')[i]);
13463           acum -= dimi;  
13464           var intersec = acum;
13465           if(mpos.y >= intersec) {
13466             return {
13467               'name': node.getData('stringArray')[i],
13468               'color': node.getData('colorArray')[i],
13469               'value': node.getData('valueArray')[i],
13470               'percentage': node.getData('percentageArray')[i],
13471                           'valuelabel': node.getData('valuelabelArray')[i],
13472               'link': url,
13473               'label': node.name
13474             };
13475           }
13476         
13477       }
13478       return false;
13479     }
13480   }
13481 });
13482
13483 /*
13484   Class: FunnelChart
13485   
13486   A visualization that displays funnel charts.
13487   
13488   Constructor Options:
13489   
13490   See <Options.FunnelChart>.
13491
13492 */
13493 $jit.FunnelChart = new Class({
13494   st: null,
13495   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13496   selected: {},
13497   busy: false,
13498   
13499   initialize: function(opt) {
13500     this.controller = this.config = 
13501       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13502         Label: { type: 'Native' }
13503       }, opt);
13504     //set functions for showLabels and showAggregates
13505     var showLabels = this.config.showLabels,
13506         typeLabels = $.type(showLabels),
13507         showAggregates = this.config.showAggregates,
13508         typeAggregates = $.type(showAggregates);
13509     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13510     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13511     Options.Fx.clearCanvas = false;
13512     this.initializeViz();
13513   },
13514   
13515   initializeViz: function() {
13516     var config = this.config, that = this;
13517     var nodeType = config.type.split(":")[0],
13518         horz = config.orientation == 'horizontal',
13519         nodeLabels = {};
13520     var st = new $jit.ST({
13521       injectInto: config.injectInto,
13522       orientation: horz? 'left' : 'bottom',
13523       levelDistance: 0,
13524       background: config.background,
13525       renderBackground: config.renderBackground,
13526       backgroundColor: config.backgroundColor,
13527       colorStop1: config.colorStop1,
13528       colorStop2: config.colorStop2,
13529       siblingOffset: config.segmentOffset,
13530       subtreeOffset: 0,
13531       withLabels: config.Label.type != 'Native',      
13532       useCanvas: config.useCanvas,
13533       Label: {
13534         type: config.Label.type
13535       },
13536       Node: {
13537         overridable: true,
13538         type: 'funnelchart-' + nodeType,
13539         align: 'left',
13540         width: 1,
13541         height: 1
13542       },
13543       Edge: {
13544         type: 'none'
13545       },
13546       Tips: {
13547         enable: config.Tips.enable,
13548         type: 'Native',
13549         force: true,
13550         onShow: function(tip, node, contains) {
13551           var elem = contains;
13552           config.Tips.onShow(tip, elem, node);
13553                           if(elem.link != 'undefined' && elem.link != '') {
13554                                 document.body.style.cursor = 'pointer';
13555                           }
13556         },
13557                 onHide: function(call) {
13558                         document.body.style.cursor = 'default';
13559
13560         }
13561       },
13562       Events: {
13563         enable: true,
13564         type: 'Native',
13565         onClick: function(node, eventInfo, evt) {
13566           if(!config.Events.enable) return;
13567           var elem = eventInfo.getContains();
13568           config.Events.onClick(elem, eventInfo, evt);
13569         },
13570         onMouseMove: function(node, eventInfo, evt) {
13571           if(!config.hoveredColor) return;
13572           if(node) {
13573             var elem = eventInfo.getContains();
13574             that.select(node.id, elem.name, elem.index);
13575           } else {
13576             that.select(false, false, false);
13577           }
13578         }
13579       },
13580       onCreateLabel: function(domElement, node) {
13581         var labelConf = config.Label,
13582             valueArray = node.getData('valueArray'),
13583             idArray = node.getData('idArray'),
13584             valuelabelArray = node.getData('valuelabelArray'),
13585             stringArray = node.getData('stringArray');
13586             size = st.canvas.getSize()
13587             prefix = $.time();
13588                 
13589                 for(var i=0, l=valueArray.length; i<l; i++) {
13590         var nlbs = {
13591           wrapper: document.createElement('div'),
13592           valueLabel: document.createElement('div'),
13593           label: document.createElement('div')
13594         };
13595         var wrapper = nlbs.wrapper,
13596             label = nlbs.label,
13597             valueLabel = nlbs.valueLabel,
13598             wrapperStyle = wrapper.style,
13599             labelStyle = label.style,
13600             valueLabelStyle = valueLabel.style;
13601         //store node labels
13602         nodeLabels[idArray[i]] = nlbs;
13603         //append labels
13604         wrapper.appendChild(label);
13605         wrapper.appendChild(valueLabel);
13606
13607         wrapperStyle.position = 'relative';
13608         wrapperStyle.overflow = 'visible';
13609         wrapperStyle.fontSize = labelConf.size + 'px';
13610         wrapperStyle.fontFamily = labelConf.family;
13611         wrapperStyle.color = labelConf.color;
13612         wrapperStyle.textAlign = 'center';
13613         wrapperStyle.width = size.width + 'px';
13614         valueLabelStyle.position = labelStyle.position = 'absolute';
13615         valueLabelStyle.left = labelStyle.left =  '0px';
13616                 valueLabelStyle.width = (size.width/3) + 'px';
13617                 valueLabelStyle.textAlign = 'right';
13618         label.innerHTML = stringArray[i];
13619         valueLabel.innerHTML = valuelabelArray[i];
13620         domElement.id = prefix+'funnel';
13621         domElement.style.width = size.width + 'px';
13622         
13623                 domElement.appendChild(wrapper);
13624                 }
13625
13626       },
13627       onPlaceLabel: function(domElement, node) {
13628
13629             var dimArray = node.getData('dimArray'),
13630             idArray = node.getData('idArray'),
13631             valueArray = node.getData('valueArray'),
13632             valuelabelArray = node.getData('valuelabelArray'),
13633             stringArray = node.getData('stringArray');
13634             size = st.canvas.getSize(),
13635             pos = node.pos.getc(true),
13636              domElement.style.left = "0px",
13637              domElement.style.top = "0px",
13638              minWidth = node.getData('width') * .25,
13639              ratio = .65,
13640              pos = node.pos.getc(true),
13641              labelConf = config.Label;
13642              
13643              
13644                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13645
13646         var labels = nodeLabels[idArray[i]],
13647             wrapperStyle = labels.wrapper.style,
13648             labelStyle = labels.label.style,
13649             valueLabelStyle = labels.valueLabel.style;
13650                 var bottomWidth = minWidth + (acum * ratio); 
13651                 
13652             font = parseInt(wrapperStyle.fontSize, 10),
13653             domStyle = domElement.style;
13654            
13655                 
13656        
13657                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13658             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13659             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13660
13661                         acum += (dimArray[i] || 0);
13662
13663                 }
13664
13665       }
13666
13667     });
13668
13669     var size = st.canvas.getSize(),
13670         margin = config.Margin;
13671         title = config.Title;
13672         subtitle = config.Subtitle;
13673         //y offset
13674
13675       st.config.offsetY = -size.height/2 + margin.bottom 
13676         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13677
13678                 st.config.offsetX = (margin.right - margin.left)/2;
13679           
13680     
13681     this.st = st;
13682     this.canvas = this.st.canvas;
13683   },
13684   
13685   renderTitle: function() {
13686         var canvas = this.canvas,
13687         size = canvas.getSize(),
13688         config = this.config,
13689         margin = config.Margin,
13690         label = config.Label,
13691         title = config.Title;
13692         ctx = canvas.getCtx();
13693         ctx.fillStyle = title.color;
13694         ctx.textAlign = 'left';
13695         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13696         if(label.type == 'Native') {
13697                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13698         }
13699   },  
13700   
13701   renderSubtitle: function() {
13702         var canvas = this.canvas,
13703         size = canvas.getSize(),
13704         config = this.config,
13705         margin = config.Margin,
13706         label = config.Label,
13707         subtitle = config.Subtitle;
13708         ctx = canvas.getCtx();
13709         ctx.fillStyle = title.color;
13710         ctx.textAlign = 'left';
13711         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13712         if(label.type == 'Native') {
13713                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13714         }
13715   },
13716   
13717   
13718   renderDropShadow: function() {
13719         var canvas = this.canvas,
13720         size = canvas.getSize(),
13721         config = this.config,
13722         margin = config.Margin,
13723         horz = config.orientation == 'horizontal',
13724         label = config.Label,
13725         title = config.Title,
13726         shadowThickness = 4,
13727         subtitle = config.Subtitle,
13728         ctx = canvas.getCtx(),
13729         minwidth = (size.width/8) * .25,
13730         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13731         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
13732     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13733           - (config.showLabels && (config.Label.size + config.labelOffset)),
13734     ratio = .65,
13735         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13736         topY = (-size.height/2) + topMargin - shadowThickness;
13737         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13738         bottomWidth = minwidth + shadowThickness;
13739         ctx.beginPath();
13740         ctx.fillStyle = "rgba(0,0,0,.2)";
13741         ctx.moveTo(0,topY);
13742         ctx.lineTo(-topWidth/2,topY); //top left
13743         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
13744         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
13745         ctx.lineTo(topWidth/2,topY);  // top right
13746         ctx.closePath(); 
13747         ctx.fill();
13748                         
13749                         
13750   },
13751
13752    renderBackground: function() {
13753                 var canvas = this.canvas,
13754                 config = this.config,
13755                 backgroundColor = config.backgroundColor,
13756                 size = canvas.getSize(),
13757                 ctx = canvas.getCtx();
13758                 //ctx.globalCompositeOperation = "destination-over";
13759             ctx.fillStyle = backgroundColor;
13760             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
13761   },
13762   clear: function() {
13763         var canvas = this.canvas;
13764         var ctx = canvas.getCtx(),
13765         size = canvas.getSize();
13766         ctx.fillStyle = "rgba(255,255,255,0)";
13767         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13768         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13769   },
13770    resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
13771         var canvas = this.canvas,
13772         size = canvas.getSize(),
13773         config = this.config,
13774         orgHeight = size.height,
13775         margin = config.Margin,
13776         st = this.st,
13777         label = config.Label,
13778         horz = config.orientation == 'horizontal',
13779         ctx = canvas.getCtx();
13780         
13781
13782         var newWindowWidth = document.body.offsetWidth;
13783         var diff = newWindowWidth - orgWindowWidth;     
13784         var newWidth = orgContainerDivWidth + (diff/cols);
13785         canvas.resize(newWidth,orgHeight);
13786
13787         if(typeof FlashCanvas == "undefined") {
13788                 canvas.clear();
13789         } else {
13790                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13791         }
13792         this.loadJSON(json);
13793
13794         },
13795         
13796   loadJSON: function(json) {
13797     if(this.busy) return;
13798     this.busy = true;
13799     var prefix = $.time(), 
13800         ch = [], 
13801         st = this.st,
13802         name = $.splat(json.label), 
13803         color = $.splat(json.color || this.colors),
13804         config = this.config,
13805         canvas = this.canvas,
13806         gradient = !!config.type.split(":")[1],
13807         animate = config.animate,
13808         title = config.Title,
13809         subtitle = config.Subtitle,
13810         renderBackground = config.renderBackground,
13811         horz = config.orientation == 'horizontal',
13812         that = this,
13813                 colorLength = color.length,
13814                 nameLength = name.length,
13815                 totalValue = 0;
13816     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13817         var val = values[i];
13818         var valArray = $.splat(val.values);
13819         totalValue += parseFloat(valArray.sum());
13820     }
13821     
13822     
13823     var nameArray = new Array();
13824     var idArray = new Array();
13825     var valArray = new Array();
13826     var valuelabelArray = new Array();
13827     var linkArray = new Array();
13828     var titleArray = new Array();
13829     var percentageArray = new Array();
13830     
13831     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13832       var val = values[i];
13833       nameArray[i] = $.splat(val.label);
13834       idArray[i] = $.splat(prefix + val.label);
13835       valArray[i] = $.splat(val.values);
13836       valuelabelArray[i] = $.splat(val.valuelabels);
13837       linkArray[i] = $.splat(val.links);
13838       titleArray[i] = $.splat(val.titles);
13839       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13840       var acum = 0;
13841     }
13842     
13843
13844     nameArray.reverse();
13845     valArray.reverse();
13846     valuelabelArray.reverse();
13847     linkArray.reverse();
13848     titleArray.reverse();
13849     percentageArray.reverse();
13850     
13851       ch.push({
13852         'id': prefix + val.label,
13853         'name': val.label,
13854         
13855         'data': {
13856           'value': valArray,
13857           '$idArray': idArray,
13858           '$linkArray': linkArray,
13859           '$titleArray': titleArray,
13860           '$valueArray': valArray,
13861           '$valuelabelArray': valuelabelArray,
13862           '$colorArray': color,
13863           '$colorMono': $.splat(color[i % colorLength]),
13864           '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
13865           '$gradient': gradient,
13866           '$config': config,
13867           '$percentageArray' : percentageArray,
13868           '$canvas': canvas,
13869           '$st': st
13870         },
13871         'children': []
13872       });
13873     
13874     var root = {
13875       'id': prefix + '$root',
13876       'name': '',
13877       'data': {
13878         '$type': 'none',
13879         '$width': 1,
13880         '$height': 1
13881       },
13882       'children': ch
13883     };
13884     st.loadJSON(root);
13885     
13886     this.normalizeDims();
13887         
13888         if(renderBackground) {
13889                 this.renderBackground();        
13890         }
13891         if(!animate && title.text) {
13892                 this.renderTitle();
13893         }
13894         if(!animate && subtitle.text) {
13895                 this.renderSubtitle();
13896         }
13897         if(typeof FlashCanvas == "undefined") {
13898                 this.renderDropShadow();
13899         }
13900     st.compute();
13901     st.select(st.root);
13902     if(animate) {
13903       if(horz) {
13904         st.fx.animate({
13905           modes: ['node-property:width:dimArray'],
13906           duration:1500,
13907           onComplete: function() {
13908             that.busy = false;
13909           }
13910         });
13911       } else {
13912         st.fx.animate({
13913           modes: ['node-property:height:dimArray'],
13914           duration:1500,
13915           onComplete: function() {
13916             that.busy = false;
13917           }
13918         });
13919       }
13920     } else {
13921       this.busy = false;
13922     }
13923   },
13924   
13925   /*
13926     Method: updateJSON
13927    
13928     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.
13929     
13930     Parameters:
13931     
13932     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13933     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13934     
13935     Example:
13936     
13937     (start code js)
13938     barChart.updateJSON(json, {
13939       onComplete: function() {
13940         alert('update complete!');
13941       }
13942     });
13943     (end code)
13944  */  
13945   updateJSON: function(json, onComplete) {
13946     if(this.busy) return;
13947     this.busy = true;
13948     
13949     var st = this.st;
13950     var graph = st.graph;
13951     var values = json.values;
13952     var animate = this.config.animate;
13953     var that = this;
13954     var horz = this.config.orientation == 'horizontal';
13955     $.each(values, function(v) {
13956       var n = graph.getByName(v.label);
13957       if(n) {
13958         n.setData('valueArray', $.splat(v.values));
13959         if(json.label) {
13960           n.setData('stringArray', $.splat(json.label));
13961         }
13962       }
13963     });
13964     this.normalizeDims();
13965     st.compute();
13966     st.select(st.root);
13967     if(animate) {
13968       if(horz) {
13969         st.fx.animate({
13970           modes: ['node-property:width:dimArray'],
13971           duration:1500,
13972           onComplete: function() {
13973             that.busy = false;
13974             onComplete && onComplete.onComplete();
13975           }
13976         });
13977       } else {
13978         st.fx.animate({
13979           modes: ['node-property:height:dimArray'],
13980           duration:1500,
13981           onComplete: function() {
13982             that.busy = false;
13983             onComplete && onComplete.onComplete();
13984           }
13985         });
13986       }
13987     }
13988   },
13989   
13990   //adds the little brown bar when hovering the node
13991   select: function(id, name) {
13992
13993     if(!this.config.hoveredColor) return;
13994     var s = this.selected;
13995     if(s.id != id || s.name != name) {
13996       s.id = id;
13997       s.name = name;
13998       s.color = this.config.hoveredColor;
13999       this.st.graph.eachNode(function(n) {
14000         if(id == n.id) {
14001           n.setData('border', s);
14002         } else {
14003           n.setData('border', false);
14004         }
14005       });
14006       this.st.plot();
14007     }
14008   },
14009   
14010   /*
14011     Method: getLegend
14012    
14013     Returns an object containing as keys the legend names and as values hex strings with color values.
14014     
14015     Example:
14016     
14017     (start code js)
14018     var legend = barChart.getLegend();
14019     (end code)
14020   */  
14021   getLegend: function() {
14022     var legend = new Array();
14023     var name = new Array();
14024     var color = new Array();
14025     var n;
14026     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14027       n = adj.nodeTo;
14028     });
14029     var colors = n.getData('colorArray'),
14030         len = colors.length;
14031     $.each(n.getData('stringArray'), function(s, i) {
14032       color[i] = colors[i % len];
14033       name[i] = s;
14034     });
14035         legend['name'] = name;
14036         legend['color'] = color;
14037     return legend;
14038   },
14039   
14040   /*
14041     Method: getMaxValue
14042    
14043     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14044     
14045     Example:
14046     
14047     (start code js)
14048     var ans = barChart.getMaxValue();
14049     (end code)
14050     
14051     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14052     
14053     Example:
14054     
14055     (start code js)
14056     //will return 100 for all BarChart instances,
14057     //displaying all of them with the same scale
14058     $jit.BarChart.implement({
14059       'getMaxValue': function() {
14060         return 100;
14061       }
14062     });
14063     (end code)
14064     
14065   */  
14066   getMaxValue: function() {
14067     var maxValue = 0, stacked = true;
14068     this.st.graph.eachNode(function(n) {
14069       var valArray = n.getData('valueArray'),
14070           acum = 0;
14071       if(!valArray) return;
14072       if(stacked) {
14073         $.each(valArray, function(v) { 
14074           acum += +v;
14075         });
14076       } else {
14077         acum = Math.max.apply(null, valArray);
14078       }
14079       maxValue = maxValue>acum? maxValue:acum;
14080     });
14081     return maxValue;
14082   },
14083   
14084   setBarType: function(type) {
14085     this.config.type = type;
14086     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14087   },
14088   
14089   normalizeDims: function() {
14090     //number of elements
14091     var root = this.st.graph.getNode(this.st.root), l=0;
14092     root.eachAdjacency(function() {
14093       l++;
14094     });
14095     var maxValue = this.getMaxValue() || 1,
14096         size = this.st.canvas.getSize(),
14097         config = this.config,
14098         margin = config.Margin,
14099         title = config.Title,
14100         subtitle = config.Subtitle,
14101         marginWidth = margin.left + margin.right,
14102         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14103         horz = config.orientation == 'horizontal',
14104         animate = config.animate,
14105         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14106
14107           - (config.showLabels && (config.Label.size + config.labelOffset)),
14108         dim1 = horz? 'height':'width',
14109         dim2 = horz? 'width':'height';
14110         
14111
14112         minWidth = size.width/8;
14113         
14114
14115
14116     this.st.graph.eachNode(function(n) {
14117       var acum = 0, animateValue = [];
14118       $.each(n.getData('valueArray'), function(v) {
14119         acum += +v;
14120         animateValue.push(0);
14121       });
14122       n.setData(dim1, minWidth);
14123             
14124       if(animate) {
14125         n.setData(dim2, acum * height / maxValue, 'end');
14126         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14127           return n * height / maxValue; 
14128         }), 'end');
14129         var dimArray = n.getData('dimArray');
14130         if(!dimArray) {
14131           n.setData('dimArray', animateValue);
14132         }
14133       } else {
14134                         n.setData(dim2, acum * height / maxValue);
14135                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14136                           return n * height / maxValue; 
14137                         }));
14138       }
14139
14140     });
14141   }
14142 });
14143
14144
14145
14146 /*
14147  * File: Options.PieChart.js
14148  *
14149 */
14150 /*
14151   Object: Options.PieChart
14152   
14153   <PieChart> options. 
14154   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14155   
14156   Syntax:
14157   
14158   (start code js)
14159
14160   Options.PieChart = {
14161     animate: true,
14162     offset: 25,
14163     sliceOffset:0,
14164     labelOffset: 3,
14165     type: 'stacked',
14166     hoveredColor: '#9fd4ff',
14167     showLabels: true,
14168     resizeLabels: false,
14169     updateHeights: false
14170   };  
14171
14172   (end code)
14173   
14174   Example:
14175   
14176   (start code js)
14177
14178   var pie = new $jit.PieChart({
14179     animate: true,
14180     sliceOffset: 5,
14181     type: 'stacked:gradient'
14182   });  
14183
14184   (end code)
14185   
14186   Parameters:
14187   
14188   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14189   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14190   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14191   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14192   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14193   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14194   showLabels - (boolean) Default's *true*. Display the name of the slots.
14195   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.
14196   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.
14197
14198 */
14199 Options.PieChart = {
14200   $extend: true,
14201
14202   animate: true,
14203   offset: 25, // page offset
14204   sliceOffset:0,
14205   labelOffset: 3, // label offset
14206   type: 'stacked', // gradient
14207   labelType: 'name',
14208   hoveredColor: '#9fd4ff',
14209   Events: {
14210     enable: false,
14211     onClick: $.empty
14212   },
14213   Tips: {
14214     enable: false,
14215     onShow: $.empty,
14216     onHide: $.empty
14217   },
14218   showLabels: true,
14219   resizeLabels: false,
14220   
14221   //only valid for mono-valued datasets
14222   updateHeights: false
14223 };
14224
14225 /*
14226  * Class: Layouts.Radial
14227  * 
14228  * Implements a Radial Layout.
14229  * 
14230  * Implemented By:
14231  * 
14232  * <RGraph>, <Hypertree>
14233  * 
14234  */
14235 Layouts.Radial = new Class({
14236
14237   /*
14238    * Method: compute
14239    * 
14240    * Computes nodes' positions.
14241    * 
14242    * Parameters:
14243    * 
14244    * property - _optional_ A <Graph.Node> position property to store the new
14245    * positions. Possible values are 'pos', 'end' or 'start'.
14246    * 
14247    */
14248   compute : function(property) {
14249     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14250     NodeDim.compute(this.graph, prop, this.config);
14251     this.graph.computeLevels(this.root, 0, "ignore");
14252     var lengthFunc = this.createLevelDistanceFunc(); 
14253     this.computeAngularWidths(prop);
14254     this.computePositions(prop, lengthFunc);
14255   },
14256
14257   /*
14258    * computePositions
14259    * 
14260    * Performs the main algorithm for computing node positions.
14261    */
14262   computePositions : function(property, getLength) {
14263     var propArray = property;
14264     var graph = this.graph;
14265     var root = graph.getNode(this.root);
14266     var parent = this.parent;
14267     var config = this.config;
14268
14269     for ( var i=0, l=propArray.length; i < l; i++) {
14270       var pi = propArray[i];
14271       root.setPos($P(0, 0), pi);
14272       root.setData('span', Math.PI * 2, pi);
14273     }
14274
14275     root.angleSpan = {
14276       begin : 0,
14277       end : 2 * Math.PI
14278     };
14279
14280     graph.eachBFS(this.root, function(elem) {
14281       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14282       var angleInit = elem.angleSpan.begin;
14283       var len = getLength(elem);
14284       //Calculate the sum of all angular widths
14285       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14286       elem.eachSubnode(function(sib) {
14287         totalAngularWidths += sib._treeAngularWidth;
14288         //get max dim
14289         for ( var i=0, l=propArray.length; i < l; i++) {
14290           var pi = propArray[i], dim = sib.getData('dim', pi);
14291           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14292         }
14293         subnodes.push(sib);
14294       }, "ignore");
14295       //Maintain children order
14296       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14297       if (parent && parent.id == elem.id && subnodes.length > 0
14298           && subnodes[0].dist) {
14299         subnodes.sort(function(a, b) {
14300           return (a.dist >= b.dist) - (a.dist <= b.dist);
14301         });
14302       }
14303       //Calculate nodes positions.
14304       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14305         var child = subnodes[k];
14306         if (!child._flag) {
14307           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14308           var theta = angleInit + angleProportion / 2;
14309
14310           for ( var i=0, l=propArray.length; i < l; i++) {
14311             var pi = propArray[i];
14312             child.setPos($P(theta, len), pi);
14313             child.setData('span', angleProportion, pi);
14314             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14315           }
14316
14317           child.angleSpan = {
14318             begin : angleInit,
14319             end : angleInit + angleProportion
14320           };
14321           angleInit += angleProportion;
14322         }
14323       }
14324     }, "ignore");
14325   },
14326
14327   /*
14328    * Method: setAngularWidthForNodes
14329    * 
14330    * Sets nodes angular widths.
14331    */
14332   setAngularWidthForNodes : function(prop) {
14333     this.graph.eachBFS(this.root, function(elem, i) {
14334       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14335       elem._angularWidth = diamValue / i;
14336     }, "ignore");
14337   },
14338
14339   /*
14340    * Method: setSubtreesAngularWidth
14341    * 
14342    * Sets subtrees angular widths.
14343    */
14344   setSubtreesAngularWidth : function() {
14345     var that = this;
14346     this.graph.eachNode(function(elem) {
14347       that.setSubtreeAngularWidth(elem);
14348     }, "ignore");
14349   },
14350
14351   /*
14352    * Method: setSubtreeAngularWidth
14353    * 
14354    * Sets the angular width for a subtree.
14355    */
14356   setSubtreeAngularWidth : function(elem) {
14357     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14358     elem.eachSubnode(function(child) {
14359       that.setSubtreeAngularWidth(child);
14360       sumAW += child._treeAngularWidth;
14361     }, "ignore");
14362     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14363   },
14364
14365   /*
14366    * Method: computeAngularWidths
14367    * 
14368    * Computes nodes and subtrees angular widths.
14369    */
14370   computeAngularWidths : function(prop) {
14371     this.setAngularWidthForNodes(prop);
14372     this.setSubtreesAngularWidth();
14373   }
14374
14375 });
14376
14377
14378 /*
14379  * File: Sunburst.js
14380  */
14381
14382 /*
14383    Class: Sunburst
14384       
14385    A radial space filling tree visualization.
14386    
14387    Inspired by:
14388  
14389    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14390    
14391    Note:
14392    
14393    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.
14394    
14395   Implements:
14396   
14397   All <Loader> methods
14398   
14399    Constructor Options:
14400    
14401    Inherits options from
14402    
14403    - <Options.Canvas>
14404    - <Options.Controller>
14405    - <Options.Node>
14406    - <Options.Edge>
14407    - <Options.Label>
14408    - <Options.Events>
14409    - <Options.Tips>
14410    - <Options.NodeStyles>
14411    - <Options.Navigation>
14412    
14413    Additionally, there are other parameters and some default values changed
14414    
14415    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14416    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14417    Node.type - Described in <Options.Node>. Default's to *multipie*.
14418    Node.height - Described in <Options.Node>. Default's *0*.
14419    Edge.type - Described in <Options.Edge>. Default's *none*.
14420    Label.textAlign - Described in <Options.Label>. Default's *start*.
14421    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14422      
14423    Instance Properties:
14424
14425    canvas - Access a <Canvas> instance.
14426    graph - Access a <Graph> instance.
14427    op - Access a <Sunburst.Op> instance.
14428    fx - Access a <Sunburst.Plot> instance.
14429    labels - Access a <Sunburst.Label> interface implementation.   
14430
14431 */
14432
14433 $jit.Sunburst = new Class({
14434
14435   Implements: [ Loader, Extras, Layouts.Radial ],
14436
14437   initialize: function(controller) {
14438     var $Sunburst = $jit.Sunburst;
14439
14440     var config = {
14441       interpolation: 'linear',
14442       levelDistance: 100,
14443       Node: {
14444         'type': 'multipie',
14445         'height':0
14446       },
14447       Edge: {
14448         'type': 'none'
14449       },
14450       Label: {
14451         textAlign: 'start',
14452         textBaseline: 'middle'
14453       }
14454     };
14455
14456     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14457         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14458
14459     var canvasConfig = this.config;
14460     if(canvasConfig.useCanvas) {
14461       this.canvas = canvasConfig.useCanvas;
14462       this.config.labelContainer = this.canvas.id + '-label';
14463     } else {
14464       if(canvasConfig.background) {
14465         canvasConfig.background = $.merge({
14466           type: 'Fade',
14467           colorStop1: this.config.colorStop1,
14468           colorStop2: this.config.colorStop2
14469         }, canvasConfig.background);
14470       }
14471       this.canvas = new Canvas(this, canvasConfig);
14472       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14473     }
14474
14475     this.graphOptions = {
14476       'complex': false,
14477       'Node': {
14478         'selected': false,
14479         'exist': true,
14480         'drawn': true
14481       }
14482     };
14483     this.graph = new Graph(this.graphOptions, this.config.Node,
14484         this.config.Edge);
14485     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14486     this.fx = new $Sunburst.Plot(this, $Sunburst);
14487     this.op = new $Sunburst.Op(this);
14488     this.json = null;
14489     this.root = null;
14490     this.rotated = null;
14491     this.busy = false;
14492     // initialize extras
14493     this.initializeExtras();
14494   },
14495
14496   /* 
14497   
14498     createLevelDistanceFunc 
14499   
14500     Returns the levelDistance function used for calculating a node distance 
14501     to its origin. This function returns a function that is computed 
14502     per level and not per node, such that all nodes with the same depth will have the 
14503     same distance to the origin. The resulting function gets the 
14504     parent node as parameter and returns a float.
14505
14506    */
14507   createLevelDistanceFunc: function() {
14508     var ld = this.config.levelDistance;
14509     return function(elem) {
14510       return (elem._depth + 1) * ld;
14511     };
14512   },
14513
14514   /* 
14515      Method: refresh 
14516      
14517      Computes positions and plots the tree.
14518
14519    */
14520   refresh: function() {
14521     this.compute();
14522     this.plot();
14523   },
14524
14525   /*
14526    reposition
14527   
14528    An alias for computing new positions to _endPos_
14529
14530    See also:
14531
14532    <Sunburst.compute>
14533    
14534   */
14535   reposition: function() {
14536     this.compute('end');
14537   },
14538
14539   /*
14540   Method: rotate
14541   
14542   Rotates the graph so that the selected node is horizontal on the right.
14543
14544   Parameters:
14545   
14546   node - (object) A <Graph.Node>.
14547   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14548   opt - (object) Configuration options merged with this visualization configuration options.
14549   
14550   See also:
14551
14552   <Sunburst.rotateAngle>
14553   
14554   */
14555   rotate: function(node, method, opt) {
14556     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14557     this.rotated = node;
14558     this.rotateAngle(-theta, method, opt);
14559   },
14560
14561   /*
14562   Method: rotateAngle
14563   
14564   Rotates the graph of an angle theta.
14565   
14566    Parameters:
14567    
14568    node - (object) A <Graph.Node>.
14569    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14570    opt - (object) Configuration options merged with this visualization configuration options.
14571    
14572    See also:
14573
14574    <Sunburst.rotate>
14575   
14576   */
14577   rotateAngle: function(theta, method, opt) {
14578     var that = this;
14579     var options = $.merge(this.config, opt || {}, {
14580       modes: [ 'polar' ]
14581     });
14582     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14583     if(method === 'animate') {
14584       this.fx.animation.pause();
14585     }
14586     this.graph.eachNode(function(n) {
14587       var p = n.getPos(prop);
14588       p.theta += theta;
14589       if (p.theta < 0) {
14590         p.theta += Math.PI * 2;
14591       }
14592     });
14593     if (method == 'animate') {
14594       this.fx.animate(options);
14595     } else if (method == 'replot') {
14596       this.fx.plot();
14597       this.busy = false;
14598     }
14599   },
14600
14601   /*
14602    Method: plot
14603   
14604    Plots the Sunburst. This is a shortcut to *fx.plot*.
14605   */
14606   plot: function() {
14607     this.fx.plot();
14608   }
14609 });
14610
14611 $jit.Sunburst.$extend = true;
14612
14613 (function(Sunburst) {
14614
14615   /*
14616      Class: Sunburst.Op
14617
14618      Custom extension of <Graph.Op>.
14619
14620      Extends:
14621
14622      All <Graph.Op> methods
14623      
14624      See also:
14625      
14626      <Graph.Op>
14627
14628   */
14629   Sunburst.Op = new Class( {
14630
14631     Implements: Graph.Op
14632
14633   });
14634
14635   /*
14636      Class: Sunburst.Plot
14637
14638     Custom extension of <Graph.Plot>.
14639   
14640     Extends:
14641   
14642     All <Graph.Plot> methods
14643     
14644     See also:
14645     
14646     <Graph.Plot>
14647   
14648   */
14649   Sunburst.Plot = new Class( {
14650
14651     Implements: Graph.Plot
14652
14653   });
14654
14655   /*
14656     Class: Sunburst.Label
14657
14658     Custom extension of <Graph.Label>. 
14659     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14660   
14661     Extends:
14662   
14663     All <Graph.Label> methods and subclasses.
14664   
14665     See also:
14666   
14667     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14668   
14669    */
14670   Sunburst.Label = {};
14671
14672   /*
14673      Sunburst.Label.Native
14674
14675      Custom extension of <Graph.Label.Native>.
14676
14677      Extends:
14678
14679      All <Graph.Label.Native> methods
14680
14681      See also:
14682
14683      <Graph.Label.Native>
14684   */
14685   Sunburst.Label.Native = new Class( {
14686     Implements: Graph.Label.Native,
14687
14688     initialize: function(viz) {
14689       this.viz = viz;
14690       this.label = viz.config.Label;
14691       this.config = viz.config;
14692     },
14693
14694     renderLabel: function(canvas, node, controller) {
14695       var span = node.getData('span');
14696       if(span < Math.PI /2 && Math.tan(span) * 
14697           this.config.levelDistance * node._depth < 10) {
14698         return;
14699       }
14700       var ctx = canvas.getCtx();
14701       var measure = ctx.measureText(node.name);
14702       if (node.id == this.viz.root) {
14703         var x = -measure.width / 2, y = 0, thetap = 0;
14704         var ld = 0;
14705       } else {
14706         var indent = 5;
14707         var ld = controller.levelDistance - indent;
14708         var clone = node.pos.clone();
14709         clone.rho += indent;
14710         var p = clone.getp(true);
14711         var ct = clone.getc(true);
14712         var x = ct.x, y = ct.y;
14713         // get angle in degrees
14714         var pi = Math.PI;
14715         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14716         var thetap = cond ? p.theta + pi : p.theta;
14717         if (cond) {
14718           x -= Math.abs(Math.cos(p.theta) * measure.width);
14719           y += Math.sin(p.theta) * measure.width;
14720         } else if (node.id == this.viz.root) {
14721           x -= measure.width / 2;
14722         }
14723       }
14724       ctx.save();
14725       ctx.translate(x, y);
14726       ctx.rotate(thetap);
14727       ctx.fillText(node.name, 0, 0);
14728       ctx.restore();
14729     }
14730   });
14731
14732   /*
14733      Sunburst.Label.SVG
14734
14735     Custom extension of <Graph.Label.SVG>.
14736   
14737     Extends:
14738   
14739     All <Graph.Label.SVG> methods
14740   
14741     See also:
14742   
14743     <Graph.Label.SVG>
14744   
14745   */
14746   Sunburst.Label.SVG = new Class( {
14747     Implements: Graph.Label.SVG,
14748
14749     initialize: function(viz) {
14750       this.viz = viz;
14751     },
14752
14753     /* 
14754        placeLabel
14755
14756        Overrides abstract method placeLabel in <Graph.Plot>.
14757
14758        Parameters:
14759
14760        tag - A DOM label element.
14761        node - A <Graph.Node>.
14762        controller - A configuration/controller object passed to the visualization.
14763       
14764      */
14765     placeLabel: function(tag, node, controller) {
14766       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14767       var radius = canvas.getSize();
14768       var labelPos = {
14769         x: Math.round(pos.x + radius.width / 2),
14770         y: Math.round(pos.y + radius.height / 2)
14771       };
14772       tag.setAttribute('x', labelPos.x);
14773       tag.setAttribute('y', labelPos.y);
14774
14775       var bb = tag.getBBox();
14776       if (bb) {
14777         // center the label
14778     var x = tag.getAttribute('x');
14779     var y = tag.getAttribute('y');
14780     // get polar coordinates
14781     var p = node.pos.getp(true);
14782     // get angle in degrees
14783     var pi = Math.PI;
14784     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14785     if (cond) {
14786       tag.setAttribute('x', x - bb.width);
14787       tag.setAttribute('y', y - bb.height);
14788     } else if (node.id == viz.root) {
14789       tag.setAttribute('x', x - bb.width / 2);
14790     }
14791
14792     var thetap = cond ? p.theta + pi : p.theta;
14793     if(node._depth)
14794       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14795           + ' ' + y + ')');
14796   }
14797
14798   controller.onPlaceLabel(tag, node);
14799 }
14800   });
14801
14802   /*
14803      Sunburst.Label.HTML
14804
14805      Custom extension of <Graph.Label.HTML>.
14806
14807      Extends:
14808
14809      All <Graph.Label.HTML> methods.
14810
14811      See also:
14812
14813      <Graph.Label.HTML>
14814
14815   */
14816   Sunburst.Label.HTML = new Class( {
14817     Implements: Graph.Label.HTML,
14818
14819     initialize: function(viz) {
14820       this.viz = viz;
14821     },
14822     /* 
14823        placeLabel
14824
14825        Overrides abstract method placeLabel in <Graph.Plot>.
14826
14827        Parameters:
14828
14829        tag - A DOM label element.
14830        node - A <Graph.Node>.
14831        controller - A configuration/controller object passed to the visualization.
14832       
14833      */
14834     placeLabel: function(tag, node, controller) {
14835       var pos = node.pos.clone(), 
14836           canvas = this.viz.canvas,
14837           height = node.getData('height'),
14838           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14839           radius = canvas.getSize();
14840       pos.rho += ldist;
14841       pos = pos.getc(true);
14842       
14843       var labelPos = {
14844         x: Math.round(pos.x + radius.width / 2),
14845         y: Math.round(pos.y + radius.height / 2)
14846       };
14847
14848       var style = tag.style;
14849       style.left = labelPos.x + 'px';
14850       style.top = labelPos.y + 'px';
14851       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14852
14853       controller.onPlaceLabel(tag, node);
14854     }
14855   });
14856
14857   /*
14858     Class: Sunburst.Plot.NodeTypes
14859
14860     This class contains a list of <Graph.Node> built-in types. 
14861     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14862
14863     You can add your custom node types, customizing your visualization to the extreme.
14864
14865     Example:
14866
14867     (start code js)
14868       Sunburst.Plot.NodeTypes.implement({
14869         'mySpecialType': {
14870           'render': function(node, canvas) {
14871             //print your custom node to canvas
14872           },
14873           //optional
14874           'contains': function(node, pos) {
14875             //return true if pos is inside the node or false otherwise
14876           }
14877         }
14878       });
14879     (end code)
14880
14881   */
14882   Sunburst.Plot.NodeTypes = new Class( {
14883     'none': {
14884       'render': $.empty,
14885       'contains': $.lambda(false),
14886       'anglecontains': function(node, pos) {
14887         var span = node.getData('span') / 2, theta = node.pos.theta;
14888         var begin = theta - span, end = theta + span;
14889         if (begin < 0)
14890           begin += Math.PI * 2;
14891         var atan = Math.atan2(pos.y, pos.x);
14892         if (atan < 0)
14893           atan += Math.PI * 2;
14894         if (begin > end) {
14895           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14896         } else {
14897           return atan > begin && atan < end;
14898         }
14899       },
14900           'anglecontainsgauge': function(node, pos) {
14901         var span = node.getData('span') / 2, theta = node.pos.theta;
14902                 var config = node.getData('config');
14903         var ld = this.config.levelDistance;
14904                 var yOffset = pos.y-(ld/2);
14905                 var begin = ((theta - span)/2)+Math.PI,
14906         end = ((theta + span)/2)+Math.PI;
14907                 
14908         if (begin < 0)
14909           begin += Math.PI * 2;
14910         var atan = Math.atan2(yOffset, pos.x);
14911
14912                 
14913         if (atan < 0)
14914           atan += Math.PI * 2;
14915                   
14916                   
14917         if (begin > end) {
14918           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14919         } else {
14920           return atan > begin && atan < end;
14921         }
14922       }
14923     },
14924
14925     'pie': {
14926       'render': function(node, canvas) {
14927         var span = node.getData('span') / 2, theta = node.pos.theta;
14928         var begin = theta - span, end = theta + span;
14929         var polarNode = node.pos.getp(true);
14930         var polar = new Polar(polarNode.rho, begin);
14931         var p1coord = polar.getc(true);
14932         polar.theta = end;
14933         var p2coord = polar.getc(true);
14934
14935         var ctx = canvas.getCtx();
14936         ctx.beginPath();
14937         ctx.moveTo(0, 0);
14938         ctx.lineTo(p1coord.x, p1coord.y);
14939         ctx.moveTo(0, 0);
14940         ctx.lineTo(p2coord.x, p2coord.y);
14941         ctx.moveTo(0, 0);
14942         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14943             false);
14944         ctx.fill();
14945       },
14946       'contains': function(node, pos) {
14947         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14948           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14949           var ld = this.config.levelDistance, d = node._depth;
14950           return (rho <= ld * d);
14951         }
14952         return false;
14953       }
14954     },
14955     'multipie': {
14956       'render': function(node, canvas) {
14957         var height = node.getData('height');
14958         var ldist = height? height : this.config.levelDistance;
14959         var span = node.getData('span') / 2, theta = node.pos.theta;
14960         var begin = theta - span, end = theta + span;
14961         var polarNode = node.pos.getp(true);
14962
14963         var polar = new Polar(polarNode.rho, begin);
14964         var p1coord = polar.getc(true);
14965
14966         polar.theta = end;
14967         var p2coord = polar.getc(true);
14968
14969         polar.rho += ldist;
14970         var p3coord = polar.getc(true);
14971
14972         polar.theta = begin;
14973         var p4coord = polar.getc(true);
14974
14975         var ctx = canvas.getCtx();
14976         ctx.moveTo(0, 0);
14977         ctx.beginPath();
14978         ctx.arc(0, 0, polarNode.rho, begin, end, false);
14979         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
14980         ctx.moveTo(p1coord.x, p1coord.y);
14981         ctx.lineTo(p4coord.x, p4coord.y);
14982         ctx.moveTo(p2coord.x, p2coord.y);
14983         ctx.lineTo(p3coord.x, p3coord.y);
14984         ctx.fill();
14985
14986         if (node.collapsed) {
14987           ctx.save();
14988           ctx.lineWidth = 2;
14989           ctx.moveTo(0, 0);
14990           ctx.beginPath();
14991           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
14992               true);
14993           ctx.stroke();
14994           ctx.restore();
14995         }
14996       },
14997       'contains': function(node, pos) {
14998         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14999           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15000           var height = node.getData('height');
15001           var ldist = height? height : this.config.levelDistance;
15002           var ld = this.config.levelDistance, d = node._depth;
15003           return (rho >= ld * d) && (rho <= (ld * d + ldist));
15004         }
15005         return false;
15006       }
15007     },
15008
15009     'gradient-multipie': {
15010       'render': function(node, canvas) {
15011         var ctx = canvas.getCtx();
15012         var height = node.getData('height');
15013         var ldist = height? height : this.config.levelDistance;
15014         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15015             0, 0, node.getPos().rho + ldist);
15016
15017         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15018         $.each(colorArray, function(i) {
15019           ans.push(parseInt(i * 0.5, 10));
15020         });
15021         var endColor = $.rgbToHex(ans);
15022         radialGradient.addColorStop(0, endColor);
15023         radialGradient.addColorStop(1, node.getData('color'));
15024         ctx.fillStyle = radialGradient;
15025         this.nodeTypes['multipie'].render.call(this, node, canvas);
15026       },
15027       'contains': function(node, pos) {
15028         return this.nodeTypes['multipie'].contains.call(this, node, pos);
15029       }
15030     },
15031
15032     'gradient-pie': {
15033       'render': function(node, canvas) {
15034         var ctx = canvas.getCtx();
15035         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15036             .getPos().rho);
15037
15038         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15039         $.each(colorArray, function(i) {
15040           ans.push(parseInt(i * 0.5, 10));
15041         });
15042         var endColor = $.rgbToHex(ans);
15043         radialGradient.addColorStop(1, endColor);
15044         radialGradient.addColorStop(0, node.getData('color'));
15045         ctx.fillStyle = radialGradient;
15046         this.nodeTypes['pie'].render.call(this, node, canvas);
15047       },
15048       'contains': function(node, pos) {
15049         return this.nodeTypes['pie'].contains.call(this, node, pos);
15050       }
15051     }
15052   });
15053
15054   /*
15055     Class: Sunburst.Plot.EdgeTypes
15056
15057     This class contains a list of <Graph.Adjacence> built-in types. 
15058     Edge types implemented are 'none', 'line' and 'arrow'.
15059   
15060     You can add your custom edge types, customizing your visualization to the extreme.
15061   
15062     Example:
15063   
15064     (start code js)
15065       Sunburst.Plot.EdgeTypes.implement({
15066         'mySpecialType': {
15067           'render': function(adj, canvas) {
15068             //print your custom edge to canvas
15069           },
15070           //optional
15071           'contains': function(adj, pos) {
15072             //return true if pos is inside the arc or false otherwise
15073           }
15074         }
15075       });
15076     (end code)
15077   
15078   */
15079   Sunburst.Plot.EdgeTypes = new Class({
15080     'none': $.empty,
15081     'line': {
15082       'render': function(adj, canvas) {
15083         var from = adj.nodeFrom.pos.getc(true),
15084             to = adj.nodeTo.pos.getc(true);
15085         this.edgeHelper.line.render(from, to, canvas);
15086       },
15087       'contains': function(adj, pos) {
15088         var from = adj.nodeFrom.pos.getc(true),
15089             to = adj.nodeTo.pos.getc(true);
15090         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15091       }
15092     },
15093     'arrow': {
15094       'render': function(adj, canvas) {
15095         var from = adj.nodeFrom.pos.getc(true),
15096             to = adj.nodeTo.pos.getc(true),
15097             dim = adj.getData('dim'),
15098             direction = adj.data.$direction,
15099             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15100         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15101       },
15102       'contains': function(adj, pos) {
15103         var from = adj.nodeFrom.pos.getc(true),
15104             to = adj.nodeTo.pos.getc(true);
15105         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15106       }
15107     },
15108     'hyperline': {
15109       'render': function(adj, canvas) {
15110         var from = adj.nodeFrom.pos.getc(),
15111             to = adj.nodeTo.pos.getc(),
15112             dim = Math.max(from.norm(), to.norm());
15113         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15114       },
15115       'contains': $.lambda(false) //TODO(nico): Implement this!
15116     }
15117   });
15118
15119 })($jit.Sunburst);
15120
15121
15122 /*
15123  * File: PieChart.js
15124  *
15125 */
15126
15127 $jit.Sunburst.Plot.NodeTypes.implement({
15128   'piechart-stacked' : {
15129     'render' : function(node, canvas) {
15130       var pos = node.pos.getp(true),
15131           dimArray = node.getData('dimArray'),
15132           valueArray = node.getData('valueArray'),
15133           colorArray = node.getData('colorArray'),
15134           colorLength = colorArray.length,
15135           stringArray = node.getData('stringArray'),
15136           span = node.getData('span') / 2,
15137           theta = node.pos.theta,
15138           begin = theta - span,
15139           end = theta + span,
15140           polar = new Polar;
15141     
15142       var ctx = canvas.getCtx(), 
15143           opt = {},
15144           gradient = node.getData('gradient'),
15145           border = node.getData('border'),
15146           config = node.getData('config'),
15147           showLabels = config.showLabels,
15148           resizeLabels = config.resizeLabels,
15149           label = config.Label;
15150
15151       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15152       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15153
15154       if (colorArray && dimArray && stringArray) {
15155         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15156           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15157           if(dimi <= 0) continue;
15158           ctx.fillStyle = ctx.strokeStyle = colori;
15159           if(gradient && dimi) {
15160             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15161                 xpos, ypos, acum + dimi + config.sliceOffset);
15162             var colorRgb = $.hexToRgb(colori), 
15163                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15164                 endColor = $.rgbToHex(ans);
15165
15166             radialGradient.addColorStop(0, colori);
15167             radialGradient.addColorStop(0.5, colori);
15168             radialGradient.addColorStop(1, endColor);
15169             ctx.fillStyle = radialGradient;
15170           }
15171           
15172           polar.rho = acum + config.sliceOffset;
15173           polar.theta = begin;
15174           var p1coord = polar.getc(true);
15175           polar.theta = end;
15176           var p2coord = polar.getc(true);
15177           polar.rho += dimi;
15178           var p3coord = polar.getc(true);
15179           polar.theta = begin;
15180           var p4coord = polar.getc(true);
15181
15182           ctx.beginPath();
15183           //fixing FF arc method + fill
15184           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15185           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15186           ctx.fill();
15187           if(border && border.name == stringArray[i]) {
15188             opt.acum = acum;
15189             opt.dimValue = dimArray[i];
15190             opt.begin = begin;
15191             opt.end = end;
15192           }
15193           acum += (dimi || 0);
15194           valAcum += (valueArray[i] || 0);
15195         }
15196         if(border) {
15197           ctx.save();
15198           ctx.globalCompositeOperation = "source-over";
15199           ctx.lineWidth = 2;
15200           ctx.strokeStyle = border.color;
15201           var s = begin < end? 1 : -1;
15202           ctx.beginPath();
15203           //fixing FF arc method + fill
15204           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15205           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15206           ctx.closePath();
15207           ctx.stroke();
15208           ctx.restore();
15209         }
15210         if(showLabels && label.type == 'Native') {
15211           ctx.save();
15212           ctx.fillStyle = ctx.strokeStyle = label.color;
15213           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15214               fontSize = (label.size * scale) >> 0;
15215           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15216           
15217           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15218           ctx.textBaseline = 'middle';
15219           ctx.textAlign = 'center';
15220           
15221           polar.rho = acum + config.labelOffset + config.sliceOffset;
15222           polar.theta = node.pos.theta;
15223           var cart = polar.getc(true);
15224           
15225           ctx.fillText(node.name, cart.x, cart.y);
15226           ctx.restore();
15227         }
15228       }
15229     },
15230     'contains': function(node, pos) {
15231       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15232         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15233         var ld = this.config.levelDistance, d = node._depth;
15234         var config = node.getData('config');
15235         if(rho <=ld * d + config.sliceOffset) {
15236           var dimArray = node.getData('dimArray');
15237           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15238             var dimi = dimArray[i];
15239             if(rho >= acum && rho <= acum + dimi) {
15240               return {
15241                 name: node.getData('stringArray')[i],
15242                 color: node.getData('colorArray')[i],
15243                 value: node.getData('valueArray')[i],
15244                 label: node.name
15245               };
15246             }
15247             acum += dimi;
15248           }
15249         }
15250         return false;
15251         
15252       }
15253       return false;
15254     }
15255   },
15256     'piechart-basic' : {
15257     'render' : function(node, canvas) {
15258       var pos = node.pos.getp(true),
15259           dimArray = node.getData('dimArray'),
15260           valueArray = node.getData('valueArray'),
15261           colorArray = node.getData('colorMono'),
15262           colorLength = colorArray.length,
15263           stringArray = node.getData('stringArray'),
15264                   percentage = node.getData('percentage'),
15265                   iteration = node.getData('iteration'),
15266           span = node.getData('span') / 2,
15267           theta = node.pos.theta,
15268           begin = theta - span,
15269           end = theta + span,
15270           polar = new Polar;
15271     
15272       var ctx = canvas.getCtx(), 
15273           opt = {},
15274           gradient = node.getData('gradient'),
15275           border = node.getData('border'),
15276           config = node.getData('config'),
15277           renderSubtitle = node.getData('renderSubtitle'),
15278           renderBackground = config.renderBackground,
15279           showLabels = config.showLabels,
15280           resizeLabels = config.resizeLabels,
15281           label = config.Label;
15282
15283       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15284       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15285       //background rendering for IE
15286                 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15287                         backgroundColor = config.backgroundColor,
15288                         size = canvas.getSize();
15289                         ctx.save();
15290                     ctx.fillStyle = backgroundColor;
15291                     ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15292                     
15293                     //subtitle
15294
15295                         var margin = config.Margin,
15296                         title = config.Title,
15297                         subtitle = config.Subtitle;
15298                         ctx.fillStyle = title.color;
15299                         ctx.textAlign = 'left';
15300                         
15301                         if(title.text != "") {
15302                                 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15303                                 ctx.moveTo(0,0);
15304                                 if(label.type == 'Native') {
15305                                         ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15306                                 }
15307                         }       
15308         
15309                         if(subtitle.text != "") {
15310                                 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15311                                 if(label.type == 'Native') {
15312                                         ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15313                                 } 
15314                         }
15315                         ctx.restore();          
15316                 }
15317       if (colorArray && dimArray && stringArray) {
15318         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15319           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15320           if(dimi <= 0) continue;
15321           ctx.fillStyle = ctx.strokeStyle = colori;
15322           
15323           polar.rho = acum + config.sliceOffset;
15324           polar.theta = begin;
15325           var p1coord = polar.getc(true);
15326           polar.theta = end;
15327           var p2coord = polar.getc(true);
15328           polar.rho += dimi;
15329           var p3coord = polar.getc(true);
15330           polar.theta = begin;
15331           var p4coord = polar.getc(true);
15332           
15333           if(typeof FlashCanvas == "undefined") {
15334                   //drop shadow
15335                   ctx.beginPath();
15336                   ctx.fillStyle = "rgba(0,0,0,.2)";
15337                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15338                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15339                   ctx.fill();
15340                   
15341                   if(gradient && dimi) {
15342                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15343                         xpos, ypos, acum + dimi + config.sliceOffset);
15344                     var colorRgb = $.hexToRgb(colori), 
15345                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15346                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15347         
15348                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15349                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15350                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15351                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15352                     ctx.fillStyle = radialGradient;
15353                   }
15354           }
15355
15356           
15357           //fixing FF arc method + fill
15358           ctx.beginPath();
15359           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15360           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15361           ctx.fill();
15362           if(border && border.name == stringArray[i]) {
15363             opt.acum = acum;
15364             opt.dimValue = dimArray[i];
15365             opt.begin = begin;
15366             opt.end = end;
15367             opt.sliceValue = valueArray[i];
15368           }
15369           acum += (dimi || 0);
15370           valAcum += (valueArray[i] || 0);
15371         }
15372         if(border) {
15373           ctx.save();
15374           ctx.globalCompositeOperation = "source-over";
15375           ctx.lineWidth = 2;
15376           ctx.strokeStyle = border.color;
15377           var s = begin < end? 1 : -1;
15378           ctx.beginPath();
15379           //fixing FF arc method + fill
15380           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15381           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15382           ctx.closePath();
15383           ctx.stroke();
15384           ctx.restore();
15385         }
15386         if(showLabels && label.type == 'Native') {
15387           ctx.save();
15388           ctx.fillStyle = ctx.strokeStyle = label.color;
15389           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15390               fontSize = (label.size * scale) >> 0;
15391           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15392           
15393           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15394           ctx.textBaseline = 'middle';
15395           ctx.textAlign = 'center';
15396           pi = Math.PI;
15397           angle = theta * 360 / (2 * pi);
15398           polar.rho = acum + config.labelOffset + config.sliceOffset;
15399           polar.theta = node.pos.theta;
15400           var cart = polar.getc(true);
15401           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15402                 
15403                 } else {
15404                   if(config.labelType == 'name') {
15405                                 ctx.fillText(node.name, cart.x, cart.y);
15406                           } else {
15407                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15408                           }
15409           }
15410           ctx.restore();
15411         }
15412       }
15413     },
15414     'contains': function(node, pos) {
15415       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15416         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15417         var ld = this.config.levelDistance, d = node._depth;
15418         var config = node.getData('config');
15419
15420         if(rho <=ld * d + config.sliceOffset) {
15421           var dimArray = node.getData('dimArray');
15422           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15423             var dimi = dimArray[i];
15424             if(rho >= acum && rho <= acum + dimi) {
15425                           var url = Url.decode(node.getData('linkArray')[i]);
15426               return {
15427                 name: node.getData('stringArray')[i],
15428                 link: url,
15429                 color: node.getData('colorArray')[i],
15430                 value: node.getData('valueArray')[i],
15431                 percentage: node.getData('percentage'),
15432                 valuelabel: node.getData('valuelabelsArray')[i],
15433                 label: node.name
15434               };
15435             }
15436             acum += dimi;
15437           }
15438         }
15439         return false;
15440         
15441       }
15442       return false;
15443     }
15444   }
15445 });
15446
15447 /*
15448   Class: PieChart
15449   
15450   A visualization that displays stacked bar charts.
15451   
15452   Constructor Options:
15453   
15454   See <Options.PieChart>.
15455
15456 */
15457 $jit.PieChart = new Class({
15458   sb: null,
15459   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15460   selected: {},
15461   busy: false,
15462   
15463   initialize: function(opt) {
15464     this.controller = this.config = 
15465       $.merge(Options("Canvas", "PieChart", "Label"), {
15466         Label: { type: 'Native' }
15467       }, opt);
15468     this.initializeViz();
15469   },
15470   
15471   initializeViz: function() {
15472     var config = this.config, that = this;
15473     var nodeType = config.type.split(":")[0];
15474     var sb = new $jit.Sunburst({
15475       injectInto: config.injectInto,
15476       useCanvas: config.useCanvas,
15477       withLabels: config.Label.type != 'Native',
15478       background: config.background,
15479       renderBackground: config.renderBackground,
15480       backgroundColor: config.backgroundColor,
15481       colorStop1: config.colorStop1,
15482       colorStop2: config.colorStop2,
15483       Label: {
15484         type: config.Label.type
15485       },
15486       Node: {
15487         overridable: true,
15488         type: 'piechart-' + nodeType,
15489         width: 1,
15490         height: 1
15491       },
15492       Edge: {
15493         type: 'none'
15494       },
15495       Tips: {
15496         enable: config.Tips.enable,
15497         type: 'Native',
15498         force: true,
15499         onShow: function(tip, node, contains) {
15500           var elem = contains;
15501           config.Tips.onShow(tip, elem, node);
15502                           if(elem.link != 'undefined' && elem.link != '') {
15503                                 document.body.style.cursor = 'pointer';
15504                           }
15505         },
15506                 onHide: function() {
15507                                 document.body.style.cursor = 'default';
15508         }
15509       },
15510       Events: {
15511         enable: true,
15512         type: 'Native',
15513         onClick: function(node, eventInfo, evt) {
15514           if(!config.Events.enable) return;
15515           var elem = eventInfo.getContains();
15516           config.Events.onClick(elem, eventInfo, evt);
15517         },
15518         onMouseMove: function(node, eventInfo, evt) {
15519           if(!config.hoveredColor) return;
15520           if(node) {
15521             var elem = eventInfo.getContains();
15522             that.select(node.id, elem.name, elem.index);
15523           } else {
15524             that.select(false, false, false);
15525           }
15526         }
15527       },
15528       onCreateLabel: function(domElement, node) {
15529         var labelConf = config.Label;
15530         if(config.showLabels) {
15531           var style = domElement.style;
15532           style.fontSize = labelConf.size + 'px';
15533           style.fontFamily = labelConf.family;
15534           style.color = labelConf.color;
15535           style.textAlign = 'center';
15536           if(config.labelType == 'name') {
15537                 domElement.innerHTML = node.name;
15538           } else {
15539                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15540           }
15541           domElement.style.width = '400px';
15542         }
15543       },
15544       onPlaceLabel: function(domElement, node) {
15545         if(!config.showLabels) return;
15546         var pos = node.pos.getp(true),
15547             dimArray = node.getData('dimArray'),
15548             span = node.getData('span') / 2,
15549             theta = node.pos.theta,
15550             begin = theta - span,
15551             end = theta + span,
15552             polar = new Polar;
15553
15554         var showLabels = config.showLabels,
15555             resizeLabels = config.resizeLabels,
15556             label = config.Label;
15557         
15558         if (dimArray) {
15559           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15560             acum += dimArray[i];
15561           }
15562           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15563               fontSize = (label.size * scale) >> 0;
15564           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15565           domElement.style.fontSize = fontSize + 'px';
15566           polar.rho = acum + config.labelOffset + config.sliceOffset;
15567           polar.theta = (begin + end) / 2;
15568           var pos = polar.getc(true);
15569           var radius = that.canvas.getSize();
15570           var labelPos = {
15571             x: Math.round(pos.x + radius.width / 2),
15572             y: Math.round(pos.y + radius.height / 2)
15573           };
15574           domElement.style.left = (labelPos.x - 200) + 'px';
15575           domElement.style.top = labelPos.y + 'px';
15576         }
15577       }
15578     });
15579     
15580     var size = sb.canvas.getSize(),
15581         min = Math.min;
15582     sb.config.levelDistance = min(size.width, size.height)/2 
15583       - config.offset - config.sliceOffset;
15584     this.sb = sb;
15585     this.canvas = this.sb.canvas;
15586     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15587   },
15588     renderBackground: function() {
15589                 var canvas = this.canvas,
15590                 config = this.config,
15591                 backgroundColor = config.backgroundColor,
15592                 size = canvas.getSize(),
15593                 ctx = canvas.getCtx();
15594                 ctx.globalCompositeOperation = "destination-over";
15595             ctx.fillStyle = backgroundColor;
15596             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15597   },
15598    renderTitle: function() {
15599         var canvas = this.canvas,
15600         size = canvas.getSize(),
15601         config = this.config,
15602         margin = config.Margin,
15603         radius = this.sb.config.levelDistance,
15604         title = config.Title,
15605         label = config.Label,
15606         subtitle = config.Subtitle;
15607         ctx = canvas.getCtx();
15608         ctx.fillStyle = title.color;
15609         ctx.textAlign = 'left';
15610         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15611         ctx.moveTo(0,0);
15612         if(label.type == 'Native') {
15613                 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15614         }       
15615   },
15616   renderSubtitle: function() {
15617         var canvas = this.canvas,
15618         size = canvas.getSize(),
15619         config = this.config,
15620         margin = config.Margin,
15621         radius = this.sb.config.levelDistance,
15622         title = config.Title,
15623         label = config.Label,
15624         subtitle = config.Subtitle;
15625         ctx = canvas.getCtx();
15626         ctx.fillStyle = title.color;
15627         ctx.textAlign = 'left';
15628         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15629         ctx.moveTo(0,0);
15630         if(label.type == 'Native') {
15631                 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15632         }       
15633   },
15634   clear: function() {
15635         var canvas = this.canvas;
15636         var ctx = canvas.getCtx(),
15637         size = canvas.getSize();
15638         ctx.fillStyle = "rgba(255,255,255,0)";
15639         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15640         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15641   },
15642   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
15643         var canvas = this.canvas,
15644         size = canvas.getSize(),
15645         config = this.config,
15646         orgHeight = size.height,
15647         margin = config.Margin,
15648         st = this.st,
15649         horz = config.orientation == 'horizontal';
15650         
15651
15652         var newWindowWidth = document.body.offsetWidth;
15653         var diff = newWindowWidth - orgWindowWidth;     
15654         var newWidth = orgContainerDivWidth + (diff/cols);
15655         var scale = newWidth/orgContainerDivWidth;
15656         canvas.resize(newWidth,orgHeight);
15657         if(typeof FlashCanvas == "undefined") {
15658                 canvas.clear();
15659         } else {
15660                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15661         }
15662         this.loadJSON(json);
15663
15664         },
15665   /*
15666     Method: loadJSON
15667    
15668     Loads JSON data into the visualization. 
15669     
15670     Parameters:
15671     
15672     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>.
15673     
15674     Example:
15675     (start code js)
15676     var pieChart = new $jit.PieChart(options);
15677     pieChart.loadJSON(json);
15678     (end code)
15679   */  
15680   loadJSON: function(json) {
15681     var prefix = $.time(), 
15682         ch = [], 
15683         sb = this.sb,
15684         name = $.splat(json.label),
15685         nameLength = name.length,
15686         color = $.splat(json.color || this.colors),
15687         colorLength = color.length,
15688         config = this.config,
15689         renderBackground = config.renderBackground,
15690         title = config.Title,
15691                 subtitle = config.Subtitle,
15692         gradient = !!config.type.split(":")[1],
15693         animate = config.animate,
15694         mono = nameLength == 1;
15695         totalValue = 0;
15696     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15697         var val = values[i];
15698         var valArray = $.splat(val.values);
15699         totalValue += parseFloat(valArray.sum());
15700     }
15701
15702     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15703       var val = values[i];
15704       var valArray = $.splat(val.values);
15705           var percentage = (valArray.sum()/totalValue) * 100;
15706
15707       var linkArray = $.splat(val.links);
15708       var valuelabelsArray = $.splat(val.valuelabels);
15709       
15710  
15711       ch.push({
15712         'id': prefix + val.label,
15713         'name': val.label,
15714         'data': {
15715           'value': valArray,
15716           'valuelabel': valuelabelsArray,
15717           '$linkArray': linkArray,
15718           '$valuelabelsArray': valuelabelsArray,
15719           '$valueArray': valArray,
15720           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15721           '$colorMono': $.splat(color[i % colorLength]),
15722           '$stringArray': name,
15723           '$gradient': gradient,
15724           '$config': config,
15725           '$iteration': i,
15726           '$percentage': percentage.toFixed(1),
15727           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15728         },
15729         'children': []
15730       });
15731     }
15732     var root = {
15733       'id': prefix + '$root',
15734       'name': '',
15735       'data': {
15736         '$type': 'none',
15737         '$width': 1,
15738         '$height': 1
15739       },
15740       'children': ch
15741     };
15742     sb.loadJSON(root);
15743     
15744     
15745     this.normalizeDims();
15746
15747     
15748     sb.refresh();
15749     if(title.text != "") {
15750         this.renderTitle();
15751     }
15752        
15753     if(subtitle.text != "") {
15754         this.renderSubtitle();
15755     }
15756      if(renderBackground && typeof FlashCanvas == "undefined") {
15757         this.renderBackground();        
15758     }
15759     
15760     if(animate) {
15761       sb.fx.animate({
15762         modes: ['node-property:dimArray'],
15763         duration:1500
15764       });
15765     }
15766   },
15767   
15768   /*
15769     Method: updateJSON
15770    
15771     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.
15772     
15773     Parameters:
15774     
15775     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15776     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15777     
15778     Example:
15779     
15780     (start code js)
15781     pieChart.updateJSON(json, {
15782       onComplete: function() {
15783         alert('update complete!');
15784       }
15785     });
15786     (end code)
15787   */  
15788   updateJSON: function(json, onComplete) {
15789     if(this.busy) return;
15790     this.busy = true;
15791     
15792     var sb = this.sb;
15793     var graph = sb.graph;
15794     var values = json.values;
15795     var animate = this.config.animate;
15796     var that = this;
15797     $.each(values, function(v) {
15798       var n = graph.getByName(v.label),
15799           vals = $.splat(v.values);
15800       if(n) {
15801         n.setData('valueArray', vals);
15802         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15803         if(json.label) {
15804           n.setData('stringArray', $.splat(json.label));
15805         }
15806       }
15807     });
15808     this.normalizeDims();
15809     if(animate) {
15810       sb.compute('end');
15811       sb.fx.animate({
15812         modes: ['node-property:dimArray:span', 'linear'],
15813         duration:1500,
15814         onComplete: function() {
15815           that.busy = false;
15816           onComplete && onComplete.onComplete();
15817         }
15818       });
15819     } else {
15820       sb.refresh();
15821     }
15822   },
15823     
15824   //adds the little brown bar when hovering the node
15825   select: function(id, name) {
15826     if(!this.config.hoveredColor) return;
15827     var s = this.selected;
15828     if(s.id != id || s.name != name) {
15829       s.id = id;
15830       s.name = name;
15831       s.color = this.config.hoveredColor;
15832       this.sb.graph.eachNode(function(n) {
15833         if(id == n.id) {
15834           n.setData('border', s);
15835         } else {
15836           n.setData('border', false);
15837         }
15838       });
15839       this.sb.plot();
15840     }
15841   },
15842   
15843   /*
15844     Method: getLegend
15845    
15846     Returns an object containing as keys the legend names and as values hex strings with color values.
15847     
15848     Example:
15849     
15850     (start code js)
15851     var legend = pieChart.getLegend();
15852     (end code)
15853   */  
15854   getLegend: function() {
15855     var legend = new Array();
15856     var name = new Array();
15857     var color = new Array();
15858     var n;
15859     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15860       n = adj.nodeTo;
15861     });
15862     var colors = n.getData('colorArray'),
15863         len = colors.length;
15864     $.each(n.getData('stringArray'), function(s, i) {
15865       color[i] = colors[i % len];
15866       name[i] = s;
15867     });
15868         legend['name'] = name;
15869         legend['color'] = color;
15870     return legend;
15871   },
15872   
15873   /*
15874     Method: getMaxValue
15875    
15876     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15877     
15878     Example:
15879     
15880     (start code js)
15881     var ans = pieChart.getMaxValue();
15882     (end code)
15883     
15884     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15885     
15886     Example:
15887     
15888     (start code js)
15889     //will return 100 for all PieChart instances,
15890     //displaying all of them with the same scale
15891     $jit.PieChart.implement({
15892       'getMaxValue': function() {
15893         return 100;
15894       }
15895     });
15896     (end code)
15897     
15898   */  
15899   getMaxValue: function() {
15900     var maxValue = 0;
15901     this.sb.graph.eachNode(function(n) {
15902       var valArray = n.getData('valueArray'),
15903           acum = 0;
15904       $.each(valArray, function(v) { 
15905         acum += +v;
15906       });
15907       maxValue = maxValue>acum? maxValue:acum;
15908     });
15909     return maxValue;
15910   },
15911   
15912   normalizeDims: function() {
15913     //number of elements
15914     var root = this.sb.graph.getNode(this.sb.root), l=0;
15915     root.eachAdjacency(function() {
15916       l++;
15917     });
15918     var maxValue = this.getMaxValue() || 1,
15919         config = this.config,
15920         animate = config.animate,
15921         rho = this.sb.config.levelDistance;
15922     this.sb.graph.eachNode(function(n) {
15923       var acum = 0, animateValue = [];
15924       $.each(n.getData('valueArray'), function(v) {
15925         acum += +v;
15926         animateValue.push(1);
15927       });
15928       var stat = (animateValue.length == 1) && !config.updateHeights;
15929       if(animate) {
15930         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15931           return stat? rho: (n * rho / maxValue); 
15932         }), 'end');
15933         var dimArray = n.getData('dimArray');
15934         if(!dimArray) {
15935           n.setData('dimArray', animateValue);
15936         }
15937       } else {
15938         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15939           return stat? rho : (n * rho / maxValue); 
15940         }));
15941       }
15942       n.setData('normalizedDim', acum / maxValue);
15943     });
15944   }
15945 });
15946
15947
15948 //Gauge Chart
15949
15950 Options.GaugeChart = {
15951   $extend: true,
15952
15953   animate: true,
15954   offset: 25, // page offset
15955   sliceOffset:0,
15956   labelOffset: 3, // label offset
15957   type: 'stacked', // gradient
15958   labelType: 'name',
15959   hoveredColor: '#9fd4ff',
15960   Events: {
15961     enable: false,
15962     onClick: $.empty
15963   },
15964   Tips: {
15965     enable: false,
15966     onShow: $.empty,
15967     onHide: $.empty
15968   },
15969   showLabels: true,
15970   resizeLabels: false,
15971   
15972   //only valid for mono-valued datasets
15973   updateHeights: false
15974 };
15975
15976
15977
15978 $jit.Sunburst.Plot.NodeTypes.implement({
15979     'gaugechart-basic' : {
15980     'render' : function(node, canvas) {
15981       var pos = node.pos.getp(true),
15982           dimArray = node.getData('dimArray'),
15983           valueArray = node.getData('valueArray'),
15984           valuelabelsArray = node.getData('valuelabelsArray'),
15985           gaugeTarget = node.getData('gaugeTarget'),
15986           nodeIteration = node.getData('nodeIteration'),
15987           nodeLength = node.getData('nodeLength'),
15988           colorArray = node.getData('colorMono'),
15989           colorLength = colorArray.length,
15990           stringArray = node.getData('stringArray'),
15991           span = node.getData('span') / 2,
15992           theta = node.pos.theta,
15993           begin = ((theta - span)/2)+Math.PI,
15994           end = ((theta + span)/2)+Math.PI,
15995           polar = new Polar;
15996
15997   
15998       var ctx = canvas.getCtx(), 
15999           opt = {},
16000           gradient = node.getData('gradient'),
16001           border = node.getData('border'),
16002           config = node.getData('config'),
16003           showLabels = config.showLabels,
16004           resizeLabels = config.resizeLabels,
16005           label = config.Label;
16006
16007       var xpos = Math.cos((begin + end) /2);
16008       var ypos = Math.sin((begin + end) /2);
16009
16010       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16011         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16012           var dimi = dimArray[i], colori = colorArray[i % colorLength];
16013           if(dimi <= 0) continue;
16014           ctx.fillStyle = ctx.strokeStyle = colori;
16015           if(gradient && dimi) {
16016             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16017                 xpos, (ypos + dimi/2), acum + dimi);
16018             var colorRgb = $.hexToRgb(colori), 
16019                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16020                 endColor = $.rgbToHex(ans);
16021
16022             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16023             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16024             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16025             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
16026             ctx.fillStyle = radialGradient;
16027           }
16028           
16029           polar.rho = acum;
16030           polar.theta = begin;
16031           var p1coord = polar.getc(true);
16032           polar.theta = end;
16033           var p2coord = polar.getc(true);
16034           polar.rho += dimi;
16035           var p3coord = polar.getc(true);
16036           polar.theta = begin;
16037           var p4coord = polar.getc(true);
16038
16039                   
16040           ctx.beginPath();
16041           //fixing FF arc method + fill
16042           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16043           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16044           ctx.fill();
16045                   
16046
16047           acum += (dimi || 0);
16048           valAcum += (valueArray[i] || 0);                
16049         }
16050                 
16051                 if(showLabels && label.type == 'Native') {
16052                           ctx.save();
16053                           ctx.fillStyle = ctx.strokeStyle = label.color;
16054
16055                           
16056                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16057                           ctx.textBaseline = 'bottom';
16058                           ctx.textAlign = 'center';
16059
16060                           polar.rho = acum * .65;
16061                           polar.theta = begin;
16062                           var cart = polar.getc(true);
16063                           
16064                           //changes y pos of first label
16065                           if(nodeIteration == 1) {
16066                                 textY = cart.y - (label.size/2) + acum /2;
16067                           } else {
16068                                 textY = cart.y + acum/2;
16069                           }
16070                           
16071                           if(config.labelType == 'name') {
16072                                 ctx.fillText(node.name, cart.x, textY);
16073                           } else {
16074                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16075                           }
16076                           
16077                           //adds final label
16078                           if(nodeIteration == nodeLength) {
16079                                 polar.theta = end;
16080                                 var cart = polar.getc(true);
16081                                 if(config.labelType == 'name') {
16082                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16083                                 } else {
16084                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16085                                 }
16086                                 
16087                           }
16088                           ctx.restore();
16089                 }
16090
16091       }
16092       },
16093     'contains': function(node, pos) {
16094                 
16095                 
16096                 
16097       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16098                 var config = node.getData('config');
16099         var ld = this.config.levelDistance , d = node._depth;
16100                 var yOffset = pos.y - (ld/2);
16101                 var xOffset = pos.x;
16102         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16103         if(rho <=parseInt(ld * d)) {
16104           var dimArray = node.getData('dimArray');
16105           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16106                         var dimi = dimArray[i];
16107             if(rho >= ld * .8 && rho <= acum + dimi) {
16108                 
16109                           var url = Url.decode(node.getData('linkArray')[i]);
16110               return {
16111                 name: node.getData('stringArray')[i],
16112                 link: url,
16113                 color: node.getData('colorArray')[i],
16114                 value: node.getData('valueArray')[i],
16115                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16116                 label: node.name
16117               };
16118             }
16119             acum += dimi;
16120                         
16121                         
16122           }
16123         }
16124         return false;
16125         
16126       }
16127       return false;
16128     }
16129   }
16130 });
16131
16132 /*
16133   Class: GaugeChart
16134   
16135   A visualization that displays gauge charts
16136   
16137   Constructor Options:
16138   
16139   See <Options.Gauge>.
16140
16141 */
16142 $jit.GaugeChart = new Class({
16143   sb: null,
16144   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16145   selected: {},
16146   busy: false,
16147   
16148   initialize: function(opt) {
16149     this.controller = this.config = 
16150       $.merge(Options("Canvas", "GaugeChart", "Label"), {
16151         Label: { type: 'Native' }
16152       }, opt);
16153     this.initializeViz();
16154   },
16155   
16156   initializeViz: function() {
16157     var config = this.config, that = this;
16158     var nodeType = config.type.split(":")[0];
16159     var sb = new $jit.Sunburst({
16160       injectInto: config.injectInto,
16161       useCanvas: config.useCanvas,
16162       withLabels: config.Label.type != 'Native',
16163       background: config.background,
16164       renderBackground: config.renderBackground,
16165       backgroundColor: config.backgroundColor,
16166       colorStop1: config.colorStop1,
16167       colorStop2: config.colorStop2,
16168       Label: {
16169         type: config.Label.type
16170       },
16171       Node: {
16172         overridable: true,
16173         type: 'gaugechart-' + nodeType,
16174         width: 1,
16175         height: 1
16176       },
16177       Edge: {
16178         type: 'none'
16179       },
16180       Tips: {
16181         enable: config.Tips.enable,
16182         type: 'Native',
16183         force: true,
16184         onShow: function(tip, node, contains) {
16185           var elem = contains;
16186           config.Tips.onShow(tip, elem, node);
16187                           if(elem.link != 'undefined' && elem.link != '') {
16188                                 document.body.style.cursor = 'pointer';
16189                           }
16190         },
16191                 onHide: function() {
16192                                 document.body.style.cursor = 'default';
16193         }
16194       },
16195       Events: {
16196         enable: true,
16197         type: 'Native',
16198         onClick: function(node, eventInfo, evt) {
16199           if(!config.Events.enable) return;
16200           var elem = eventInfo.getContains();
16201           config.Events.onClick(elem, eventInfo, evt);
16202         }
16203         },
16204       onCreateLabel: function(domElement, node) {
16205         var labelConf = config.Label;
16206         if(config.showLabels) {
16207           var style = domElement.style;
16208           style.fontSize = labelConf.size + 'px';
16209           style.fontFamily = labelConf.family;
16210           style.color = labelConf.color;
16211           style.textAlign = 'center';
16212           valuelabelsArray = node.getData('valuelabelsArray'),
16213           nodeIteration = node.getData('nodeIteration'),
16214           nodeLength = node.getData('nodeLength'),
16215           canvas = sb.canvas,
16216           prefix = $.time();
16217           
16218           if(config.labelType == 'name') {
16219                 domElement.innerHTML = node.name;
16220           } else {
16221                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16222           }
16223           
16224           domElement.style.width = '400px';
16225           
16226           //adds final label
16227                   if(nodeIteration == nodeLength && nodeLength != 0) {
16228                   idLabel = canvas.id + "-label";
16229                   container = document.getElementById(idLabel);
16230                   finalLabel = document.createElement('div');
16231                   finalLabelStyle = finalLabel.style;
16232                   finalLabel.id = prefix + "finalLabel";
16233                   finalLabelStyle.position = "absolute";
16234                   finalLabelStyle.width = "400px";
16235                   finalLabelStyle.left = "0px";
16236                   container.appendChild(finalLabel);
16237                         if(config.labelType == 'name') {
16238                                 finalLabel.innerHTML = node.name;
16239                         } else {
16240                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16241                         }
16242                         
16243                   }
16244         }
16245       },
16246       onPlaceLabel: function(domElement, node) {
16247         if(!config.showLabels) return;
16248         var pos = node.pos.getp(true),
16249             dimArray = node.getData('dimArray'),
16250             nodeIteration = node.getData('nodeIteration'),
16251             nodeLength = node.getData('nodeLength'),
16252             span = node.getData('span') / 2,
16253             theta = node.pos.theta,
16254             begin = ((theta - span)/2)+Math.PI,
16255             end = ((theta + span)/2)+Math.PI,
16256             polar = new Polar;
16257
16258         var showLabels = config.showLabels,
16259             resizeLabels = config.resizeLabels,
16260             label = config.Label,
16261             radiusOffset = sb.config.levelDistance;
16262
16263         if (dimArray) {
16264           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16265             acum += dimArray[i];
16266           }
16267           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16268               fontSize = (label.size * scale) >> 0;
16269           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16270           domElement.style.fontSize = fontSize + 'px';
16271           polar.rho = acum * .65;
16272           polar.theta = begin;
16273           var pos = polar.getc(true);
16274           var radius = that.canvas.getSize();
16275           var labelPos = {
16276             x: Math.round(pos.x + radius.width / 2),
16277             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16278           };
16279           
16280
16281           
16282           domElement.style.left = (labelPos.x - 200) + 'px';
16283           domElement.style.top = labelPos.y + 'px';
16284           
16285           //reposition first label
16286           if(nodeIteration == 1) {
16287                  domElement.style.top = labelPos.y - label.size + 'px';
16288           }
16289           
16290           
16291           //position final label
16292                 if(nodeIteration == nodeLength && nodeLength != 0) {
16293                         polar.theta = end;
16294                         var final = polar.getc(true);
16295                         var finalPos = {
16296                                         x: Math.round(final.x + radius.width / 2),
16297                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16298                                 };
16299                         finalLabel.style.left = (finalPos.x - 200) + "px";
16300                         finalLabel.style.top = finalPos.y - label.size + "px";
16301                     }
16302           
16303         }
16304       }
16305    
16306     });
16307     this.sb = sb;
16308     this.canvas = this.sb.canvas;
16309     var size = sb.canvas.getSize(),
16310         min = Math.min;
16311         sb.config.levelDistance = min(size.width, size.height)/2 
16312       - config.offset - config.sliceOffset;
16313
16314
16315   },
16316         
16317   renderBackground: function() {
16318         var canvas = this.sb.canvas,
16319         config = this.config,
16320         style = config.gaugeStyle,
16321         ctx = canvas.getCtx(),
16322         size = canvas.getSize(),
16323         radius = this.sb.config.levelDistance,
16324         startAngle = (Math.PI/180)*1,
16325         endAngle = (Math.PI/180)*179;
16326         
16327
16328         //background border
16329         ctx.fillStyle = style.borderColor;      
16330         ctx.beginPath();
16331         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16332         ctx.fill(); 
16333         
16334         
16335         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16336         radialGradient.addColorStop(0, '#ffffff');  
16337         radialGradient.addColorStop(0.3, style.backgroundColor);  
16338         radialGradient.addColorStop(0.6, style.backgroundColor);  
16339         radialGradient.addColorStop(1, '#FFFFFF'); 
16340         ctx.fillStyle = radialGradient;
16341         
16342         //background
16343         startAngle = (Math.PI/180)*0;
16344         endAngle = (Math.PI/180)*180;
16345         ctx.beginPath();
16346         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16347         ctx.fill();     
16348         
16349         
16350  
16351   },
16352   
16353   
16354   renderNeedle: function(gaugePosition,target) {
16355         var canvas = this.sb.canvas,
16356         config = this.config,
16357         style = config.gaugeStyle,
16358         ctx = canvas.getCtx(),
16359         size = canvas.getSize(),
16360         radius = this.sb.config.levelDistance;
16361         gaugeCenter = (radius/2);
16362         startAngle = 0;
16363         endAngle = (Math.PI/180)*180;
16364         
16365         
16366         // needle
16367         ctx.fillStyle = style.needleColor;
16368         var segments = 180/target;
16369         needleAngle = gaugePosition * segments;
16370         ctx.translate(0, gaugeCenter);
16371         ctx.save();
16372         ctx.rotate(needleAngle * Math.PI / 180);  
16373         ctx.beginPath();
16374         ctx.moveTo(0,0); 
16375         ctx.lineTo(0,-4);  
16376         ctx.lineTo(-radius*.9,-1);  
16377         ctx.lineTo(-radius*.9,1);  
16378         ctx.lineTo(0,4);  
16379         ctx.lineTo(0,0);  
16380         ctx.closePath(); 
16381         ctx.fill();
16382         ctx.restore(); 
16383         
16384         
16385         // stroke needle
16386         ctx.lineWidth = 1;
16387         ctx.strokeStyle = '#aa0000';
16388         ctx.save();
16389         ctx.rotate(needleAngle * Math.PI / 180);  
16390         ctx.beginPath();
16391         ctx.moveTo(0,0); 
16392         ctx.lineTo(0,-4);  
16393         ctx.lineTo(-radius*.8,-1);  
16394         ctx.lineTo(-radius*.8,1);  
16395         ctx.lineTo(0,4);  
16396         ctx.lineTo(0,0);  
16397         ctx.closePath(); 
16398         ctx.stroke();
16399         ctx.restore(); 
16400
16401         //needle cap
16402         ctx.fillStyle = "#000000";
16403         ctx.lineWidth = style.borderSize;
16404     ctx.strokeStyle = style.borderColor;
16405         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16406         radialGradient.addColorStop(0, '#666666');  
16407         radialGradient.addColorStop(0.8, '#444444');  
16408         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16409         ctx.fillStyle = radialGradient;
16410         ctx.translate(0,5);
16411         ctx.save();
16412         ctx.beginPath();
16413         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16414         ctx.fill();     
16415         ctx.restore();
16416
16417         
16418   },
16419   
16420   renderTicks: function(values) {
16421         var canvas = this.sb.canvas,
16422         config = this.config,
16423         style = config.gaugeStyle,
16424         ctx = canvas.getCtx(),
16425         size = canvas.getSize(),
16426         radius = this.sb.config.levelDistance,
16427         gaugeCenter = (radius/2);
16428         
16429         
16430         ctx.strokeStyle = style.borderColor;
16431         ctx.lineWidth = 5;
16432         ctx.lineCap = "round";
16433                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16434                         var val = values[i];
16435                         if(val.label != 'GaugePosition') {
16436                         total += (parseInt(val.values) || 0);
16437                         }
16438                 }
16439         
16440                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16441                         var val = values[i];
16442                         if(val.label != 'GaugePosition') {
16443                         acum += (parseInt(val.values) || 0);
16444
16445                            var segments = 180/total;
16446                         angle = acum * segments;
16447
16448                           //alert(acum);
16449                                  ctx.save();
16450                                  ctx.translate(0, gaugeCenter);
16451                                  ctx.beginPath();
16452                                 ctx.rotate(angle * (Math.PI/180));
16453                                 ctx.moveTo(-radius,0);
16454                                 ctx.lineTo(-radius*.75,0);
16455                                 ctx.stroke();
16456                                  ctx.restore();
16457                         
16458                         }
16459                 }
16460         },
16461         
16462         renderPositionLabel: function(position) {
16463                 var canvas = this.sb.canvas,
16464                 config = this.config,
16465                 label = config.Label,
16466                 style = config.gaugeStyle,
16467                 ctx = canvas.getCtx(),
16468                 size = canvas.getSize(),
16469                 radius = this.sb.config.levelDistance,
16470                 gaugeCenter = (radius/2);
16471                 ctx.textBaseline = 'middle';
16472                 ctx.textAlign = 'center';
16473                 ctx.font = style.positionFontSize + 'px ' + label.family;
16474                 ctx.fillStyle = "#ffffff";
16475                 ctx.lineWidth = 2;
16476                 height = style.positionFontSize + 10,
16477                 cornerRadius = 8,
16478                 idLabel = canvas.id + "-label";
16479                 container = document.getElementById(idLabel);
16480                 if(label.type == 'Native') {
16481                         var m = ctx.measureText(position),
16482                         width = m.width + 40;
16483                 } else {
16484                         var width = 70;
16485                 }
16486                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16487                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16488                 if(label.type == 'Native') {
16489                         ctx.fillStyle = label.color;
16490                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16491                 } else {
16492                         var labelDiv =  document.createElement('div');
16493                         labelDivStyle = labelDiv.style;
16494                         labelDivStyle.color =  label.color;
16495                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16496                         labelDivStyle.position = "absolute";
16497                         labelDivStyle.width = width + "px";
16498                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16499                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16500                         labelDiv.innerHTML = position;
16501                         container.appendChild(labelDiv);
16502                 }
16503         
16504     },
16505     
16506    renderSubtitle: function() {
16507         var canvas = this.canvas,
16508         size = canvas.getSize(),
16509         config = this.config,
16510         margin = config.Margin,
16511         radius = this.sb.config.levelDistance,
16512         title = config.Title,
16513         label = config.Label,
16514         subtitle = config.Subtitle;
16515         ctx = canvas.getCtx();
16516         ctx.fillStyle = title.color;
16517         ctx.textAlign = 'left';
16518         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16519         ctx.moveTo(0,0);
16520         if(label.type == 'Native') {
16521                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2)); 
16522         }
16523   },
16524   
16525   renderChartBackground: function() {
16526                 var canvas = this.canvas,
16527                 config = this.config,
16528                 backgroundColor = config.backgroundColor,
16529                 size = canvas.getSize(),
16530                 ctx = canvas.getCtx();
16531                 //ctx.globalCompositeOperation = "destination-over";
16532             ctx.fillStyle = backgroundColor;
16533             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16534   },
16535   clear: function() {
16536         var canvas = this.canvas;
16537         var ctx = canvas.getCtx(),
16538         size = canvas.getSize();
16539         ctx.fillStyle = "rgba(255,255,255,0)";
16540         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16541         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16542  },
16543   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
16544         var canvas = this.canvas,
16545         size = canvas.getSize(),
16546         config = this.config,
16547         orgHeight = size.height,
16548         margin = config.Margin,
16549         st = this.st,
16550         horz = config.orientation == 'horizontal';
16551         
16552
16553         var newWindowWidth = document.body.offsetWidth;
16554         var diff = newWindowWidth - orgWindowWidth;     
16555         var newWidth = orgContainerDivWidth + (diff/cols);
16556         canvas.resize(newWidth,orgHeight);
16557         if(typeof FlashCanvas == "undefined") {
16558                 canvas.clear();
16559         } else {
16560                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16561         }
16562         this.loadJSON(json);
16563
16564         },
16565   loadJSON: function(json) {
16566   
16567      var prefix = $.time(), 
16568         ch = [], 
16569         sb = this.sb,
16570         name = $.splat(json.label),
16571         nameLength = name.length,
16572         color = $.splat(json.color || this.colors),
16573         colorLength = color.length,
16574         config = this.config,
16575         renderBackground = config.renderBackground,
16576         gradient = !!config.type.split(":")[1],
16577         animate = config.animate,
16578         mono = nameLength == 1;
16579                 var props = $.splat(json.properties)[0];
16580
16581     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16582         
16583       var val = values[i];
16584           if(val.label != 'GaugePosition') {
16585                   var valArray = $.splat(val.values);
16586                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16587                   var valuelabelsArray = $.splat(val.valuelabels);
16588
16589                   ch.push({
16590                         'id': prefix + val.label,
16591                         'name': val.label,
16592                         'data': {
16593                           'value': valArray,
16594                           'valuelabel': valuelabelsArray,
16595                           '$linkArray': linkArray,
16596                           '$valuelabelsArray': valuelabelsArray,
16597                           '$valueArray': valArray,
16598                           '$nodeIteration': i,
16599                           '$nodeLength': l-1,
16600                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16601                           '$colorMono': $.splat(color[i % colorLength]),
16602                           '$stringArray': name,
16603                           '$gradient': gradient,
16604                           '$config': config,
16605                           '$gaugeTarget': props['gaugeTarget'],
16606                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16607                         },
16608                         'children': []
16609                   });
16610           } else {
16611                 var gaugePosition = val.gvalue;
16612                 var gaugePositionLabel = val.gvaluelabel;
16613           }
16614     }
16615     var root = {
16616       'id': prefix + '$root',
16617       'name': '',
16618       'data': {
16619         '$type': 'none',
16620         '$width': 1,
16621         '$height': 1
16622       },
16623       'children': ch
16624     };
16625         
16626         
16627     sb.loadJSON(root);
16628     
16629     if(renderBackground) {
16630         this.renderChartBackground();   
16631     }
16632     
16633     this.renderBackground();
16634     this.renderSubtitle();
16635     
16636     this.normalizeDims();
16637         
16638     sb.refresh();
16639     if(animate) {
16640       sb.fx.animate({
16641         modes: ['node-property:dimArray'],
16642         duration:1500
16643       });
16644     }
16645         
16646
16647         this.renderPositionLabel(gaugePositionLabel);
16648         if (props['gaugeTarget'] != 0) {
16649                 this.renderTicks(json.values);
16650                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16651         }
16652         
16653         
16654
16655   },
16656   
16657   updateJSON: function(json, onComplete) {
16658     if(this.busy) return;
16659     this.busy = true;
16660     
16661     var sb = this.sb;
16662     var graph = sb.graph;
16663     var values = json.values;
16664     var animate = this.config.animate;
16665     var that = this;
16666     $.each(values, function(v) {
16667       var n = graph.getByName(v.label),
16668           vals = $.splat(v.values);
16669       if(n) {
16670         n.setData('valueArray', vals);
16671         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16672         if(json.label) {
16673           n.setData('stringArray', $.splat(json.label));
16674         }
16675       }
16676     });
16677     this.normalizeDims();
16678     if(animate) {
16679       sb.compute('end');
16680       sb.fx.animate({
16681         modes: ['node-property:dimArray:span', 'linear'],
16682         duration:1500,
16683         onComplete: function() {
16684           that.busy = false;
16685           onComplete && onComplete.onComplete();
16686         }
16687       });
16688     } else {
16689       sb.refresh();
16690     }
16691   },
16692     
16693   //adds the little brown bar when hovering the node
16694   select: function(id, name) {
16695     if(!this.config.hoveredColor) return;
16696     var s = this.selected;
16697     if(s.id != id || s.name != name) {
16698       s.id = id;
16699       s.name = name;
16700       s.color = this.config.hoveredColor;
16701       this.sb.graph.eachNode(function(n) {
16702         if(id == n.id) {
16703           n.setData('border', s);
16704         } else {
16705           n.setData('border', false);
16706         }
16707       });
16708       this.sb.plot();
16709     }
16710   },
16711   
16712   /*
16713     Method: getLegend
16714    
16715     Returns an object containing as keys the legend names and as values hex strings with color values.
16716     
16717     Example:
16718     
16719     (start code js)
16720     var legend = pieChart.getLegend();
16721     (end code)
16722   */  
16723   getLegend: function() {
16724     var legend = new Array();
16725     var name = new Array();
16726     var color = new Array();
16727     var n;
16728     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16729       n = adj.nodeTo;
16730     });
16731     var colors = n.getData('colorArray'),
16732         len = colors.length;
16733     $.each(n.getData('stringArray'), function(s, i) {
16734       color[i] = colors[i % len];
16735       name[i] = s;
16736     });
16737         legend['name'] = name;
16738         legend['color'] = color;
16739     return legend;
16740   },
16741   
16742   /*
16743     Method: getMaxValue
16744    
16745     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16746     
16747     Example:
16748     
16749     (start code js)
16750     var ans = pieChart.getMaxValue();
16751     (end code)
16752     
16753     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16754     
16755     Example:
16756     
16757     (start code js)
16758     //will return 100 for all PieChart instances,
16759     //displaying all of them with the same scale
16760     $jit.PieChart.implement({
16761       'getMaxValue': function() {
16762         return 100;
16763       }
16764     });
16765     (end code)
16766     
16767   */  
16768   getMaxValue: function() {
16769     var maxValue = 0;
16770     this.sb.graph.eachNode(function(n) {
16771       var valArray = n.getData('valueArray'),
16772           acum = 0;
16773       $.each(valArray, function(v) { 
16774         acum += +v;
16775       });
16776       maxValue = maxValue>acum? maxValue:acum;
16777     });
16778     return maxValue;
16779   },
16780   
16781   normalizeDims: function() {
16782     //number of elements
16783     var root = this.sb.graph.getNode(this.sb.root), l=0;
16784     root.eachAdjacency(function() {
16785       l++;
16786     });
16787     var maxValue = this.getMaxValue() || 1,
16788         config = this.config,
16789         animate = config.animate,
16790         rho = this.sb.config.levelDistance;
16791     this.sb.graph.eachNode(function(n) {
16792       var acum = 0, animateValue = [];
16793       $.each(n.getData('valueArray'), function(v) {
16794         acum += +v;
16795         animateValue.push(1);
16796       });
16797       var stat = (animateValue.length == 1) && !config.updateHeights;
16798       if(animate) {
16799         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16800           return stat? rho: (n * rho / maxValue); 
16801         }), 'end');
16802         var dimArray = n.getData('dimArray');
16803         if(!dimArray) {
16804           n.setData('dimArray', animateValue);
16805         }
16806       } else {
16807         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16808           return stat? rho : (n * rho / maxValue); 
16809         }));
16810       }
16811       n.setData('normalizedDim', acum / maxValue);
16812     });
16813   }
16814 });
16815
16816
16817 /*
16818  * Class: Layouts.TM
16819  * 
16820  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16821  * 
16822  * Implemented By:
16823  * 
16824  * <TM>
16825  * 
16826  */
16827 Layouts.TM = {};
16828
16829 Layouts.TM.SliceAndDice = new Class({
16830   compute: function(prop) {
16831     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16832     this.controller.onBeforeCompute(root);
16833     var size = this.canvas.getSize(),
16834         config = this.config,
16835         width = size.width,
16836         height = size.height;
16837     this.graph.computeLevels(this.root, 0, "ignore");
16838     //set root position and dimensions
16839     root.getPos(prop).setc(-width/2, -height/2);
16840     root.setData('width', width, prop);
16841     root.setData('height', height + config.titleHeight, prop);
16842     this.computePositions(root, root, this.layout.orientation, prop);
16843     this.controller.onAfterCompute(root);
16844   },
16845   
16846   computePositions: function(par, ch, orn, prop) {
16847     //compute children areas
16848     var totalArea = 0;
16849     par.eachSubnode(function(n) {
16850       totalArea += n.getData('area', prop);
16851     });
16852     
16853     var config = this.config,
16854         offst = config.offset,
16855         width  = par.getData('width', prop),
16856         height = par.getData('height', prop) - config.titleHeight,
16857         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16858     
16859     var otherSize, size, dim, pos, pos2, posth, pos2th;
16860     var horizontal = (orn == "h");
16861     if(horizontal) {
16862       orn = 'v';    
16863       otherSize = height;
16864       size = width * fact;
16865       dim = 'height';
16866       pos = 'y';
16867       pos2 = 'x';
16868       posth = config.titleHeight;
16869       pos2th = 0;
16870     } else {
16871       orn = 'h';    
16872       otherSize = height * fact;
16873       size = width;
16874       dim = 'width';
16875       pos = 'x';
16876       pos2 = 'y';
16877       posth = 0;
16878       pos2th = config.titleHeight;
16879     }
16880     var cpos = ch.getPos(prop);
16881     ch.setData('width', size, prop);
16882     ch.setData('height', otherSize, prop);
16883     var offsetSize = 0, tm = this;
16884     ch.eachSubnode(function(n) {
16885       var p = n.getPos(prop);
16886       p[pos] = offsetSize + cpos[pos] + posth;
16887       p[pos2] = cpos[pos2] + pos2th;
16888       tm.computePositions(ch, n, orn, prop);
16889       offsetSize += n.getData(dim, prop);
16890     });
16891   }
16892
16893 });
16894
16895 Layouts.TM.Area = {
16896  /*
16897     Method: compute
16898  
16899    Called by loadJSON to calculate recursively all node positions and lay out the tree.
16900  
16901     Parameters:
16902
16903        json - A JSON tree. See also <Loader.loadJSON>.
16904        coord - A coordinates object specifying width, height, left and top style properties.
16905  */
16906  compute: function(prop) {
16907     prop = prop || "current";
16908     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16909     this.controller.onBeforeCompute(root);
16910     var config = this.config,
16911         size = this.canvas.getSize(),
16912         width = size.width,
16913         height = size.height,
16914         offst = config.offset,
16915         offwdth = width - offst,
16916         offhght = height - offst;
16917     this.graph.computeLevels(this.root, 0, "ignore");
16918     //set root position and dimensions
16919     root.getPos(prop).setc(-width/2, -height/2);
16920     root.setData('width', width, prop);
16921     root.setData('height', height, prop);
16922     //create a coordinates object
16923     var coord = {
16924         'top': -height/2 + config.titleHeight,
16925         'left': -width/2,
16926         'width': offwdth,
16927         'height': offhght - config.titleHeight
16928     };
16929     this.computePositions(root, coord, prop);
16930     this.controller.onAfterCompute(root);
16931  }, 
16932  
16933  /*
16934     Method: computeDim
16935  
16936    Computes dimensions and positions of a group of nodes
16937    according to a custom layout row condition. 
16938  
16939     Parameters:
16940
16941        tail - An array of nodes.  
16942        initElem - An array of nodes (containing the initial node to be laid).
16943        w - A fixed dimension where nodes will be layed out.
16944        coord - A coordinates object specifying width, height, left and top style properties.
16945        comp - A custom comparison function
16946  */
16947  computeDim: function(tail, initElem, w, coord, comp, prop) {
16948    if(tail.length + initElem.length == 1) {
16949      var l = (tail.length == 1)? tail : initElem;
16950      this.layoutLast(l, w, coord, prop);
16951      return;
16952    }
16953    if(tail.length >= 2 && initElem.length == 0) {
16954      initElem = [tail.shift()];
16955    }
16956    if(tail.length == 0) {
16957      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
16958      return;
16959    }
16960    var c = tail[0];
16961    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
16962      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
16963    } else {
16964      var newCoords = this.layoutRow(initElem, w, coord, prop);
16965      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
16966    }
16967  },
16968
16969  
16970  /*
16971     Method: worstAspectRatio
16972  
16973    Calculates the worst aspect ratio of a group of rectangles. 
16974        
16975     See also:
16976        
16977        <http://en.wikipedia.org/wiki/Aspect_ratio>
16978    
16979     Parameters:
16980
16981      ch - An array of nodes.  
16982      w  - The fixed dimension where rectangles are being laid out.
16983
16984     Returns:
16985  
16986         The worst aspect ratio.
16987
16988
16989  */
16990  worstAspectRatio: function(ch, w) {
16991    if(!ch || ch.length == 0) return Number.MAX_VALUE;
16992    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
16993    for(var i=0, l=ch.length; i<l; i++) {
16994      var area = ch[i]._area;
16995      areaSum += area; 
16996      minArea = minArea < area? minArea : area;
16997      maxArea = maxArea > area? maxArea : area; 
16998    }
16999    var sqw = w * w, sqAreaSum = areaSum * areaSum;
17000    return Math.max(sqw * maxArea / sqAreaSum,
17001            sqAreaSum / (sqw * minArea));
17002  },
17003  
17004  /*
17005     Method: avgAspectRatio
17006  
17007    Calculates the average aspect ratio of a group of rectangles. 
17008        
17009        See also:
17010        
17011        <http://en.wikipedia.org/wiki/Aspect_ratio>
17012    
17013     Parameters:
17014
17015      ch - An array of nodes.  
17016        w - The fixed dimension where rectangles are being laid out.
17017
17018     Returns:
17019  
17020         The average aspect ratio.
17021
17022
17023  */
17024  avgAspectRatio: function(ch, w) {
17025    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17026    var arSum = 0;
17027    for(var i=0, l=ch.length; i<l; i++) {
17028      var area = ch[i]._area;
17029      var h = area / w;
17030      arSum += w > h? w / h : h / w;
17031    }
17032    return arSum / l;
17033  },
17034
17035  /*
17036     layoutLast
17037  
17038    Performs the layout of the last computed sibling.
17039  
17040     Parameters:
17041
17042        ch - An array of nodes.  
17043        w - A fixed dimension where nodes will be layed out.
17044      coord - A coordinates object specifying width, height, left and top style properties.
17045  */
17046  layoutLast: function(ch, w, coord, prop) {
17047    var child = ch[0];
17048    child.getPos(prop).setc(coord.left, coord.top);
17049    child.setData('width', coord.width, prop);
17050    child.setData('height', coord.height, prop);
17051  }
17052 };
17053
17054
17055 Layouts.TM.Squarified = new Class({
17056  Implements: Layouts.TM.Area,
17057  
17058  computePositions: function(node, coord, prop) {
17059    var config = this.config;
17060    
17061    if (coord.width >= coord.height) 
17062      this.layout.orientation = 'h';
17063    else
17064      this.layout.orientation = 'v';
17065    
17066    var ch = node.getSubnodes([1, 1], "ignore");
17067    if(ch.length > 0) {
17068      this.processChildrenLayout(node, ch, coord, prop);
17069      for(var i=0, l=ch.length; i<l; i++) {
17070        var chi = ch[i]; 
17071        var offst = config.offset,
17072            height = chi.getData('height', prop) - offst - config.titleHeight,
17073            width = chi.getData('width', prop) - offst;
17074        var chipos = chi.getPos(prop);
17075        coord = {
17076          'width': width,
17077          'height': height,
17078          'top': chipos.y + config.titleHeight,
17079          'left': chipos.x
17080        };
17081        this.computePositions(chi, coord, prop);
17082      }
17083    }
17084  },
17085
17086  /*
17087     Method: processChildrenLayout
17088  
17089    Computes children real areas and other useful parameters for performing the Squarified algorithm.
17090  
17091     Parameters:
17092
17093        par - The parent node of the json subtree.  
17094        ch - An Array of nodes
17095      coord - A coordinates object specifying width, height, left and top style properties.
17096  */
17097  processChildrenLayout: function(par, ch, coord, prop) {
17098    //compute children real areas
17099    var parentArea = coord.width * coord.height;
17100    var i, l=ch.length, totalChArea=0, chArea = [];
17101    for(i=0; i<l; i++) {
17102      chArea[i] = parseFloat(ch[i].getData('area', prop));
17103      totalChArea += chArea[i];
17104    }
17105    for(i=0; i<l; i++) {
17106      ch[i]._area = parentArea * chArea[i] / totalChArea;
17107    }
17108    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17109    ch.sort(function(a, b) { 
17110      var diff = b._area - a._area; 
17111      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
17112    });
17113    var initElem = [ch[0]];
17114    var tail = ch.slice(1);
17115    this.squarify(tail, initElem, minimumSideValue, coord, prop);
17116  },
17117
17118  /*
17119    Method: squarify
17120  
17121    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17122  
17123     Parameters:
17124
17125        tail - An array of nodes.  
17126        initElem - An array of nodes, containing the initial node to be laid out.
17127        w - A fixed dimension where nodes will be laid out.
17128        coord - A coordinates object specifying width, height, left and top style properties.
17129  */
17130  squarify: function(tail, initElem, w, coord, prop) {
17131    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17132  },
17133  
17134  /*
17135     Method: layoutRow
17136  
17137    Performs the layout of an array of nodes.
17138  
17139     Parameters:
17140
17141        ch - An array of nodes.  
17142        w - A fixed dimension where nodes will be laid out.
17143        coord - A coordinates object specifying width, height, left and top style properties.
17144  */
17145  layoutRow: function(ch, w, coord, prop) {
17146    if(this.layout.horizontal()) {
17147      return this.layoutV(ch, w, coord, prop);
17148    } else {
17149      return this.layoutH(ch, w, coord, prop);
17150    }
17151  },
17152  
17153  layoutV: function(ch, w, coord, prop) {
17154    var totalArea = 0, rnd = function(x) { return x; }; 
17155    $.each(ch, function(elem) { totalArea += elem._area; });
17156    var width = rnd(totalArea / w), top =  0; 
17157    for(var i=0, l=ch.length; i<l; i++) {
17158      var h = rnd(ch[i]._area / width);
17159      var chi = ch[i];
17160      chi.getPos(prop).setc(coord.left, coord.top + top);
17161      chi.setData('width', width, prop);
17162      chi.setData('height', h, prop);
17163      top += h;
17164    }
17165    var ans = {
17166      'height': coord.height,
17167      'width': coord.width - width,
17168      'top': coord.top,
17169      'left': coord.left + width
17170    };
17171    //take minimum side value.
17172    ans.dim = Math.min(ans.width, ans.height);
17173    if(ans.dim != ans.height) this.layout.change();
17174    return ans;
17175  },
17176  
17177  layoutH: function(ch, w, coord, prop) {
17178    var totalArea = 0; 
17179    $.each(ch, function(elem) { totalArea += elem._area; });
17180    var height = totalArea / w,
17181        top = coord.top, 
17182        left = 0;
17183    
17184    for(var i=0, l=ch.length; i<l; i++) {
17185      var chi = ch[i];
17186      var w = chi._area / height;
17187      chi.getPos(prop).setc(coord.left + left, top);
17188      chi.setData('width', w, prop);
17189      chi.setData('height', height, prop);
17190      left += w;
17191    }
17192    var ans = {
17193      'height': coord.height - height,
17194      'width': coord.width,
17195      'top': coord.top + height,
17196      'left': coord.left
17197    };
17198    ans.dim = Math.min(ans.width, ans.height);
17199    if(ans.dim != ans.width) this.layout.change();
17200    return ans;
17201  }
17202 });
17203
17204 Layouts.TM.Strip = new Class({
17205   Implements: Layouts.TM.Area,
17206
17207     /*
17208       Method: compute
17209     
17210      Called by loadJSON to calculate recursively all node positions and lay out the tree.
17211     
17212       Parameters:
17213     
17214          json - A JSON subtree. See also <Loader.loadJSON>. 
17215        coord - A coordinates object specifying width, height, left and top style properties.
17216     */
17217     computePositions: function(node, coord, prop) {
17218      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17219      if(ch.length > 0) {
17220        this.processChildrenLayout(node, ch, coord, prop);
17221        for(var i=0, l=ch.length; i<l; i++) {
17222          var chi = ch[i];
17223          var offst = config.offset,
17224              height = chi.getData('height', prop) - offst - config.titleHeight,
17225              width  = chi.getData('width', prop)  - offst;
17226          var chipos = chi.getPos(prop);
17227          coord = {
17228            'width': width,
17229            'height': height,
17230            'top': chipos.y + config.titleHeight,
17231            'left': chipos.x
17232          };
17233          this.computePositions(chi, coord, prop);
17234        }
17235      }
17236     },
17237     
17238     /*
17239       Method: processChildrenLayout
17240     
17241      Computes children real areas and other useful parameters for performing the Strip algorithm.
17242     
17243       Parameters:
17244     
17245          par - The parent node of the json subtree.  
17246          ch - An Array of nodes
17247          coord - A coordinates object specifying width, height, left and top style properties.
17248     */
17249     processChildrenLayout: function(par, ch, coord, prop) {
17250      //compute children real areas
17251       var parentArea = coord.width * coord.height;
17252       var i, l=ch.length, totalChArea=0, chArea = [];
17253       for(i=0; i<l; i++) {
17254         chArea[i] = +ch[i].getData('area', prop);
17255         totalChArea += chArea[i];
17256       }
17257       for(i=0; i<l; i++) {
17258         ch[i]._area = parentArea * chArea[i] / totalChArea;
17259       }
17260      var side = this.layout.horizontal()? coord.width : coord.height;
17261      var initElem = [ch[0]];
17262      var tail = ch.slice(1);
17263      this.stripify(tail, initElem, side, coord, prop);
17264     },
17265     
17266     /*
17267       Method: stripify
17268     
17269      Performs an heuristic method to calculate div elements sizes in order to have 
17270      a good compromise between aspect ratio and order.
17271     
17272       Parameters:
17273     
17274          tail - An array of nodes.  
17275          initElem - An array of nodes.
17276          w - A fixed dimension where nodes will be layed out.
17277        coord - A coordinates object specifying width, height, left and top style properties.
17278     */
17279     stripify: function(tail, initElem, w, coord, prop) {
17280      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17281     },
17282     
17283     /*
17284       Method: layoutRow
17285     
17286      Performs the layout of an array of nodes.
17287     
17288       Parameters:
17289     
17290          ch - An array of nodes.  
17291          w - A fixed dimension where nodes will be laid out.
17292          coord - A coordinates object specifying width, height, left and top style properties.
17293     */
17294     layoutRow: function(ch, w, coord, prop) {
17295      if(this.layout.horizontal()) {
17296        return this.layoutH(ch, w, coord, prop);
17297      } else {
17298        return this.layoutV(ch, w, coord, prop);
17299      }
17300     },
17301     
17302     layoutV: function(ch, w, coord, prop) {
17303      var totalArea = 0; 
17304      $.each(ch, function(elem) { totalArea += elem._area; });
17305      var width = totalArea / w, top =  0; 
17306      for(var i=0, l=ch.length; i<l; i++) {
17307        var chi = ch[i];
17308        var h = chi._area / width;
17309        chi.getPos(prop).setc(coord.left, 
17310            coord.top + (w - h - top));
17311        chi.setData('width', width, prop);
17312        chi.setData('height', h, prop);
17313        top += h;
17314      }
17315     
17316      return {
17317        'height': coord.height,
17318        'width': coord.width - width,
17319        'top': coord.top,
17320        'left': coord.left + width,
17321        'dim': w
17322      };
17323     },
17324     
17325     layoutH: function(ch, w, coord, prop) {
17326      var totalArea = 0; 
17327      $.each(ch, function(elem) { totalArea += elem._area; });
17328      var height = totalArea / w,
17329          top = coord.height - height, 
17330          left = 0;
17331      
17332      for(var i=0, l=ch.length; i<l; i++) {
17333        var chi = ch[i];
17334        var s = chi._area / height;
17335        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17336        chi.setData('width', s, prop);
17337        chi.setData('height', height, prop);
17338        left += s;
17339      }
17340      return {
17341        'height': coord.height - height,
17342        'width': coord.width,
17343        'top': coord.top,
17344        'left': coord.left,
17345        'dim': w
17346      };
17347     }
17348  });
17349
17350 /*
17351  * Class: Layouts.Icicle
17352  *
17353  * Implements the icicle tree layout.
17354  *
17355  * Implemented By:
17356  *
17357  * <Icicle>
17358  *
17359  */
17360
17361 Layouts.Icicle = new Class({
17362  /*
17363   * Method: compute
17364   *
17365   * Called by loadJSON to calculate all node positions.
17366   *
17367   * Parameters:
17368   *
17369   * posType - The nodes' position to compute. Either "start", "end" or
17370   *            "current". Defaults to "current".
17371   */
17372   compute: function(posType) {
17373     posType = posType || "current";
17374     var root = this.graph.getNode(this.root),
17375         config = this.config,
17376         size = this.canvas.getSize(),
17377         width = size.width,
17378         height = size.height,
17379         offset = config.offset,
17380         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17381
17382     this.controller.onBeforeCompute(root);
17383
17384     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17385
17386     var treeDepth = 0;
17387
17388     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17389
17390     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17391     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17392     var initialDepth = startNode._depth;
17393     if(this.layout.horizontal()) {
17394       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17395     } else {
17396       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17397     }
17398   },
17399
17400   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17401     root.getPos(posType).setc(x, y);
17402     root.setData('width', width, posType);
17403     root.setData('height', height, posType);
17404
17405     var nodeLength, prevNodeLength = 0, totalDim = 0;
17406     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17407
17408     if(!children.length)
17409       return;
17410
17411     $.each(children, function(e) { totalDim += e.getData('dim'); });
17412
17413     for(var i=0, l=children.length; i < l; i++) {
17414       if(this.layout.horizontal()) {
17415         nodeLength = height * children[i].getData('dim') / totalDim;
17416         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17417         y += nodeLength;
17418       } else {
17419         nodeLength = width * children[i].getData('dim') / totalDim;
17420         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17421         x += nodeLength;
17422       }
17423     }
17424   }
17425 });
17426
17427
17428
17429 /*
17430  * File: Icicle.js
17431  *
17432 */
17433
17434 /*
17435   Class: Icicle
17436   
17437   Icicle space filling visualization.
17438   
17439   Implements:
17440   
17441   All <Loader> methods
17442   
17443   Constructor Options:
17444   
17445   Inherits options from
17446   
17447   - <Options.Canvas>
17448   - <Options.Controller>
17449   - <Options.Node>
17450   - <Options.Edge>
17451   - <Options.Label>
17452   - <Options.Events>
17453   - <Options.Tips>
17454   - <Options.NodeStyles>
17455   - <Options.Navigation>
17456   
17457   Additionally, there are other parameters and some default values changed
17458
17459   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17460   offset - (number) Default's *2*. Boxes offset.
17461   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17462   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17463   animate - (boolean) Default's *false*. Whether to animate transitions.
17464   Node.type - Described in <Options.Node>. Default's *rectangle*.
17465   Label.type - Described in <Options.Label>. Default's *Native*.
17466   duration - Described in <Options.Fx>. Default's *700*.
17467   fps - Described in <Options.Fx>. Default's *45*.
17468   
17469   Instance Properties:
17470   
17471   canvas - Access a <Canvas> instance.
17472   graph - Access a <Graph> instance.
17473   op - Access a <Icicle.Op> instance.
17474   fx - Access a <Icicle.Plot> instance.
17475   labels - Access a <Icicle.Label> interface implementation.
17476
17477 */
17478
17479 $jit.Icicle = new Class({
17480   Implements: [ Loader, Extras, Layouts.Icicle ],
17481
17482   layout: {
17483     orientation: "h",
17484     vertical: function(){
17485       return this.orientation == "v";
17486     },
17487     horizontal: function(){
17488       return this.orientation == "h";
17489     },
17490     change: function(){
17491       this.orientation = this.vertical()? "h" : "v";
17492     }
17493   },
17494
17495   initialize: function(controller) {
17496     var config = {
17497       animate: false,
17498       orientation: "h",
17499       offset: 2,
17500       levelsToShow: Number.MAX_VALUE,
17501       constrained: false,
17502       Node: {
17503         type: 'rectangle',
17504         overridable: true
17505       },
17506       Edge: {
17507         type: 'none'
17508       },
17509       Label: {
17510         type: 'Native'
17511       },
17512       duration: 700,
17513       fps: 45
17514     };
17515
17516     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17517                        "Events", "Navigation", "Controller", "Label");
17518     this.controller = this.config = $.merge(opts, config, controller);
17519     this.layout.orientation = this.config.orientation;
17520
17521     var canvasConfig = this.config;
17522     if (canvasConfig.useCanvas) {
17523       this.canvas = canvasConfig.useCanvas;
17524       this.config.labelContainer = this.canvas.id + '-label';
17525     } else {
17526       this.canvas = new Canvas(this, canvasConfig);
17527       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17528     }
17529
17530     this.graphOptions = {
17531       'complex': true,
17532       'Node': {
17533         'selected': false,
17534         'exist': true,
17535         'drawn': true
17536       }
17537     };
17538
17539     this.graph = new Graph(
17540       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17541
17542     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17543     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17544     this.op = new $jit.Icicle.Op(this);
17545     this.group = new $jit.Icicle.Group(this);
17546     this.clickedNode = null;
17547
17548     this.initializeExtras();
17549   },
17550
17551   /* 
17552     Method: refresh 
17553     
17554     Computes positions and plots the tree.
17555   */
17556   refresh: function(){
17557     var labelType = this.config.Label.type;
17558     if(labelType != 'Native') {
17559       var that = this;
17560       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17561     }
17562     this.compute();
17563     this.plot();
17564   },
17565
17566   /* 
17567     Method: plot 
17568     
17569     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17570   
17571    */
17572   plot: function(){
17573     this.fx.plot(this.config);
17574   },
17575
17576   /* 
17577     Method: enter 
17578     
17579     Sets the node as root.
17580     
17581      Parameters:
17582      
17583      node - (object) A <Graph.Node>.
17584   
17585    */
17586   enter: function (node) {
17587     if (this.busy)
17588       return;
17589     this.busy = true;
17590
17591     var that = this,
17592         config = this.config;
17593
17594     var callback = {
17595       onComplete: function() {
17596         //compute positions of newly inserted nodes
17597         if(config.request)
17598           that.compute();
17599
17600         if(config.animate) {
17601           that.graph.nodeList.setDataset(['current', 'end'], {
17602             'alpha': [1, 0] //fade nodes
17603           });
17604
17605           Graph.Util.eachSubgraph(node, function(n) {
17606             n.setData('alpha', 1, 'end');
17607           }, "ignore");
17608
17609           that.fx.animate({
17610             duration: 500,
17611             modes:['node-property:alpha'],
17612             onComplete: function() {
17613               that.clickedNode = node;
17614               that.compute('end');
17615
17616               that.fx.animate({
17617                 modes:['linear', 'node-property:width:height'],
17618                 duration: 1000,
17619                 onComplete: function() {
17620                   that.busy = false;
17621                   that.clickedNode = node;
17622                 }
17623               });
17624             }
17625           });
17626         } else {
17627           that.clickedNode = node;
17628           that.busy = false;
17629           that.refresh();
17630         }
17631       }
17632     };
17633
17634     if(config.request) {
17635       this.requestNodes(clickedNode, callback);
17636     } else {
17637       callback.onComplete();
17638     }
17639   },
17640
17641   /* 
17642     Method: out 
17643     
17644     Sets the parent node of the current selected node as root.
17645   
17646    */
17647   out: function(){
17648     if(this.busy)
17649       return;
17650
17651     var that = this,
17652         GUtil = Graph.Util,
17653         config = this.config,
17654         graph = this.graph,
17655         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17656         parent = parents[0],
17657         clickedNode = parent,
17658         previousClickedNode = this.clickedNode;
17659
17660     this.busy = true;
17661     this.events.hoveredNode = false;
17662
17663     if(!parent) {
17664       this.busy = false;
17665       return;
17666     }
17667
17668     //final plot callback
17669     callback = {
17670       onComplete: function() {
17671         that.clickedNode = parent;
17672         if(config.request) {
17673           that.requestNodes(parent, {
17674             onComplete: function() {
17675               that.compute();
17676               that.plot();
17677               that.busy = false;
17678             }
17679           });
17680         } else {
17681           that.compute();
17682           that.plot();
17683           that.busy = false;
17684         }
17685       }
17686     };
17687
17688     //animate node positions
17689     if(config.animate) {
17690       this.clickedNode = clickedNode;
17691       this.compute('end');
17692       //animate the visible subtree only
17693       this.clickedNode = previousClickedNode;
17694       this.fx.animate({
17695         modes:['linear', 'node-property:width:height'],
17696         duration: 1000,
17697         onComplete: function() {
17698           //animate the parent subtree
17699           that.clickedNode = clickedNode;
17700           //change nodes alpha
17701           graph.nodeList.setDataset(['current', 'end'], {
17702             'alpha': [0, 1]
17703           });
17704           GUtil.eachSubgraph(previousClickedNode, function(node) {
17705             node.setData('alpha', 1);
17706           }, "ignore");
17707           that.fx.animate({
17708             duration: 500,
17709             modes:['node-property:alpha'],
17710             onComplete: function() {
17711               callback.onComplete();
17712             }
17713           });
17714         }
17715       });
17716     } else {
17717       callback.onComplete();
17718     }
17719   },
17720   requestNodes: function(node, onComplete){
17721     var handler = $.merge(this.controller, onComplete),
17722         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17723
17724     if (handler.request) {
17725       var leaves = [], d = node._depth;
17726       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17727         if (n.drawn && !Graph.Util.anySubnode(n)) {
17728           leaves.push(n);
17729           n._level = n._depth - d;
17730           if (this.config.constrained)
17731             n._level = levelsToShow - n._level;
17732
17733         }
17734       });
17735       this.group.requestNodes(leaves, handler);
17736     } else {
17737       handler.onComplete();
17738     }
17739   }
17740 });
17741
17742 /*
17743   Class: Icicle.Op
17744   
17745   Custom extension of <Graph.Op>.
17746   
17747   Extends:
17748   
17749   All <Graph.Op> methods
17750   
17751   See also:
17752   
17753   <Graph.Op>
17754   
17755   */
17756 $jit.Icicle.Op = new Class({
17757
17758   Implements: Graph.Op
17759
17760 });
17761
17762 /*
17763  * Performs operations on group of nodes.
17764  */
17765 $jit.Icicle.Group = new Class({
17766
17767   initialize: function(viz){
17768     this.viz = viz;
17769     this.canvas = viz.canvas;
17770     this.config = viz.config;
17771   },
17772
17773   /*
17774    * Calls the request method on the controller to request a subtree for each node.
17775    */
17776   requestNodes: function(nodes, controller){
17777     var counter = 0, len = nodes.length, nodeSelected = {};
17778     var complete = function(){
17779       controller.onComplete();
17780     };
17781     var viz = this.viz;
17782     if (len == 0)
17783       complete();
17784     for(var i = 0; i < len; i++) {
17785       nodeSelected[nodes[i].id] = nodes[i];
17786       controller.request(nodes[i].id, nodes[i]._level, {
17787         onComplete: function(nodeId, data){
17788           if (data && data.children) {
17789             data.id = nodeId;
17790             viz.op.sum(data, {
17791               type: 'nothing'
17792             });
17793           }
17794           if (++counter == len) {
17795             Graph.Util.computeLevels(viz.graph, viz.root, 0);
17796             complete();
17797           }
17798         }
17799       });
17800     }
17801   }
17802 });
17803
17804 /*
17805   Class: Icicle.Plot
17806   
17807   Custom extension of <Graph.Plot>.
17808   
17809   Extends:
17810   
17811   All <Graph.Plot> methods
17812   
17813   See also:
17814   
17815   <Graph.Plot>
17816   
17817   */
17818 $jit.Icicle.Plot = new Class({
17819   Implements: Graph.Plot,
17820
17821   plot: function(opt, animating){
17822     opt = opt || this.viz.controller;
17823     var viz = this.viz,
17824         graph = viz.graph,
17825         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17826         initialDepth = root._depth;
17827
17828     viz.canvas.clear();
17829     this.plotTree(root, $.merge(opt, {
17830       'withLabels': true,
17831       'hideLabels': false,
17832       'plotSubtree': function(root, node) {
17833         return !viz.config.constrained ||
17834                (node._depth - initialDepth < viz.config.levelsToShow);
17835       }
17836     }), animating);
17837   }
17838 });
17839
17840 /*
17841   Class: Icicle.Label
17842   
17843   Custom extension of <Graph.Label>. 
17844   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17845   
17846   Extends:
17847   
17848   All <Graph.Label> methods and subclasses.
17849   
17850   See also:
17851   
17852   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17853   
17854   */
17855 $jit.Icicle.Label = {};
17856
17857 /*
17858   Icicle.Label.Native
17859   
17860   Custom extension of <Graph.Label.Native>.
17861   
17862   Extends:
17863   
17864   All <Graph.Label.Native> methods
17865   
17866   See also:
17867   
17868   <Graph.Label.Native>
17869
17870   */
17871 $jit.Icicle.Label.Native = new Class({
17872   Implements: Graph.Label.Native,
17873
17874   renderLabel: function(canvas, node, controller) {
17875     var ctx = canvas.getCtx(),
17876         width = node.getData('width'),
17877         height = node.getData('height'),
17878         size = node.getLabelData('size'),
17879         m = ctx.measureText(node.name);
17880
17881     // Guess as much as possible if the label will fit in the node
17882     if(height < (size * 1.5) || width < m.width)
17883       return;
17884
17885     var pos = node.pos.getc(true);
17886     ctx.fillText(node.name,
17887                  pos.x + width / 2,
17888                  pos.y + height / 2);
17889   }
17890 });
17891
17892 /*
17893   Icicle.Label.SVG
17894   
17895   Custom extension of <Graph.Label.SVG>.
17896   
17897   Extends:
17898   
17899   All <Graph.Label.SVG> methods
17900   
17901   See also:
17902   
17903   <Graph.Label.SVG>
17904 */
17905 $jit.Icicle.Label.SVG = new Class( {
17906   Implements: Graph.Label.SVG,
17907
17908   initialize: function(viz){
17909     this.viz = viz;
17910   },
17911
17912   /*
17913     placeLabel
17914    
17915     Overrides abstract method placeLabel in <Graph.Plot>.
17916    
17917     Parameters:
17918    
17919     tag - A DOM label element.
17920     node - A <Graph.Node>.
17921     controller - A configuration/controller object passed to the visualization.
17922    */
17923   placeLabel: function(tag, node, controller){
17924     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17925     var radius = canvas.getSize();
17926     var labelPos = {
17927       x: Math.round(pos.x + radius.width / 2),
17928       y: Math.round(pos.y + radius.height / 2)
17929     };
17930     tag.setAttribute('x', labelPos.x);
17931     tag.setAttribute('y', labelPos.y);
17932
17933     controller.onPlaceLabel(tag, node);
17934   }
17935 });
17936
17937 /*
17938   Icicle.Label.HTML
17939   
17940   Custom extension of <Graph.Label.HTML>.
17941   
17942   Extends:
17943   
17944   All <Graph.Label.HTML> methods.
17945   
17946   See also:
17947   
17948   <Graph.Label.HTML>
17949   
17950   */
17951 $jit.Icicle.Label.HTML = new Class( {
17952   Implements: Graph.Label.HTML,
17953
17954   initialize: function(viz){
17955     this.viz = viz;
17956   },
17957
17958   /*
17959     placeLabel
17960    
17961     Overrides abstract method placeLabel in <Graph.Plot>.
17962    
17963     Parameters:
17964    
17965     tag - A DOM label element.
17966     node - A <Graph.Node>.
17967     controller - A configuration/controller object passed to the visualization.
17968    */
17969   placeLabel: function(tag, node, controller){
17970     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17971     var radius = canvas.getSize();
17972     var labelPos = {
17973       x: Math.round(pos.x + radius.width / 2),
17974       y: Math.round(pos.y + radius.height / 2)
17975     };
17976
17977     var style = tag.style;
17978     style.left = labelPos.x + 'px';
17979     style.top = labelPos.y + 'px';
17980     style.display = '';
17981
17982     controller.onPlaceLabel(tag, node);
17983   }
17984 });
17985
17986 /*
17987   Class: Icicle.Plot.NodeTypes
17988   
17989   This class contains a list of <Graph.Node> built-in types. 
17990   Node types implemented are 'none', 'rectangle'.
17991   
17992   You can add your custom node types, customizing your visualization to the extreme.
17993   
17994   Example:
17995   
17996   (start code js)
17997     Icicle.Plot.NodeTypes.implement({
17998       'mySpecialType': {
17999         'render': function(node, canvas) {
18000           //print your custom node to canvas
18001         },
18002         //optional
18003         'contains': function(node, pos) {
18004           //return true if pos is inside the node or false otherwise
18005         }
18006       }
18007     });
18008   (end code)
18009   
18010   */
18011 $jit.Icicle.Plot.NodeTypes = new Class( {
18012   'none': {
18013     'render': $.empty
18014   },
18015
18016   'rectangle': {
18017     'render': function(node, canvas, animating) {
18018       var config = this.viz.config;
18019       var offset = config.offset;
18020       var width = node.getData('width');
18021       var height = node.getData('height');
18022       var border = node.getData('border');
18023       var pos = node.pos.getc(true);
18024       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18025       var ctx = canvas.getCtx();
18026       
18027       if(width - offset < 2 || height - offset < 2) return;
18028       
18029       if(config.cushion) {
18030         var color = node.getData('color');
18031         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
18032                                           posy + (height - offset)/2, 1, 
18033                                           posx + (width-offset)/2, posy + (height-offset)/2, 
18034                                           width < height? height : width);
18035         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
18036             function(r) { return r * 0.3 >> 0; }));
18037         lg.addColorStop(0, color);
18038         lg.addColorStop(1, colorGrad);
18039         ctx.fillStyle = lg;
18040       }
18041
18042       if (border) {
18043         ctx.strokeStyle = border;
18044         ctx.lineWidth = 3;
18045       }
18046
18047       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18048       border && ctx.strokeRect(pos.x, pos.y, width, height);
18049     },
18050
18051     'contains': function(node, pos) {
18052       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18053       var npos = node.pos.getc(true),
18054           width = node.getData('width'),
18055           height = node.getData('height');
18056       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18057     }
18058   }
18059 });
18060
18061 $jit.Icicle.Plot.EdgeTypes = new Class( {
18062   'none': $.empty
18063 });
18064
18065
18066
18067 /*
18068  * File: Layouts.ForceDirected.js
18069  *
18070 */
18071
18072 /*
18073  * Class: Layouts.ForceDirected
18074  * 
18075  * Implements a Force Directed Layout.
18076  * 
18077  * Implemented By:
18078  * 
18079  * <ForceDirected>
18080  * 
18081  * Credits:
18082  * 
18083  * Marcus Cobden <http://marcuscobden.co.uk>
18084  * 
18085  */
18086 Layouts.ForceDirected = new Class({
18087
18088   getOptions: function(random) {
18089     var s = this.canvas.getSize();
18090     var w = s.width, h = s.height;
18091     //count nodes
18092     var count = 0;
18093     this.graph.eachNode(function(n) { 
18094       count++;
18095     });
18096     var k2 = w * h / count, k = Math.sqrt(k2);
18097     var l = this.config.levelDistance;
18098     
18099     return {
18100       width: w,
18101       height: h,
18102       tstart: w * 0.1,
18103       nodef: function(x) { return k2 / (x || 1); },
18104       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18105     };
18106   },
18107   
18108   compute: function(property, incremental) {
18109     var prop = $.splat(property || ['current', 'start', 'end']);
18110     var opt = this.getOptions();
18111     NodeDim.compute(this.graph, prop, this.config);
18112     this.graph.computeLevels(this.root, 0, "ignore");
18113     this.graph.eachNode(function(n) {
18114       $.each(prop, function(p) {
18115         var pos = n.getPos(p);
18116         if(pos.equals(Complex.KER)) {
18117           pos.x = opt.width/5 * (Math.random() - 0.5);
18118           pos.y = opt.height/5 * (Math.random() - 0.5);
18119         }
18120         //initialize disp vector
18121         n.disp = {};
18122         $.each(prop, function(p) {
18123           n.disp[p] = $C(0, 0);
18124         });
18125       });
18126     });
18127     this.computePositions(prop, opt, incremental);
18128   },
18129   
18130   computePositions: function(property, opt, incremental) {
18131     var times = this.config.iterations, i = 0, that = this;
18132     if(incremental) {
18133       (function iter() {
18134         for(var total=incremental.iter, j=0; j<total; j++) {
18135           opt.t = opt.tstart * (1 - i++/(times -1));
18136           that.computePositionStep(property, opt);
18137           if(i >= times) {
18138             incremental.onComplete();
18139             return;
18140           }
18141         }
18142         incremental.onStep(Math.round(i / (times -1) * 100));
18143         setTimeout(iter, 1);
18144       })();
18145     } else {
18146       for(; i < times; i++) {
18147         opt.t = opt.tstart * (1 - i/(times -1));
18148         this.computePositionStep(property, opt);
18149       }
18150     }
18151   },
18152   
18153   computePositionStep: function(property, opt) {
18154     var graph = this.graph;
18155     var min = Math.min, max = Math.max;
18156     var dpos = $C(0, 0);
18157     //calculate repulsive forces
18158     graph.eachNode(function(v) {
18159       //initialize disp
18160       $.each(property, function(p) {
18161         v.disp[p].x = 0; v.disp[p].y = 0;
18162       });
18163       graph.eachNode(function(u) {
18164         if(u.id != v.id) {
18165           $.each(property, function(p) {
18166             var vp = v.getPos(p), up = u.getPos(p);
18167             dpos.x = vp.x - up.x;
18168             dpos.y = vp.y - up.y;
18169             var norm = dpos.norm() || 1;
18170             v.disp[p].$add(dpos
18171                 .$scale(opt.nodef(norm) / norm));
18172           });
18173         }
18174       });
18175     });
18176     //calculate attractive forces
18177     var T = !!graph.getNode(this.root).visited;
18178     graph.eachNode(function(node) {
18179       node.eachAdjacency(function(adj) {
18180         var nodeTo = adj.nodeTo;
18181         if(!!nodeTo.visited === T) {
18182           $.each(property, function(p) {
18183             var vp = node.getPos(p), up = nodeTo.getPos(p);
18184             dpos.x = vp.x - up.x;
18185             dpos.y = vp.y - up.y;
18186             var norm = dpos.norm() || 1;
18187             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18188             nodeTo.disp[p].$add(dpos.$scale(-1));
18189           });
18190         }
18191       });
18192       node.visited = !T;
18193     });
18194     //arrange positions to fit the canvas
18195     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18196     graph.eachNode(function(u) {
18197       $.each(property, function(p) {
18198         var disp = u.disp[p];
18199         var norm = disp.norm() || 1;
18200         var p = u.getPos(p);
18201         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
18202             disp.y * min(Math.abs(disp.y), t) / norm));
18203         p.x = min(w2, max(-w2, p.x));
18204         p.y = min(h2, max(-h2, p.y));
18205       });
18206     });
18207   }
18208 });
18209
18210 /*
18211  * File: ForceDirected.js
18212  */
18213
18214 /*
18215    Class: ForceDirected
18216       
18217    A visualization that lays graphs using a Force-Directed layout algorithm.
18218    
18219    Inspired by:
18220   
18221    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18222    
18223   Implements:
18224   
18225   All <Loader> methods
18226   
18227    Constructor Options:
18228    
18229    Inherits options from
18230    
18231    - <Options.Canvas>
18232    - <Options.Controller>
18233    - <Options.Node>
18234    - <Options.Edge>
18235    - <Options.Label>
18236    - <Options.Events>
18237    - <Options.Tips>
18238    - <Options.NodeStyles>
18239    - <Options.Navigation>
18240    
18241    Additionally, there are two parameters
18242    
18243    levelDistance - (number) Default's *50*. The natural length desired for the edges.
18244    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*. 
18245      
18246    Instance Properties:
18247
18248    canvas - Access a <Canvas> instance.
18249    graph - Access a <Graph> instance.
18250    op - Access a <ForceDirected.Op> instance.
18251    fx - Access a <ForceDirected.Plot> instance.
18252    labels - Access a <ForceDirected.Label> interface implementation.
18253
18254 */
18255
18256 $jit.ForceDirected = new Class( {
18257
18258   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18259
18260   initialize: function(controller) {
18261     var $ForceDirected = $jit.ForceDirected;
18262
18263     var config = {
18264       iterations: 50,
18265       levelDistance: 50
18266     };
18267
18268     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18269         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18270
18271     var canvasConfig = this.config;
18272     if(canvasConfig.useCanvas) {
18273       this.canvas = canvasConfig.useCanvas;
18274       this.config.labelContainer = this.canvas.id + '-label';
18275     } else {
18276       if(canvasConfig.background) {
18277         canvasConfig.background = $.merge({
18278           type: 'Circles'
18279         }, canvasConfig.background);
18280       }
18281       this.canvas = new Canvas(this, canvasConfig);
18282       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18283     }
18284
18285     this.graphOptions = {
18286       'complex': true,
18287       'Node': {
18288         'selected': false,
18289         'exist': true,
18290         'drawn': true
18291       }
18292     };
18293     this.graph = new Graph(this.graphOptions, this.config.Node,
18294         this.config.Edge);
18295     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18296     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18297     this.op = new $ForceDirected.Op(this);
18298     this.json = null;
18299     this.busy = false;
18300     // initialize extras
18301     this.initializeExtras();
18302   },
18303
18304   /* 
18305     Method: refresh 
18306     
18307     Computes positions and plots the tree.
18308   */
18309   refresh: function() {
18310     this.compute();
18311     this.plot();
18312   },
18313
18314   reposition: function() {
18315     this.compute('end');
18316   },
18317
18318 /*
18319   Method: computeIncremental
18320   
18321   Performs the Force Directed algorithm incrementally.
18322   
18323   Description:
18324   
18325   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18326   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18327   avoiding browser messages such as "This script is taking too long to complete".
18328   
18329   Parameters:
18330   
18331   opt - (object) The object properties are described below
18332   
18333   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18334   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18335   
18336   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18337   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18338   computations for final animation positions then you can just choose 'end'.
18339   
18340   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18341   parameter a percentage value.
18342   
18343   onComplete - A callback function called when the algorithm completed.
18344   
18345   Example:
18346   
18347   In this example I calculate the end positions and then animate the graph to those positions
18348   
18349   (start code js)
18350   var fd = new $jit.ForceDirected(...);
18351   fd.computeIncremental({
18352     iter: 20,
18353     property: 'end',
18354     onStep: function(perc) {
18355       Log.write("loading " + perc + "%");
18356     },
18357     onComplete: function() {
18358       Log.write("done");
18359       fd.animate();
18360     }
18361   });
18362   (end code)
18363   
18364   In this example I calculate all positions and (re)plot the graph
18365   
18366   (start code js)
18367   var fd = new ForceDirected(...);
18368   fd.computeIncremental({
18369     iter: 20,
18370     property: ['end', 'start', 'current'],
18371     onStep: function(perc) {
18372       Log.write("loading " + perc + "%");
18373     },
18374     onComplete: function() {
18375       Log.write("done");
18376       fd.plot();
18377     }
18378   });
18379   (end code)
18380   
18381   */
18382   computeIncremental: function(opt) {
18383     opt = $.merge( {
18384       iter: 20,
18385       property: 'end',
18386       onStep: $.empty,
18387       onComplete: $.empty
18388     }, opt || {});
18389
18390     this.config.onBeforeCompute(this.graph.getNode(this.root));
18391     this.compute(opt.property, opt);
18392   },
18393
18394   /*
18395     Method: plot
18396    
18397     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18398    */
18399   plot: function() {
18400     this.fx.plot();
18401   },
18402
18403   /*
18404      Method: animate
18405     
18406      Animates the graph from the current positions to the 'end' node positions.
18407   */
18408   animate: function(opt) {
18409     this.fx.animate($.merge( {
18410       modes: [ 'linear' ]
18411     }, opt || {}));
18412   }
18413 });
18414
18415 $jit.ForceDirected.$extend = true;
18416
18417 (function(ForceDirected) {
18418
18419   /*
18420      Class: ForceDirected.Op
18421      
18422      Custom extension of <Graph.Op>.
18423
18424      Extends:
18425
18426      All <Graph.Op> methods
18427      
18428      See also:
18429      
18430      <Graph.Op>
18431
18432   */
18433   ForceDirected.Op = new Class( {
18434
18435     Implements: Graph.Op
18436
18437   });
18438
18439   /*
18440     Class: ForceDirected.Plot
18441     
18442     Custom extension of <Graph.Plot>.
18443   
18444     Extends:
18445   
18446     All <Graph.Plot> methods
18447     
18448     See also:
18449     
18450     <Graph.Plot>
18451   
18452   */
18453   ForceDirected.Plot = new Class( {
18454
18455     Implements: Graph.Plot
18456
18457   });
18458
18459   /*
18460     Class: ForceDirected.Label
18461     
18462     Custom extension of <Graph.Label>. 
18463     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18464   
18465     Extends:
18466   
18467     All <Graph.Label> methods and subclasses.
18468   
18469     See also:
18470   
18471     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18472   
18473   */
18474   ForceDirected.Label = {};
18475
18476   /*
18477      ForceDirected.Label.Native
18478      
18479      Custom extension of <Graph.Label.Native>.
18480
18481      Extends:
18482
18483      All <Graph.Label.Native> methods
18484
18485      See also:
18486
18487      <Graph.Label.Native>
18488
18489   */
18490   ForceDirected.Label.Native = new Class( {
18491     Implements: Graph.Label.Native
18492   });
18493
18494   /*
18495     ForceDirected.Label.SVG
18496     
18497     Custom extension of <Graph.Label.SVG>.
18498   
18499     Extends:
18500   
18501     All <Graph.Label.SVG> methods
18502   
18503     See also:
18504   
18505     <Graph.Label.SVG>
18506   
18507   */
18508   ForceDirected.Label.SVG = new Class( {
18509     Implements: Graph.Label.SVG,
18510
18511     initialize: function(viz) {
18512       this.viz = viz;
18513     },
18514
18515     /* 
18516        placeLabel
18517
18518        Overrides abstract method placeLabel in <Graph.Label>.
18519
18520        Parameters:
18521
18522        tag - A DOM label element.
18523        node - A <Graph.Node>.
18524        controller - A configuration/controller object passed to the visualization.
18525       
18526      */
18527     placeLabel: function(tag, node, controller) {
18528       var pos = node.pos.getc(true), 
18529           canvas = this.viz.canvas,
18530           ox = canvas.translateOffsetX,
18531           oy = canvas.translateOffsetY,
18532           sx = canvas.scaleOffsetX,
18533           sy = canvas.scaleOffsetY,
18534           radius = canvas.getSize();
18535       var labelPos = {
18536         x: Math.round(pos.x * sx + ox + radius.width / 2),
18537         y: Math.round(pos.y * sy + oy + radius.height / 2)
18538       };
18539       tag.setAttribute('x', labelPos.x);
18540       tag.setAttribute('y', labelPos.y);
18541
18542       controller.onPlaceLabel(tag, node);
18543     }
18544   });
18545
18546   /*
18547      ForceDirected.Label.HTML
18548      
18549      Custom extension of <Graph.Label.HTML>.
18550
18551      Extends:
18552
18553      All <Graph.Label.HTML> methods.
18554
18555      See also:
18556
18557      <Graph.Label.HTML>
18558
18559   */
18560   ForceDirected.Label.HTML = new Class( {
18561     Implements: Graph.Label.HTML,
18562
18563     initialize: function(viz) {
18564       this.viz = viz;
18565     },
18566     /* 
18567        placeLabel
18568
18569        Overrides abstract method placeLabel in <Graph.Plot>.
18570
18571        Parameters:
18572
18573        tag - A DOM label element.
18574        node - A <Graph.Node>.
18575        controller - A configuration/controller object passed to the visualization.
18576       
18577      */
18578     placeLabel: function(tag, node, controller) {
18579       var pos = node.pos.getc(true), 
18580           canvas = this.viz.canvas,
18581           ox = canvas.translateOffsetX,
18582           oy = canvas.translateOffsetY,
18583           sx = canvas.scaleOffsetX,
18584           sy = canvas.scaleOffsetY,
18585           radius = canvas.getSize();
18586       var labelPos = {
18587         x: Math.round(pos.x * sx + ox + radius.width / 2),
18588         y: Math.round(pos.y * sy + oy + radius.height / 2)
18589       };
18590       var style = tag.style;
18591       style.left = labelPos.x + 'px';
18592       style.top = labelPos.y + 'px';
18593       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18594
18595       controller.onPlaceLabel(tag, node);
18596     }
18597   });
18598
18599   /*
18600     Class: ForceDirected.Plot.NodeTypes
18601
18602     This class contains a list of <Graph.Node> built-in types. 
18603     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18604
18605     You can add your custom node types, customizing your visualization to the extreme.
18606
18607     Example:
18608
18609     (start code js)
18610       ForceDirected.Plot.NodeTypes.implement({
18611         'mySpecialType': {
18612           'render': function(node, canvas) {
18613             //print your custom node to canvas
18614           },
18615           //optional
18616           'contains': function(node, pos) {
18617             //return true if pos is inside the node or false otherwise
18618           }
18619         }
18620       });
18621     (end code)
18622
18623   */
18624   ForceDirected.Plot.NodeTypes = new Class({
18625     'none': {
18626       'render': $.empty,
18627       'contains': $.lambda(false)
18628     },
18629     'circle': {
18630       'render': function(node, canvas){
18631         var pos = node.pos.getc(true), 
18632             dim = node.getData('dim');
18633         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18634       },
18635       'contains': function(node, pos){
18636         var npos = node.pos.getc(true), 
18637             dim = node.getData('dim');
18638         return this.nodeHelper.circle.contains(npos, pos, dim);
18639       }
18640     },
18641     'ellipse': {
18642       'render': function(node, canvas){
18643         var pos = node.pos.getc(true), 
18644             width = node.getData('width'), 
18645             height = node.getData('height');
18646         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18647         },
18648       // TODO(nico): be more precise...
18649       'contains': function(node, pos){
18650         var npos = node.pos.getc(true), 
18651             width = node.getData('width'), 
18652             height = node.getData('height');
18653         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18654       }
18655     },
18656     'square': {
18657       'render': function(node, canvas){
18658         var pos = node.pos.getc(true), 
18659             dim = node.getData('dim');
18660         this.nodeHelper.square.render('fill', pos, dim, canvas);
18661       },
18662       'contains': function(node, pos){
18663         var npos = node.pos.getc(true), 
18664             dim = node.getData('dim');
18665         return this.nodeHelper.square.contains(npos, pos, dim);
18666       }
18667     },
18668     'rectangle': {
18669       'render': function(node, canvas){
18670         var pos = node.pos.getc(true), 
18671             width = node.getData('width'), 
18672             height = node.getData('height');
18673         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18674       },
18675       'contains': function(node, pos){
18676         var npos = node.pos.getc(true), 
18677             width = node.getData('width'), 
18678             height = node.getData('height');
18679         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18680       }
18681     },
18682     'triangle': {
18683       'render': function(node, canvas){
18684         var pos = node.pos.getc(true), 
18685             dim = node.getData('dim');
18686         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18687       },
18688       'contains': function(node, pos) {
18689         var npos = node.pos.getc(true), 
18690             dim = node.getData('dim');
18691         return this.nodeHelper.triangle.contains(npos, pos, dim);
18692       }
18693     },
18694     'star': {
18695       'render': function(node, canvas){
18696         var pos = node.pos.getc(true),
18697             dim = node.getData('dim');
18698         this.nodeHelper.star.render('fill', pos, dim, canvas);
18699       },
18700       'contains': function(node, pos) {
18701         var npos = node.pos.getc(true),
18702             dim = node.getData('dim');
18703         return this.nodeHelper.star.contains(npos, pos, dim);
18704       }
18705     }
18706   });
18707
18708   /*
18709     Class: ForceDirected.Plot.EdgeTypes
18710   
18711     This class contains a list of <Graph.Adjacence> built-in types. 
18712     Edge types implemented are 'none', 'line' and 'arrow'.
18713   
18714     You can add your custom edge types, customizing your visualization to the extreme.
18715   
18716     Example:
18717   
18718     (start code js)
18719       ForceDirected.Plot.EdgeTypes.implement({
18720         'mySpecialType': {
18721           'render': function(adj, canvas) {
18722             //print your custom edge to canvas
18723           },
18724           //optional
18725           'contains': function(adj, pos) {
18726             //return true if pos is inside the arc or false otherwise
18727           }
18728         }
18729       });
18730     (end code)
18731   
18732   */
18733   ForceDirected.Plot.EdgeTypes = new Class({
18734     'none': $.empty,
18735     'line': {
18736       'render': function(adj, canvas) {
18737         var from = adj.nodeFrom.pos.getc(true),
18738             to = adj.nodeTo.pos.getc(true);
18739         this.edgeHelper.line.render(from, to, canvas);
18740       },
18741       'contains': function(adj, pos) {
18742         var from = adj.nodeFrom.pos.getc(true),
18743             to = adj.nodeTo.pos.getc(true);
18744         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18745       }
18746     },
18747     'arrow': {
18748       'render': function(adj, canvas) {
18749         var from = adj.nodeFrom.pos.getc(true),
18750             to = adj.nodeTo.pos.getc(true),
18751             dim = adj.getData('dim'),
18752             direction = adj.data.$direction,
18753             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18754         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18755       },
18756       'contains': function(adj, pos) {
18757         var from = adj.nodeFrom.pos.getc(true),
18758             to = adj.nodeTo.pos.getc(true);
18759         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18760       }
18761     }
18762   });
18763
18764 })($jit.ForceDirected);
18765
18766
18767 /*
18768  * File: Treemap.js
18769  *
18770 */
18771
18772 $jit.TM = {};
18773
18774 var TM = $jit.TM;
18775
18776 $jit.TM.$extend = true;
18777
18778 /*
18779   Class: TM.Base
18780   
18781   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18782   
18783   Implements:
18784   
18785   All <Loader> methods
18786   
18787   Constructor Options:
18788   
18789   Inherits options from
18790   
18791   - <Options.Canvas>
18792   - <Options.Controller>
18793   - <Options.Node>
18794   - <Options.Edge>
18795   - <Options.Label>
18796   - <Options.Events>
18797   - <Options.Tips>
18798   - <Options.NodeStyles>
18799   - <Options.Navigation>
18800   
18801   Additionally, there are other parameters and some default values changed
18802
18803   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18804   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18805   offset - (number) Default's *2*. Boxes offset.
18806   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18807   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18808   animate - (boolean) Default's *false*. Whether to animate transitions.
18809   Node.type - Described in <Options.Node>. Default's *rectangle*.
18810   duration - Described in <Options.Fx>. Default's *700*.
18811   fps - Described in <Options.Fx>. Default's *45*.
18812   
18813   Instance Properties:
18814   
18815   canvas - Access a <Canvas> instance.
18816   graph - Access a <Graph> instance.
18817   op - Access a <TM.Op> instance.
18818   fx - Access a <TM.Plot> instance.
18819   labels - Access a <TM.Label> interface implementation.
18820
18821   Inspired by:
18822   
18823   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18824   
18825   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18826   
18827    Note:
18828    
18829    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.
18830
18831 */
18832 TM.Base = {
18833   layout: {
18834     orientation: "h",
18835     vertical: function(){
18836       return this.orientation == "v";
18837     },
18838     horizontal: function(){
18839       return this.orientation == "h";
18840     },
18841     change: function(){
18842       this.orientation = this.vertical()? "h" : "v";
18843     }
18844   },
18845
18846   initialize: function(controller){
18847     var config = {
18848       orientation: "h",
18849       titleHeight: 13,
18850       offset: 2,
18851       levelsToShow: 0,
18852       constrained: false,
18853       animate: false,
18854       Node: {
18855         type: 'rectangle',
18856         overridable: true,
18857         //we all know why this is not zero,
18858         //right, Firefox?
18859         width: 3,
18860         height: 3,
18861         color: '#444'
18862       },
18863       Label: {
18864         textAlign: 'center',
18865         textBaseline: 'top'
18866       },
18867       Edge: {
18868         type: 'none'
18869       },
18870       duration: 700,
18871       fps: 45
18872     };
18873
18874     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18875         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18876     this.layout.orientation = this.config.orientation;
18877
18878     var canvasConfig = this.config;
18879     if (canvasConfig.useCanvas) {
18880       this.canvas = canvasConfig.useCanvas;
18881       this.config.labelContainer = this.canvas.id + '-label';
18882     } else {
18883       if(canvasConfig.background) {
18884         canvasConfig.background = $.merge({
18885           type: 'Circles'
18886         }, canvasConfig.background);
18887       }
18888       this.canvas = new Canvas(this, canvasConfig);
18889       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18890     }
18891
18892     this.graphOptions = {
18893       'complex': true,
18894       'Node': {
18895         'selected': false,
18896         'exist': true,
18897         'drawn': true
18898       }
18899     };
18900     this.graph = new Graph(this.graphOptions, this.config.Node,
18901         this.config.Edge);
18902     this.labels = new TM.Label[canvasConfig.Label.type](this);
18903     this.fx = new TM.Plot(this);
18904     this.op = new TM.Op(this);
18905     this.group = new TM.Group(this);
18906     this.geom = new TM.Geom(this);
18907     this.clickedNode = null;
18908     this.busy = false;
18909     // initialize extras
18910     this.initializeExtras();
18911   },
18912
18913   /* 
18914     Method: refresh 
18915     
18916     Computes positions and plots the tree.
18917   */
18918   refresh: function(){
18919     if(this.busy) return;
18920     this.busy = true;
18921     var that = this;
18922     if(this.config.animate) {
18923       this.compute('end');
18924       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18925           && this.clickedNode.id || this.root));
18926       this.fx.animate($.merge(this.config, {
18927         modes: ['linear', 'node-property:width:height'],
18928         onComplete: function() {
18929           that.busy = false;
18930         }
18931       }));
18932     } else {
18933       var labelType = this.config.Label.type;
18934       if(labelType != 'Native') {
18935         var that = this;
18936         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18937       }
18938       this.busy = false;
18939       this.compute();
18940       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18941           && this.clickedNode.id || this.root));
18942       this.plot();
18943     }
18944   },
18945
18946   /* 
18947     Method: plot 
18948     
18949     Plots the TreeMap. This is a shortcut to *fx.plot*. 
18950   
18951    */
18952   plot: function(){
18953     this.fx.plot();
18954   },
18955
18956   /* 
18957   Method: leaf 
18958   
18959   Returns whether the node is a leaf.
18960   
18961    Parameters:
18962    
18963    n - (object) A <Graph.Node>.
18964
18965  */
18966   leaf: function(n){
18967     return n.getSubnodes([
18968         1, 1
18969     ], "ignore").length == 0;
18970   },
18971   
18972   /* 
18973   Method: enter 
18974   
18975   Sets the node as root.
18976   
18977    Parameters:
18978    
18979    n - (object) A <Graph.Node>.
18980
18981  */
18982   enter: function(n){
18983     if(this.busy) return;
18984     this.busy = true;
18985     
18986     var that = this,
18987         config = this.config,
18988         graph = this.graph,
18989         clickedNode = n,
18990         previousClickedNode = this.clickedNode;
18991
18992     var callback = {
18993       onComplete: function() {
18994         //ensure that nodes are shown for that level
18995         if(config.levelsToShow > 0) {
18996           that.geom.setRightLevelToShow(n);
18997         }
18998         //compute positions of newly inserted nodes
18999         if(config.levelsToShow > 0 || config.request) that.compute();
19000         if(config.animate) {
19001           //fade nodes
19002           graph.nodeList.setData('alpha', 0, 'end');
19003           n.eachSubgraph(function(n) {
19004             n.setData('alpha', 1, 'end');
19005           }, "ignore");
19006           that.fx.animate({
19007             duration: 500,
19008             modes:['node-property:alpha'],
19009             onComplete: function() {
19010               //compute end positions
19011               that.clickedNode = clickedNode;
19012               that.compute('end');
19013               //animate positions
19014               //TODO(nico) commenting this line didn't seem to throw errors...
19015               that.clickedNode = previousClickedNode;
19016               that.fx.animate({
19017                 modes:['linear', 'node-property:width:height'],
19018                 duration: 1000,
19019                 onComplete: function() { 
19020                   that.busy = false;
19021                   //TODO(nico) check comment above
19022                   that.clickedNode = clickedNode;
19023                 }
19024               });
19025             }
19026           });
19027         } else {
19028           that.busy = false;
19029           that.clickedNode = n;
19030           that.refresh();
19031         }
19032       }
19033     };
19034     if(config.request) {
19035       this.requestNodes(clickedNode, callback);
19036     } else {
19037       callback.onComplete();
19038     }
19039   },
19040
19041   /* 
19042   Method: out 
19043   
19044   Sets the parent node of the current selected node as root.
19045
19046  */
19047   out: function(){
19048     if(this.busy) return;
19049     this.busy = true;
19050     this.events.hoveredNode = false;
19051     var that = this,
19052         config = this.config,
19053         graph = this.graph,
19054         parents = graph.getNode(this.clickedNode 
19055             && this.clickedNode.id || this.root).getParents(),
19056         parent = parents[0],
19057         clickedNode = parent,
19058         previousClickedNode = this.clickedNode;
19059     
19060     //if no parents return
19061     if(!parent) {
19062       this.busy = false;
19063       return;
19064     }
19065     //final plot callback
19066     callback = {
19067       onComplete: function() {
19068         that.clickedNode = parent;
19069         if(config.request) {
19070           that.requestNodes(parent, {
19071             onComplete: function() {
19072               that.compute();
19073               that.plot();
19074               that.busy = false;
19075             }
19076           });
19077         } else {
19078           that.compute();
19079           that.plot();
19080           that.busy = false;
19081         }
19082       }
19083     };
19084     //prune tree
19085     if (config.levelsToShow > 0)
19086       this.geom.setRightLevelToShow(parent);
19087     //animate node positions
19088     if(config.animate) {
19089       this.clickedNode = clickedNode;
19090       this.compute('end');
19091       //animate the visible subtree only
19092       this.clickedNode = previousClickedNode;
19093       this.fx.animate({
19094         modes:['linear', 'node-property:width:height'],
19095         duration: 1000,
19096         onComplete: function() {
19097           //animate the parent subtree
19098           that.clickedNode = clickedNode;
19099           //change nodes alpha
19100           graph.eachNode(function(n) {
19101             n.setDataset(['current', 'end'], {
19102               'alpha': [0, 1]
19103             });
19104           }, "ignore");
19105           previousClickedNode.eachSubgraph(function(node) {
19106             node.setData('alpha', 1);
19107           }, "ignore");
19108           that.fx.animate({
19109             duration: 500,
19110             modes:['node-property:alpha'],
19111             onComplete: function() {
19112               callback.onComplete();
19113             }
19114           });
19115         }
19116       });
19117     } else {
19118       callback.onComplete();
19119     }
19120   },
19121
19122   requestNodes: function(node, onComplete){
19123     var handler = $.merge(this.controller, onComplete), 
19124         lev = this.config.levelsToShow;
19125     if (handler.request) {
19126       var leaves = [], d = node._depth;
19127       node.eachLevel(0, lev, function(n){
19128         var nodeLevel = lev - (n._depth - d);
19129         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19130           leaves.push(n);
19131           n._level = nodeLevel;
19132         }
19133       });
19134       this.group.requestNodes(leaves, handler);
19135     } else {
19136       handler.onComplete();
19137     }
19138   }
19139 };
19140
19141 /*
19142   Class: TM.Op
19143   
19144   Custom extension of <Graph.Op>.
19145   
19146   Extends:
19147   
19148   All <Graph.Op> methods
19149   
19150   See also:
19151   
19152   <Graph.Op>
19153   
19154   */
19155 TM.Op = new Class({
19156   Implements: Graph.Op,
19157
19158   initialize: function(viz){
19159     this.viz = viz;
19160   }
19161 });
19162
19163 //extend level methods of Graph.Geom
19164 TM.Geom = new Class({
19165   Implements: Graph.Geom,
19166   
19167   getRightLevelToShow: function() {
19168     return this.viz.config.levelsToShow;
19169   },
19170   
19171   setRightLevelToShow: function(node) {
19172     var level = this.getRightLevelToShow(), 
19173         fx = this.viz.labels;
19174     node.eachLevel(0, level+1, function(n) {
19175       var d = n._depth - node._depth;
19176       if(d > level) {
19177         n.drawn = false; 
19178         n.exist = false;
19179         n.ignore = true;
19180         fx.hideLabel(n, false);
19181       } else {
19182         n.drawn = true;
19183         n.exist = true;
19184         delete n.ignore;
19185       }
19186     });
19187     node.drawn = true;
19188     delete node.ignore;
19189   }
19190 });
19191
19192 /*
19193
19194 Performs operations on group of nodes.
19195
19196 */
19197 TM.Group = new Class( {
19198
19199   initialize: function(viz){
19200     this.viz = viz;
19201     this.canvas = viz.canvas;
19202     this.config = viz.config;
19203   },
19204
19205   /*
19206   
19207     Calls the request method on the controller to request a subtree for each node. 
19208   */
19209   requestNodes: function(nodes, controller){
19210     var counter = 0, len = nodes.length, nodeSelected = {};
19211     var complete = function(){
19212       controller.onComplete();
19213     };
19214     var viz = this.viz;
19215     if (len == 0)
19216       complete();
19217     for ( var i = 0; i < len; i++) {
19218       nodeSelected[nodes[i].id] = nodes[i];
19219       controller.request(nodes[i].id, nodes[i]._level, {
19220         onComplete: function(nodeId, data){
19221           if (data && data.children) {
19222             data.id = nodeId;
19223             viz.op.sum(data, {
19224               type: 'nothing'
19225             });
19226           }
19227           if (++counter == len) {
19228             viz.graph.computeLevels(viz.root, 0);
19229             complete();
19230           }
19231         }
19232       });
19233     }
19234   }
19235 });
19236
19237 /*
19238   Class: TM.Plot
19239   
19240   Custom extension of <Graph.Plot>.
19241   
19242   Extends:
19243   
19244   All <Graph.Plot> methods
19245   
19246   See also:
19247   
19248   <Graph.Plot>
19249   
19250   */
19251 TM.Plot = new Class({
19252
19253   Implements: Graph.Plot,
19254
19255   initialize: function(viz){
19256     this.viz = viz;
19257     this.config = viz.config;
19258     this.node = this.config.Node;
19259     this.edge = this.config.Edge;
19260     this.animation = new Animation;
19261     this.nodeTypes = new TM.Plot.NodeTypes;
19262     this.edgeTypes = new TM.Plot.EdgeTypes;
19263     this.labels = viz.labels;
19264   },
19265
19266   plot: function(opt, animating){
19267     var viz = this.viz, 
19268         graph = viz.graph;
19269     viz.canvas.clear();
19270     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19271       'withLabels': true,
19272       'hideLabels': false,
19273       'plotSubtree': function(n, ch){
19274         return n.anySubnode("exist");
19275       }
19276     }), animating);
19277   }
19278 });
19279
19280 /*
19281   Class: TM.Label
19282   
19283   Custom extension of <Graph.Label>. 
19284   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19285
19286   Extends:
19287
19288   All <Graph.Label> methods and subclasses.
19289
19290   See also:
19291
19292   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19293   
19294 */
19295 TM.Label = {};
19296
19297 /*
19298  TM.Label.Native
19299
19300  Custom extension of <Graph.Label.Native>.
19301
19302  Extends:
19303
19304  All <Graph.Label.Native> methods
19305
19306  See also:
19307
19308  <Graph.Label.Native>
19309 */
19310 TM.Label.Native = new Class({
19311   Implements: Graph.Label.Native,
19312
19313   initialize: function(viz) {
19314     this.config = viz.config;
19315     this.leaf = viz.leaf;
19316   },
19317   
19318   renderLabel: function(canvas, node, controller){
19319     if(!this.leaf(node) && !this.config.titleHeight) return;
19320     var pos = node.pos.getc(true), 
19321         ctx = canvas.getCtx(),
19322         width = node.getData('width'),
19323         height = node.getData('height'),
19324         x = pos.x + width/2,
19325         y = pos.y;
19326         
19327     ctx.fillText(node.name, x, y, width);
19328   }
19329 });
19330
19331 /*
19332  TM.Label.SVG
19333
19334   Custom extension of <Graph.Label.SVG>.
19335
19336   Extends:
19337
19338   All <Graph.Label.SVG> methods
19339
19340   See also:
19341
19342   <Graph.Label.SVG>
19343 */
19344 TM.Label.SVG = new Class( {
19345   Implements: Graph.Label.SVG,
19346
19347   initialize: function(viz){
19348     this.viz = viz;
19349     this.leaf = viz.leaf;
19350     this.config = viz.config;
19351   },
19352
19353   /* 
19354   placeLabel
19355
19356   Overrides abstract method placeLabel in <Graph.Plot>.
19357
19358   Parameters:
19359
19360   tag - A DOM label element.
19361   node - A <Graph.Node>.
19362   controller - A configuration/controller object passed to the visualization.
19363   
19364   */
19365   placeLabel: function(tag, node, controller){
19366     var pos = node.pos.getc(true), 
19367         canvas = this.viz.canvas,
19368         ox = canvas.translateOffsetX,
19369         oy = canvas.translateOffsetY,
19370         sx = canvas.scaleOffsetX,
19371         sy = canvas.scaleOffsetY,
19372         radius = canvas.getSize();
19373     var labelPos = {
19374       x: Math.round(pos.x * sx + ox + radius.width / 2),
19375       y: Math.round(pos.y * sy + oy + radius.height / 2)
19376     };
19377     tag.setAttribute('x', labelPos.x);
19378     tag.setAttribute('y', labelPos.y);
19379
19380     if(!this.leaf(node) && !this.config.titleHeight) {
19381       tag.style.display = 'none';
19382     }
19383     controller.onPlaceLabel(tag, node);
19384   }
19385 });
19386
19387 /*
19388  TM.Label.HTML
19389
19390  Custom extension of <Graph.Label.HTML>.
19391
19392  Extends:
19393
19394  All <Graph.Label.HTML> methods.
19395
19396  See also:
19397
19398  <Graph.Label.HTML>
19399
19400 */
19401 TM.Label.HTML = new Class( {
19402   Implements: Graph.Label.HTML,
19403
19404   initialize: function(viz){
19405     this.viz = viz;
19406     this.leaf = viz.leaf;
19407     this.config = viz.config;
19408   },
19409
19410   /* 
19411     placeLabel
19412   
19413     Overrides abstract method placeLabel in <Graph.Plot>.
19414   
19415     Parameters:
19416   
19417     tag - A DOM label element.
19418     node - A <Graph.Node>.
19419     controller - A configuration/controller object passed to the visualization.
19420   
19421   */
19422   placeLabel: function(tag, node, controller){
19423     var pos = node.pos.getc(true), 
19424         canvas = this.viz.canvas,
19425         ox = canvas.translateOffsetX,
19426         oy = canvas.translateOffsetY,
19427         sx = canvas.scaleOffsetX,
19428         sy = canvas.scaleOffsetY,
19429         radius = canvas.getSize();
19430     var labelPos = {
19431       x: Math.round(pos.x * sx + ox + radius.width / 2),
19432       y: Math.round(pos.y * sy + oy + radius.height / 2)
19433     };
19434
19435     var style = tag.style;
19436     style.left = labelPos.x + 'px';
19437     style.top = labelPos.y + 'px';
19438     style.width = node.getData('width') * sx + 'px';
19439     style.height = node.getData('height') * sy + 'px';
19440     style.zIndex = node._depth * 100;
19441     style.display = '';
19442
19443     if(!this.leaf(node) && !this.config.titleHeight) {
19444       tag.style.display = 'none';
19445     }
19446     controller.onPlaceLabel(tag, node);
19447   }
19448 });
19449
19450 /*
19451   Class: TM.Plot.NodeTypes
19452
19453   This class contains a list of <Graph.Node> built-in types. 
19454   Node types implemented are 'none', 'rectangle'.
19455
19456   You can add your custom node types, customizing your visualization to the extreme.
19457
19458   Example:
19459
19460   (start code js)
19461     TM.Plot.NodeTypes.implement({
19462       'mySpecialType': {
19463         'render': function(node, canvas) {
19464           //print your custom node to canvas
19465         },
19466         //optional
19467         'contains': function(node, pos) {
19468           //return true if pos is inside the node or false otherwise
19469         }
19470       }
19471     });
19472   (end code)
19473
19474 */
19475 TM.Plot.NodeTypes = new Class( {
19476   'none': {
19477     'render': $.empty
19478   },
19479
19480   'rectangle': {
19481     'render': function(node, canvas, animating){
19482       var leaf = this.viz.leaf(node),
19483           config = this.config,
19484           offst = config.offset,
19485           titleHeight = config.titleHeight,
19486           pos = node.pos.getc(true),
19487           width = node.getData('width'),
19488           height = node.getData('height'),
19489           border = node.getData('border'),
19490           ctx = canvas.getCtx(),
19491           posx = pos.x + offst / 2, 
19492           posy = pos.y + offst / 2;
19493       if(width <= offst || height <= offst) return;
19494       if (leaf) {
19495         if(config.cushion) {
19496           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19497               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19498           var color = node.getData('color');
19499           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19500               function(r) { return r * 0.2 >> 0; }));
19501           lg.addColorStop(0, color);
19502           lg.addColorStop(1, colorGrad);
19503           ctx.fillStyle = lg;
19504         }
19505         ctx.fillRect(posx, posy, width - offst, height - offst);
19506         if(border) {
19507           ctx.save();
19508           ctx.strokeStyle = border;
19509           ctx.strokeRect(posx, posy, width - offst, height - offst);
19510           ctx.restore();
19511         }
19512       } else if(titleHeight > 0){
19513         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19514             titleHeight - offst);
19515         if(border) {
19516           ctx.save();
19517           ctx.strokeStyle = border;
19518           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19519               height - offst);
19520           ctx.restore();
19521         }
19522       }
19523     },
19524     'contains': function(node, pos) {
19525       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19526       var npos = node.pos.getc(true),
19527           width = node.getData('width'), 
19528           leaf = this.viz.leaf(node),
19529           height = leaf? node.getData('height') : this.config.titleHeight;
19530       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19531     }
19532   }
19533 });
19534
19535 TM.Plot.EdgeTypes = new Class( {
19536   'none': $.empty
19537 });
19538
19539 /*
19540   Class: TM.SliceAndDice
19541   
19542   A slice and dice TreeMap visualization.
19543   
19544   Implements:
19545   
19546   All <TM.Base> methods and properties.
19547 */
19548 TM.SliceAndDice = new Class( {
19549   Implements: [
19550       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19551   ]
19552 });
19553
19554 /*
19555   Class: TM.Squarified
19556   
19557   A squarified TreeMap visualization.
19558
19559   Implements:
19560   
19561   All <TM.Base> methods and properties.
19562 */
19563 TM.Squarified = new Class( {
19564   Implements: [
19565       Loader, Extras, TM.Base, Layouts.TM.Squarified
19566   ]
19567 });
19568
19569 /*
19570   Class: TM.Strip
19571   
19572   A strip TreeMap visualization.
19573
19574   Implements:
19575   
19576   All <TM.Base> methods and properties.
19577 */
19578 TM.Strip = new Class( {
19579   Implements: [
19580       Loader, Extras, TM.Base, Layouts.TM.Strip
19581   ]
19582 });
19583
19584
19585 /*
19586  * File: RGraph.js
19587  *
19588  */
19589
19590 /*
19591    Class: RGraph
19592    
19593    A radial graph visualization with advanced animations.
19594    
19595    Inspired by:
19596  
19597    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>
19598    
19599    Note:
19600    
19601    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.
19602    
19603   Implements:
19604   
19605   All <Loader> methods
19606   
19607    Constructor Options:
19608    
19609    Inherits options from
19610    
19611    - <Options.Canvas>
19612    - <Options.Controller>
19613    - <Options.Node>
19614    - <Options.Edge>
19615    - <Options.Label>
19616    - <Options.Events>
19617    - <Options.Tips>
19618    - <Options.NodeStyles>
19619    - <Options.Navigation>
19620    
19621    Additionally, there are other parameters and some default values changed
19622    
19623    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19624    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19625      
19626    Instance Properties:
19627
19628    canvas - Access a <Canvas> instance.
19629    graph - Access a <Graph> instance.
19630    op - Access a <RGraph.Op> instance.
19631    fx - Access a <RGraph.Plot> instance.
19632    labels - Access a <RGraph.Label> interface implementation.   
19633 */
19634
19635 $jit.RGraph = new Class( {
19636
19637   Implements: [
19638       Loader, Extras, Layouts.Radial
19639   ],
19640
19641   initialize: function(controller){
19642     var $RGraph = $jit.RGraph;
19643
19644     var config = {
19645       interpolation: 'linear',
19646       levelDistance: 100
19647     };
19648
19649     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19650         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19651
19652     var canvasConfig = this.config;
19653     if(canvasConfig.useCanvas) {
19654       this.canvas = canvasConfig.useCanvas;
19655       this.config.labelContainer = this.canvas.id + '-label';
19656     } else {
19657       if(canvasConfig.background) {
19658         canvasConfig.background = $.merge({
19659           type: 'Circles'
19660         }, canvasConfig.background);
19661       }
19662       this.canvas = new Canvas(this, canvasConfig);
19663       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19664     }
19665
19666     this.graphOptions = {
19667       'complex': false,
19668       'Node': {
19669         'selected': false,
19670         'exist': true,
19671         'drawn': true
19672       }
19673     };
19674     this.graph = new Graph(this.graphOptions, this.config.Node,
19675         this.config.Edge);
19676     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19677     this.fx = new $RGraph.Plot(this, $RGraph);
19678     this.op = new $RGraph.Op(this);
19679     this.json = null;
19680     this.root = null;
19681     this.busy = false;
19682     this.parent = false;
19683     // initialize extras
19684     this.initializeExtras();
19685   },
19686
19687   /* 
19688   
19689     createLevelDistanceFunc 
19690   
19691     Returns the levelDistance function used for calculating a node distance 
19692     to its origin. This function returns a function that is computed 
19693     per level and not per node, such that all nodes with the same depth will have the 
19694     same distance to the origin. The resulting function gets the 
19695     parent node as parameter and returns a float.
19696
19697    */
19698   createLevelDistanceFunc: function(){
19699     var ld = this.config.levelDistance;
19700     return function(elem){
19701       return (elem._depth + 1) * ld;
19702     };
19703   },
19704
19705   /* 
19706      Method: refresh 
19707      
19708      Computes positions and plots the tree.
19709
19710    */
19711   refresh: function(){
19712     this.compute();
19713     this.plot();
19714   },
19715
19716   reposition: function(){
19717     this.compute('end');
19718   },
19719
19720   /*
19721    Method: plot
19722   
19723    Plots the RGraph. This is a shortcut to *fx.plot*.
19724   */
19725   plot: function(){
19726     this.fx.plot();
19727   },
19728   /*
19729    getNodeAndParentAngle
19730   
19731    Returns the _parent_ of the given node, also calculating its angle span.
19732   */
19733   getNodeAndParentAngle: function(id){
19734     var theta = false;
19735     var n = this.graph.getNode(id);
19736     var ps = n.getParents();
19737     var p = (ps.length > 0)? ps[0] : false;
19738     if (p) {
19739       var posParent = p.pos.getc(), posChild = n.pos.getc();
19740       var newPos = posParent.add(posChild.scale(-1));
19741       theta = Math.atan2(newPos.y, newPos.x);
19742       if (theta < 0)
19743         theta += 2 * Math.PI;
19744     }
19745     return {
19746       parent: p,
19747       theta: theta
19748     };
19749   },
19750   /*
19751    tagChildren
19752   
19753    Enumerates the children in order to maintain child ordering (second constraint of the paper).
19754   */
19755   tagChildren: function(par, id){
19756     if (par.angleSpan) {
19757       var adjs = [];
19758       par.eachAdjacency(function(elem){
19759         adjs.push(elem.nodeTo);
19760       }, "ignore");
19761       var len = adjs.length;
19762       for ( var i = 0; i < len && id != adjs[i].id; i++)
19763         ;
19764       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19765         adjs[j].dist = k++;
19766       }
19767     }
19768   },
19769   /* 
19770   Method: onClick 
19771   
19772   Animates the <RGraph> to center the node specified by *id*.
19773
19774    Parameters:
19775
19776    id - A <Graph.Node> id.
19777    opt - (optional|object) An object containing some extra properties described below
19778    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19779
19780    Example:
19781
19782    (start code js)
19783      rgraph.onClick('someid');
19784      //or also...
19785      rgraph.onClick('someid', {
19786       hideLabels: false
19787      });
19788     (end code)
19789     
19790   */
19791   onClick: function(id, opt){
19792     if (this.root != id && !this.busy) {
19793       this.busy = true;
19794       this.root = id;
19795       that = this;
19796       this.controller.onBeforeCompute(this.graph.getNode(id));
19797       var obj = this.getNodeAndParentAngle(id);
19798
19799       // second constraint
19800       this.tagChildren(obj.parent, id);
19801       this.parent = obj.parent;
19802       this.compute('end');
19803
19804       // first constraint
19805       var thetaDiff = obj.theta - obj.parent.endPos.theta;
19806       this.graph.eachNode(function(elem){
19807         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19808       });
19809
19810       var mode = this.config.interpolation;
19811       opt = $.merge( {
19812         onComplete: $.empty
19813       }, opt || {});
19814
19815       this.fx.animate($.merge( {
19816         hideLabels: true,
19817         modes: [
19818           mode
19819         ]
19820       }, opt, {
19821         onComplete: function(){
19822           that.busy = false;
19823           opt.onComplete();
19824         }
19825       }));
19826     }
19827   }
19828 });
19829
19830 $jit.RGraph.$extend = true;
19831
19832 (function(RGraph){
19833
19834   /*
19835      Class: RGraph.Op
19836      
19837      Custom extension of <Graph.Op>.
19838
19839      Extends:
19840
19841      All <Graph.Op> methods
19842      
19843      See also:
19844      
19845      <Graph.Op>
19846
19847   */
19848   RGraph.Op = new Class( {
19849
19850     Implements: Graph.Op
19851
19852   });
19853
19854   /*
19855      Class: RGraph.Plot
19856     
19857     Custom extension of <Graph.Plot>.
19858   
19859     Extends:
19860   
19861     All <Graph.Plot> methods
19862     
19863     See also:
19864     
19865     <Graph.Plot>
19866   
19867   */
19868   RGraph.Plot = new Class( {
19869
19870     Implements: Graph.Plot
19871
19872   });
19873
19874   /*
19875     Object: RGraph.Label
19876
19877     Custom extension of <Graph.Label>. 
19878     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19879   
19880     Extends:
19881   
19882     All <Graph.Label> methods and subclasses.
19883   
19884     See also:
19885   
19886     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19887   
19888    */
19889   RGraph.Label = {};
19890
19891   /*
19892      RGraph.Label.Native
19893
19894      Custom extension of <Graph.Label.Native>.
19895
19896      Extends:
19897
19898      All <Graph.Label.Native> methods
19899
19900      See also:
19901
19902      <Graph.Label.Native>
19903
19904   */
19905   RGraph.Label.Native = new Class( {
19906     Implements: Graph.Label.Native
19907   });
19908
19909   /*
19910      RGraph.Label.SVG
19911     
19912     Custom extension of <Graph.Label.SVG>.
19913   
19914     Extends:
19915   
19916     All <Graph.Label.SVG> methods
19917   
19918     See also:
19919   
19920     <Graph.Label.SVG>
19921   
19922   */
19923   RGraph.Label.SVG = new Class( {
19924     Implements: Graph.Label.SVG,
19925
19926     initialize: function(viz){
19927       this.viz = viz;
19928     },
19929
19930     /* 
19931        placeLabel
19932
19933        Overrides abstract method placeLabel in <Graph.Plot>.
19934
19935        Parameters:
19936
19937        tag - A DOM label element.
19938        node - A <Graph.Node>.
19939        controller - A configuration/controller object passed to the visualization.
19940       
19941      */
19942     placeLabel: function(tag, node, controller){
19943       var pos = node.pos.getc(true), 
19944           canvas = this.viz.canvas,
19945           ox = canvas.translateOffsetX,
19946           oy = canvas.translateOffsetY,
19947           sx = canvas.scaleOffsetX,
19948           sy = canvas.scaleOffsetY,
19949           radius = canvas.getSize();
19950       var labelPos = {
19951         x: Math.round(pos.x * sx + ox + radius.width / 2),
19952         y: Math.round(pos.y * sy + oy + radius.height / 2)
19953       };
19954       tag.setAttribute('x', labelPos.x);
19955       tag.setAttribute('y', labelPos.y);
19956
19957       controller.onPlaceLabel(tag, node);
19958     }
19959   });
19960
19961   /*
19962      RGraph.Label.HTML
19963
19964      Custom extension of <Graph.Label.HTML>.
19965
19966      Extends:
19967
19968      All <Graph.Label.HTML> methods.
19969
19970      See also:
19971
19972      <Graph.Label.HTML>
19973
19974   */
19975   RGraph.Label.HTML = new Class( {
19976     Implements: Graph.Label.HTML,
19977
19978     initialize: function(viz){
19979       this.viz = viz;
19980     },
19981     /* 
19982        placeLabel
19983
19984        Overrides abstract method placeLabel in <Graph.Plot>.
19985
19986        Parameters:
19987
19988        tag - A DOM label element.
19989        node - A <Graph.Node>.
19990        controller - A configuration/controller object passed to the visualization.
19991       
19992      */
19993     placeLabel: function(tag, node, controller){
19994       var pos = node.pos.getc(true), 
19995           canvas = this.viz.canvas,
19996           ox = canvas.translateOffsetX,
19997           oy = canvas.translateOffsetY,
19998           sx = canvas.scaleOffsetX,
19999           sy = canvas.scaleOffsetY,
20000           radius = canvas.getSize();
20001       var labelPos = {
20002         x: Math.round(pos.x * sx + ox + radius.width / 2),
20003         y: Math.round(pos.y * sy + oy + radius.height / 2)
20004       };
20005
20006       var style = tag.style;
20007       style.left = labelPos.x + 'px';
20008       style.top = labelPos.y + 'px';
20009       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20010
20011       controller.onPlaceLabel(tag, node);
20012     }
20013   });
20014
20015   /*
20016     Class: RGraph.Plot.NodeTypes
20017
20018     This class contains a list of <Graph.Node> built-in types. 
20019     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20020
20021     You can add your custom node types, customizing your visualization to the extreme.
20022
20023     Example:
20024
20025     (start code js)
20026       RGraph.Plot.NodeTypes.implement({
20027         'mySpecialType': {
20028           'render': function(node, canvas) {
20029             //print your custom node to canvas
20030           },
20031           //optional
20032           'contains': function(node, pos) {
20033             //return true if pos is inside the node or false otherwise
20034           }
20035         }
20036       });
20037     (end code)
20038
20039   */
20040   RGraph.Plot.NodeTypes = new Class({
20041     'none': {
20042       'render': $.empty,
20043       'contains': $.lambda(false)
20044     },
20045     'circle': {
20046       'render': function(node, canvas){
20047         var pos = node.pos.getc(true), 
20048             dim = node.getData('dim');
20049         this.nodeHelper.circle.render('fill', pos, dim, canvas);
20050       },
20051       'contains': function(node, pos){
20052         var npos = node.pos.getc(true), 
20053             dim = node.getData('dim');
20054         return this.nodeHelper.circle.contains(npos, pos, dim);
20055       }
20056     },
20057     'ellipse': {
20058       'render': function(node, canvas){
20059         var pos = node.pos.getc(true), 
20060             width = node.getData('width'), 
20061             height = node.getData('height');
20062         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20063         },
20064       // TODO(nico): be more precise...
20065       'contains': function(node, pos){
20066         var npos = node.pos.getc(true), 
20067             width = node.getData('width'), 
20068             height = node.getData('height');
20069         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20070       }
20071     },
20072     'square': {
20073       'render': function(node, canvas){
20074         var pos = node.pos.getc(true), 
20075             dim = node.getData('dim');
20076         this.nodeHelper.square.render('fill', pos, dim, canvas);
20077       },
20078       'contains': function(node, pos){
20079         var npos = node.pos.getc(true), 
20080             dim = node.getData('dim');
20081         return this.nodeHelper.square.contains(npos, pos, dim);
20082       }
20083     },
20084     'rectangle': {
20085       'render': function(node, canvas){
20086         var pos = node.pos.getc(true), 
20087             width = node.getData('width'), 
20088             height = node.getData('height');
20089         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20090       },
20091       'contains': function(node, pos){
20092         var npos = node.pos.getc(true), 
20093             width = node.getData('width'), 
20094             height = node.getData('height');
20095         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20096       }
20097     },
20098     'triangle': {
20099       'render': function(node, canvas){
20100         var pos = node.pos.getc(true), 
20101             dim = node.getData('dim');
20102         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20103       },
20104       'contains': function(node, pos) {
20105         var npos = node.pos.getc(true), 
20106             dim = node.getData('dim');
20107         return this.nodeHelper.triangle.contains(npos, pos, dim);
20108       }
20109     },
20110     'star': {
20111       'render': function(node, canvas){
20112         var pos = node.pos.getc(true),
20113             dim = node.getData('dim');
20114         this.nodeHelper.star.render('fill', pos, dim, canvas);
20115       },
20116       'contains': function(node, pos) {
20117         var npos = node.pos.getc(true),
20118             dim = node.getData('dim');
20119         return this.nodeHelper.star.contains(npos, pos, dim);
20120       }
20121     }
20122   });
20123
20124   /*
20125     Class: RGraph.Plot.EdgeTypes
20126
20127     This class contains a list of <Graph.Adjacence> built-in types. 
20128     Edge types implemented are 'none', 'line' and 'arrow'.
20129   
20130     You can add your custom edge types, customizing your visualization to the extreme.
20131   
20132     Example:
20133   
20134     (start code js)
20135       RGraph.Plot.EdgeTypes.implement({
20136         'mySpecialType': {
20137           'render': function(adj, canvas) {
20138             //print your custom edge to canvas
20139           },
20140           //optional
20141           'contains': function(adj, pos) {
20142             //return true if pos is inside the arc or false otherwise
20143           }
20144         }
20145       });
20146     (end code)
20147   
20148   */
20149   RGraph.Plot.EdgeTypes = new Class({
20150     'none': $.empty,
20151     'line': {
20152       'render': function(adj, canvas) {
20153         var from = adj.nodeFrom.pos.getc(true),
20154             to = adj.nodeTo.pos.getc(true);
20155         this.edgeHelper.line.render(from, to, canvas);
20156       },
20157       'contains': function(adj, pos) {
20158         var from = adj.nodeFrom.pos.getc(true),
20159             to = adj.nodeTo.pos.getc(true);
20160         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20161       }
20162     },
20163     'arrow': {
20164       'render': function(adj, canvas) {
20165         var from = adj.nodeFrom.pos.getc(true),
20166             to = adj.nodeTo.pos.getc(true),
20167             dim = adj.getData('dim'),
20168             direction = adj.data.$direction,
20169             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20170         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20171       },
20172       'contains': function(adj, pos) {
20173         var from = adj.nodeFrom.pos.getc(true),
20174             to = adj.nodeTo.pos.getc(true);
20175         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20176       }
20177     }
20178   });
20179
20180 })($jit.RGraph);
20181
20182
20183 /*
20184  * File: Hypertree.js
20185  * 
20186 */
20187
20188 /* 
20189      Complex 
20190      
20191      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
20192  
20193 */
20194 /* 
20195    moebiusTransformation 
20196  
20197    Calculates a moebius transformation for this point / complex. 
20198     For more information go to: 
20199         http://en.wikipedia.org/wiki/Moebius_transformation. 
20200  
20201    Parameters: 
20202  
20203       c - An initialized Complex instance representing a translation Vector. 
20204 */
20205
20206 Complex.prototype.moebiusTransformation = function(c) {
20207   var num = this.add(c);
20208   var den = c.$conjugate().$prod(this);
20209   den.x++;
20210   return num.$div(den);
20211 };
20212
20213 /* 
20214     moebiusTransformation 
20215      
20216     Calculates a moebius transformation for the hyperbolic tree. 
20217      
20218     <http://en.wikipedia.org/wiki/Moebius_transformation> 
20219       
20220      Parameters: 
20221      
20222         graph - A <Graph> instance.
20223         pos - A <Complex>.
20224         prop - A property array.
20225         theta - Rotation angle. 
20226         startPos - _optional_ start position. 
20227 */
20228 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20229   this.eachNode(graph, function(elem) {
20230     for ( var i = 0; i < prop.length; i++) {
20231       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20232       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20233     }
20234   }, flags);
20235 };
20236
20237 /* 
20238    Class: Hypertree 
20239    
20240    A Hyperbolic Tree/Graph visualization.
20241    
20242    Inspired by:
20243  
20244    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
20245    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20246  
20247   Note:
20248  
20249   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.
20250
20251   Implements:
20252   
20253   All <Loader> methods
20254   
20255   Constructor Options:
20256   
20257   Inherits options from
20258   
20259   - <Options.Canvas>
20260   - <Options.Controller>
20261   - <Options.Node>
20262   - <Options.Edge>
20263   - <Options.Label>
20264   - <Options.Events>
20265   - <Options.Tips>
20266   - <Options.NodeStyles>
20267   - <Options.Navigation>
20268   
20269   Additionally, there are other parameters and some default values changed
20270   
20271   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*.
20272   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.
20273   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20274   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20275   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20276   
20277   Instance Properties:
20278   
20279   canvas - Access a <Canvas> instance.
20280   graph - Access a <Graph> instance.
20281   op - Access a <Hypertree.Op> instance.
20282   fx - Access a <Hypertree.Plot> instance.
20283   labels - Access a <Hypertree.Label> interface implementation.
20284
20285 */
20286
20287 $jit.Hypertree = new Class( {
20288
20289   Implements: [ Loader, Extras, Layouts.Radial ],
20290
20291   initialize: function(controller) {
20292     var $Hypertree = $jit.Hypertree;
20293
20294     var config = {
20295       radius: "auto",
20296       offset: 0,
20297       Edge: {
20298         type: 'hyperline'
20299       },
20300       duration: 1500,
20301       fps: 35
20302     };
20303     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20304         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20305
20306     var canvasConfig = this.config;
20307     if(canvasConfig.useCanvas) {
20308       this.canvas = canvasConfig.useCanvas;
20309       this.config.labelContainer = this.canvas.id + '-label';
20310     } else {
20311       if(canvasConfig.background) {
20312         canvasConfig.background = $.merge({
20313           type: 'Circles'
20314         }, canvasConfig.background);
20315       }
20316       this.canvas = new Canvas(this, canvasConfig);
20317       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20318     }
20319
20320     this.graphOptions = {
20321       'complex': false,
20322       'Node': {
20323         'selected': false,
20324         'exist': true,
20325         'drawn': true
20326       }
20327     };
20328     this.graph = new Graph(this.graphOptions, this.config.Node,
20329         this.config.Edge);
20330     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20331     this.fx = new $Hypertree.Plot(this, $Hypertree);
20332     this.op = new $Hypertree.Op(this);
20333     this.json = null;
20334     this.root = null;
20335     this.busy = false;
20336     // initialize extras
20337     this.initializeExtras();
20338   },
20339
20340   /* 
20341   
20342   createLevelDistanceFunc 
20343
20344   Returns the levelDistance function used for calculating a node distance 
20345   to its origin. This function returns a function that is computed 
20346   per level and not per node, such that all nodes with the same depth will have the 
20347   same distance to the origin. The resulting function gets the 
20348   parent node as parameter and returns a float.
20349
20350   */
20351   createLevelDistanceFunc: function() {
20352     // get max viz. length.
20353     var r = this.getRadius();
20354     // get max depth.
20355     var depth = 0, max = Math.max, config = this.config;
20356     this.graph.eachNode(function(node) {
20357       depth = max(node._depth, depth);
20358     }, "ignore");
20359     depth++;
20360     // node distance generator
20361     var genDistFunc = function(a) {
20362       return function(node) {
20363         node.scale = r;
20364         var d = node._depth + 1;
20365         var acum = 0, pow = Math.pow;
20366         while (d) {
20367           acum += pow(a, d--);
20368         }
20369         return acum - config.offset;
20370       };
20371     };
20372     // estimate better edge length.
20373     for ( var i = 0.51; i <= 1; i += 0.01) {
20374       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20375       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20376     }
20377     return genDistFunc(0.75);
20378   },
20379
20380   /* 
20381     Method: getRadius 
20382     
20383     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20384     calculates the radius by taking the smaller size of the <Canvas> widget.
20385     
20386     See also:
20387     
20388     <Canvas.getSize>
20389    
20390   */
20391   getRadius: function() {
20392     var rad = this.config.radius;
20393     if (rad !== "auto") { return rad; }
20394     var s = this.canvas.getSize();
20395     return Math.min(s.width, s.height) / 2;
20396   },
20397
20398   /* 
20399     Method: refresh 
20400     
20401     Computes positions and plots the tree.
20402
20403     Parameters:
20404
20405     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20406
20407    */
20408   refresh: function(reposition) {
20409     if (reposition) {
20410       this.reposition();
20411       this.graph.eachNode(function(node) {
20412         node.startPos.rho = node.pos.rho = node.endPos.rho;
20413         node.startPos.theta = node.pos.theta = node.endPos.theta;
20414       });
20415     } else {
20416       this.compute();
20417     }
20418     this.plot();
20419   },
20420
20421   /* 
20422    reposition 
20423    
20424    Computes nodes' positions and restores the tree to its previous position.
20425
20426    For calculating nodes' positions the root must be placed on its origin. This method does this 
20427      and then attemps to restore the hypertree to its previous position.
20428     
20429   */
20430   reposition: function() {
20431     this.compute('end');
20432     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20433     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20434         'end', "ignore");
20435     this.graph.eachNode(function(node) {
20436       if (node.ignore) {
20437         node.endPos.rho = node.pos.rho;
20438         node.endPos.theta = node.pos.theta;
20439       }
20440     });
20441   },
20442
20443   /* 
20444    Method: plot 
20445    
20446    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20447
20448   */
20449   plot: function() {
20450     this.fx.plot();
20451   },
20452
20453   /* 
20454    Method: onClick 
20455    
20456    Animates the <Hypertree> to center the node specified by *id*.
20457
20458    Parameters:
20459
20460    id - A <Graph.Node> id.
20461    opt - (optional|object) An object containing some extra properties described below
20462    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20463
20464    Example:
20465
20466    (start code js)
20467      ht.onClick('someid');
20468      //or also...
20469      ht.onClick('someid', {
20470       hideLabels: false
20471      });
20472     (end code)
20473     
20474   */
20475   onClick: function(id, opt) {
20476     var pos = this.graph.getNode(id).pos.getc(true);
20477     this.move(pos, opt);
20478   },
20479
20480   /* 
20481    Method: move 
20482
20483    Translates the tree to the given position. 
20484
20485    Parameters:
20486
20487    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20488    opt - This object has been defined in <Hypertree.onClick>
20489    
20490    Example:
20491    
20492    (start code js)
20493      ht.move({ x: 0, y: 0.7 }, {
20494        hideLabels: false
20495      });
20496    (end code)
20497
20498   */
20499   move: function(pos, opt) {
20500     var versor = $C(pos.x, pos.y);
20501     if (this.busy === false && versor.norm() < 1) {
20502       this.busy = true;
20503       var root = this.graph.getClosestNodeToPos(versor), that = this;
20504       this.graph.computeLevels(root.id, 0);
20505       this.controller.onBeforeCompute(root);
20506       opt = $.merge( {
20507         onComplete: $.empty
20508       }, opt || {});
20509       this.fx.animate($.merge( {
20510         modes: [ 'moebius' ],
20511         hideLabels: true
20512       }, opt, {
20513         onComplete: function() {
20514           that.busy = false;
20515           opt.onComplete();
20516         }
20517       }), versor);
20518     }
20519   }
20520 });
20521
20522 $jit.Hypertree.$extend = true;
20523
20524 (function(Hypertree) {
20525
20526   /* 
20527      Class: Hypertree.Op 
20528    
20529      Custom extension of <Graph.Op>.
20530
20531      Extends:
20532
20533      All <Graph.Op> methods
20534      
20535      See also:
20536      
20537      <Graph.Op>
20538
20539   */
20540   Hypertree.Op = new Class( {
20541
20542     Implements: Graph.Op
20543
20544   });
20545
20546   /* 
20547      Class: Hypertree.Plot 
20548    
20549     Custom extension of <Graph.Plot>.
20550   
20551     Extends:
20552   
20553     All <Graph.Plot> methods
20554     
20555     See also:
20556     
20557     <Graph.Plot>
20558   
20559   */
20560   Hypertree.Plot = new Class( {
20561
20562     Implements: Graph.Plot
20563
20564   });
20565
20566   /*
20567     Object: Hypertree.Label
20568
20569     Custom extension of <Graph.Label>. 
20570     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20571   
20572     Extends:
20573   
20574     All <Graph.Label> methods and subclasses.
20575   
20576     See also:
20577   
20578     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20579
20580    */
20581   Hypertree.Label = {};
20582
20583   /*
20584      Hypertree.Label.Native
20585
20586      Custom extension of <Graph.Label.Native>.
20587
20588      Extends:
20589
20590      All <Graph.Label.Native> methods
20591
20592      See also:
20593
20594      <Graph.Label.Native>
20595
20596   */
20597   Hypertree.Label.Native = new Class( {
20598     Implements: Graph.Label.Native,
20599
20600     initialize: function(viz) {
20601       this.viz = viz;
20602     },
20603
20604     renderLabel: function(canvas, node, controller) {
20605       var ctx = canvas.getCtx();
20606       var coord = node.pos.getc(true);
20607       var s = this.viz.getRadius();
20608       ctx.fillText(node.name, coord.x * s, coord.y * s);
20609     }
20610   });
20611
20612   /*
20613      Hypertree.Label.SVG
20614
20615     Custom extension of <Graph.Label.SVG>.
20616   
20617     Extends:
20618   
20619     All <Graph.Label.SVG> methods
20620   
20621     See also:
20622   
20623     <Graph.Label.SVG>
20624   
20625   */
20626   Hypertree.Label.SVG = new Class( {
20627     Implements: Graph.Label.SVG,
20628
20629     initialize: function(viz) {
20630       this.viz = viz;
20631     },
20632
20633     /* 
20634        placeLabel
20635
20636        Overrides abstract method placeLabel in <Graph.Plot>.
20637
20638        Parameters:
20639
20640        tag - A DOM label element.
20641        node - A <Graph.Node>.
20642        controller - A configuration/controller object passed to the visualization.
20643       
20644      */
20645     placeLabel: function(tag, node, controller) {
20646       var pos = node.pos.getc(true), 
20647           canvas = this.viz.canvas,
20648           ox = canvas.translateOffsetX,
20649           oy = canvas.translateOffsetY,
20650           sx = canvas.scaleOffsetX,
20651           sy = canvas.scaleOffsetY,
20652           radius = canvas.getSize(),
20653           r = this.viz.getRadius();
20654       var labelPos = {
20655         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20656         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20657       };
20658       tag.setAttribute('x', labelPos.x);
20659       tag.setAttribute('y', labelPos.y);
20660       controller.onPlaceLabel(tag, node);
20661     }
20662   });
20663
20664   /*
20665      Hypertree.Label.HTML
20666
20667      Custom extension of <Graph.Label.HTML>.
20668
20669      Extends:
20670
20671      All <Graph.Label.HTML> methods.
20672
20673      See also:
20674
20675      <Graph.Label.HTML>
20676
20677   */
20678   Hypertree.Label.HTML = new Class( {
20679     Implements: Graph.Label.HTML,
20680
20681     initialize: function(viz) {
20682       this.viz = viz;
20683     },
20684     /* 
20685        placeLabel
20686
20687        Overrides abstract method placeLabel in <Graph.Plot>.
20688
20689        Parameters:
20690
20691        tag - A DOM label element.
20692        node - A <Graph.Node>.
20693        controller - A configuration/controller object passed to the visualization.
20694       
20695      */
20696     placeLabel: function(tag, node, controller) {
20697       var pos = node.pos.getc(true), 
20698           canvas = this.viz.canvas,
20699           ox = canvas.translateOffsetX,
20700           oy = canvas.translateOffsetY,
20701           sx = canvas.scaleOffsetX,
20702           sy = canvas.scaleOffsetY,
20703           radius = canvas.getSize(),
20704           r = this.viz.getRadius();
20705       var labelPos = {
20706         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20707         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20708       };
20709       var style = tag.style;
20710       style.left = labelPos.x + 'px';
20711       style.top = labelPos.y + 'px';
20712       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20713
20714       controller.onPlaceLabel(tag, node);
20715     }
20716   });
20717
20718   /*
20719     Class: Hypertree.Plot.NodeTypes
20720
20721     This class contains a list of <Graph.Node> built-in types. 
20722     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20723
20724     You can add your custom node types, customizing your visualization to the extreme.
20725
20726     Example:
20727
20728     (start code js)
20729       Hypertree.Plot.NodeTypes.implement({
20730         'mySpecialType': {
20731           'render': function(node, canvas) {
20732             //print your custom node to canvas
20733           },
20734           //optional
20735           'contains': function(node, pos) {
20736             //return true if pos is inside the node or false otherwise
20737           }
20738         }
20739       });
20740     (end code)
20741
20742   */
20743   Hypertree.Plot.NodeTypes = new Class({
20744     'none': {
20745       'render': $.empty,
20746       'contains': $.lambda(false)
20747     },
20748     'circle': {
20749       'render': function(node, canvas) {
20750         var nconfig = this.node,
20751             dim = node.getData('dim'),
20752             p = node.pos.getc();
20753         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20754         p.$scale(node.scale);
20755         if (dim > 0.2) {
20756           this.nodeHelper.circle.render('fill', p, dim, canvas);
20757         }
20758       },
20759       'contains': function(node, pos) {
20760         var dim = node.getData('dim'),
20761             npos = node.pos.getc().$scale(node.scale);
20762         return this.nodeHelper.circle.contains(npos, pos, dim);
20763       }
20764     },
20765     'ellipse': {
20766       'render': function(node, canvas) {
20767         var pos = node.pos.getc().$scale(node.scale),
20768             width = node.getData('width'),
20769             height = node.getData('height');
20770         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20771       },
20772       'contains': function(node, pos) {
20773         var width = node.getData('width'),
20774             height = node.getData('height'),
20775             npos = node.pos.getc().$scale(node.scale);
20776         return this.nodeHelper.circle.contains(npos, pos, width, height);
20777       }
20778     },
20779     'square': {
20780       'render': function(node, canvas) {
20781         var nconfig = this.node,
20782             dim = node.getData('dim'),
20783             p = node.pos.getc();
20784         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20785         p.$scale(node.scale);
20786         if (dim > 0.2) {
20787           this.nodeHelper.square.render('fill', p, dim, canvas);
20788         }
20789       },
20790       'contains': function(node, pos) {
20791         var dim = node.getData('dim'),
20792             npos = node.pos.getc().$scale(node.scale);
20793         return this.nodeHelper.square.contains(npos, pos, dim);
20794       }
20795     },
20796     'rectangle': {
20797       'render': function(node, canvas) {
20798         var nconfig = this.node,
20799             width = node.getData('width'),
20800             height = node.getData('height'),
20801             pos = node.pos.getc();
20802         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20803         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20804         pos.$scale(node.scale);
20805         if (width > 0.2 && height > 0.2) {
20806           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20807         }
20808       },
20809       'contains': function(node, pos) {
20810         var width = node.getData('width'),
20811             height = node.getData('height'),
20812             npos = node.pos.getc().$scale(node.scale);
20813         return this.nodeHelper.square.contains(npos, pos, width, height);
20814       }
20815     },
20816     'triangle': {
20817       'render': function(node, canvas) {
20818         var nconfig = this.node,
20819             dim = node.getData('dim'),
20820             p = node.pos.getc();
20821         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20822         p.$scale(node.scale);
20823         if (dim > 0.2) {
20824           this.nodeHelper.triangle.render('fill', p, dim, canvas);
20825         }
20826       },
20827       'contains': function(node, pos) {
20828         var dim = node.getData('dim'),
20829             npos = node.pos.getc().$scale(node.scale);
20830         return this.nodeHelper.triangle.contains(npos, pos, dim);
20831       }
20832     },
20833     'star': {
20834       'render': function(node, canvas) {
20835         var nconfig = this.node,
20836             dim = node.getData('dim'),
20837             p = node.pos.getc();
20838         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20839         p.$scale(node.scale);
20840         if (dim > 0.2) {
20841           this.nodeHelper.star.render('fill', p, dim, canvas);
20842         }
20843       },
20844       'contains': function(node, pos) {
20845         var dim = node.getData('dim'),
20846             npos = node.pos.getc().$scale(node.scale);
20847         return this.nodeHelper.star.contains(npos, pos, dim);
20848       }
20849     }
20850   });
20851
20852   /*
20853    Class: Hypertree.Plot.EdgeTypes
20854
20855     This class contains a list of <Graph.Adjacence> built-in types. 
20856     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20857   
20858     You can add your custom edge types, customizing your visualization to the extreme.
20859   
20860     Example:
20861   
20862     (start code js)
20863       Hypertree.Plot.EdgeTypes.implement({
20864         'mySpecialType': {
20865           'render': function(adj, canvas) {
20866             //print your custom edge to canvas
20867           },
20868           //optional
20869           'contains': function(adj, pos) {
20870             //return true if pos is inside the arc or false otherwise
20871           }
20872         }
20873       });
20874     (end code)
20875   
20876   */
20877   Hypertree.Plot.EdgeTypes = new Class({
20878     'none': $.empty,
20879     'line': {
20880       'render': function(adj, canvas) {
20881         var from = adj.nodeFrom.pos.getc(true),
20882           to = adj.nodeTo.pos.getc(true),
20883           r = adj.nodeFrom.scale;
20884           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20885       },
20886       'contains': function(adj, pos) {
20887         var from = adj.nodeFrom.pos.getc(true),
20888             to = adj.nodeTo.pos.getc(true),
20889             r = adj.nodeFrom.scale;
20890             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20891       }
20892     },
20893     'arrow': {
20894       'render': function(adj, canvas) {
20895         var from = adj.nodeFrom.pos.getc(true),
20896             to = adj.nodeTo.pos.getc(true),
20897             r = adj.nodeFrom.scale,
20898             dim = adj.getData('dim'),
20899             direction = adj.data.$direction,
20900             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20901         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20902       },
20903       'contains': function(adj, pos) {
20904         var from = adj.nodeFrom.pos.getc(true),
20905             to = adj.nodeTo.pos.getc(true),
20906             r = adj.nodeFrom.scale;
20907         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20908       }
20909     },
20910     'hyperline': {
20911       'render': function(adj, canvas) {
20912         var from = adj.nodeFrom.pos.getc(),
20913             to = adj.nodeTo.pos.getc(),
20914             dim = this.viz.getRadius();
20915         this.edgeHelper.hyperline.render(from, to, dim, canvas);
20916       },
20917       'contains': $.lambda(false)
20918     }
20919   });
20920
20921 })($jit.Hypertree);
20922
20923
20924
20925
20926  })();