Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epsylon
GitHub Repository: epsylon/ufonet
Path: blob/master/core/js/jit.js
1208 views
1
/*
2
Copyright (c) 2011 Sencha Inc. - Author: Nicolas Garcia Belmonte (http://philogb.github.com/)
3
4
Permission is hereby granted, free of charge, to any person obtaining a copy
5
of this software and associated documentation files (the "Software"), to deal
6
in the Software without restriction, including without limitation the rights
7
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
copies of the Software, and to permit persons to whom the Software is
9
furnished to do so, subject to the following conditions:
10
11
The above copyright notice and this permission notice shall be included in
12
all copies or substantial portions of the Software.
13
14
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
THE SOFTWARE.
21
*/
22
(function () {
23
24
/*
25
File: Core.js
26
27
/*
28
Object: $jit
29
30
Defines the namespace for all library Classes and Objects.
31
This variable is the *only* global variable defined in the Toolkit.
32
There are also other interesting properties attached to this variable described below.
33
*/
34
window.$jit = function(w) {
35
w = w || window;
36
for(var k in $jit) {
37
if($jit[k].$extend) {
38
w[k] = $jit[k];
39
}
40
}
41
};
42
43
$jit.version = '2.0.1';
44
/*
45
Object: $jit.id
46
47
Works just like *document.getElementById*
48
49
Example:
50
(start code js)
51
var element = $jit.id('elementId');
52
(end code)
53
54
*/
55
56
/*
57
Object: $jit.util
58
59
Contains utility functions.
60
61
Some of the utility functions and the Class system were based in the MooTools Framework
62
<http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>.
63
MIT license <http://mootools.net/license.txt>.
64
65
These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
66
I'd suggest you to use the functions from those libraries instead of using these, since their functions
67
are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
68
69
*/
70
var $ = function(d) {
71
return document.getElementById(d);
72
};
73
74
$.empty = function() {
75
};
76
77
/*
78
Method: extend
79
80
Augment an object by appending another object's properties.
81
82
Parameters:
83
84
original - (object) The object to be extended.
85
extended - (object) An object which properties are going to be appended to the original object.
86
87
Example:
88
(start code js)
89
$jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
90
(end code)
91
*/
92
$.extend = function(original, extended) {
93
for ( var key in (extended || {}))
94
original[key] = extended[key];
95
return original;
96
};
97
98
$.lambda = function(value) {
99
return (typeof value == 'function') ? value : function() {
100
return value;
101
};
102
};
103
104
$.time = Date.now || function() {
105
return +new Date;
106
};
107
108
/*
109
Method: splat
110
111
Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
112
113
Parameters:
114
115
obj - (mixed) The object to be wrapped in an array.
116
117
Example:
118
(start code js)
119
$jit.util.splat(3); //[3]
120
$jit.util.splat([3]); //[3]
121
(end code)
122
*/
123
$.splat = function(obj) {
124
var type = $.type(obj);
125
return type ? ((type != 'array') ? [ obj ] : obj) : [];
126
};
127
128
$.type = function(elem) {
129
var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
130
if(type != 'object') return type;
131
if(elem && elem.$$family) return elem.$$family;
132
return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
133
};
134
$.type.s = Object.prototype.toString;
135
136
/*
137
Method: each
138
139
Iterates through an iterable applying *f*.
140
141
Parameters:
142
143
iterable - (array) The original array.
144
fn - (function) The function to apply to the array elements.
145
146
Example:
147
(start code js)
148
$jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
149
(end code)
150
*/
151
$.each = function(iterable, fn) {
152
var type = $.type(iterable);
153
if (type == 'object') {
154
for ( var key in iterable)
155
fn(iterable[key], key);
156
} else {
157
for ( var i = 0, l = iterable.length; i < l; i++)
158
fn(iterable[i], i);
159
}
160
};
161
162
$.indexOf = function(array, item) {
163
if(Array.indexOf) return array.indexOf(item);
164
for(var i=0,l=array.length; i<l; i++) {
165
if(array[i] === item) return i;
166
}
167
return -1;
168
};
169
170
/*
171
Method: map
172
173
Maps or collects an array by applying *f*.
174
175
Parameters:
176
177
array - (array) The original array.
178
f - (function) The function to apply to the array elements.
179
180
Example:
181
(start code js)
182
$jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
183
(end code)
184
*/
185
$.map = function(array, f) {
186
var ans = [];
187
$.each(array, function(elem, i) {
188
ans.push(f(elem, i));
189
});
190
return ans;
191
};
192
193
/*
194
Method: reduce
195
196
Iteratively applies the binary function *f* storing the result in an accumulator.
197
198
Parameters:
199
200
array - (array) The original array.
201
f - (function) The function to apply to the array elements.
202
opt - (optional|mixed) The starting value for the acumulator.
203
204
Example:
205
(start code js)
206
$jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
207
(end code)
208
*/
209
$.reduce = function(array, f, opt) {
210
var l = array.length;
211
if(l==0) return opt;
212
var acum = arguments.length == 3? opt : array[--l];
213
while(l--) {
214
acum = f(acum, array[l]);
215
}
216
return acum;
217
};
218
219
/*
220
Method: merge
221
222
Merges n-objects and their sub-objects creating a new, fresh object.
223
224
Parameters:
225
226
An arbitrary number of objects.
227
228
Example:
229
(start code js)
230
$jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
231
(end code)
232
*/
233
$.merge = function() {
234
var mix = {};
235
for ( var i = 0, l = arguments.length; i < l; i++) {
236
var object = arguments[i];
237
if ($.type(object) != 'object')
238
continue;
239
for ( var key in object) {
240
var op = object[key], mp = mix[key];
241
mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
242
.merge(mp, op) : $.unlink(op);
243
}
244
}
245
return mix;
246
};
247
248
$.unlink = function(object) {
249
var unlinked;
250
switch ($.type(object)) {
251
case 'object':
252
unlinked = {};
253
for ( var p in object)
254
unlinked[p] = $.unlink(object[p]);
255
break;
256
case 'array':
257
unlinked = [];
258
for ( var i = 0, l = object.length; i < l; i++)
259
unlinked[i] = $.unlink(object[i]);
260
break;
261
default:
262
return object;
263
}
264
return unlinked;
265
};
266
267
$.zip = function() {
268
if(arguments.length === 0) return [];
269
for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
270
for(var i=0, row=[]; i<l; i++) {
271
row.push(arguments[i][j]);
272
}
273
ans.push(row);
274
}
275
return ans;
276
};
277
278
/*
279
Method: rgbToHex
280
281
Converts an RGB array into a Hex string.
282
283
Parameters:
284
285
srcArray - (array) An array with R, G and B values
286
287
Example:
288
(start code js)
289
$jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
290
(end code)
291
*/
292
$.rgbToHex = function(srcArray, array) {
293
if (srcArray.length < 3)
294
return null;
295
if (srcArray.length == 4 && srcArray[3] == 0 && !array)
296
return 'transparent';
297
var hex = [];
298
for ( var i = 0; i < 3; i++) {
299
var bit = (srcArray[i] - 0).toString(16);
300
hex.push(bit.length == 1 ? '0' + bit : bit);
301
}
302
return array ? hex : '#' + hex.join('');
303
};
304
305
/*
306
Method: hexToRgb
307
308
Converts an Hex color string into an RGB array.
309
310
Parameters:
311
312
hex - (string) A color hex string.
313
314
Example:
315
(start code js)
316
$jit.util.hexToRgb('#fff'); //[255, 255, 255]
317
(end code)
318
*/
319
$.hexToRgb = function(hex) {
320
if (hex.length != 7) {
321
hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
322
hex.shift();
323
if (hex.length != 3)
324
return null;
325
var rgb = [];
326
for ( var i = 0; i < 3; i++) {
327
var value = hex[i];
328
if (value.length == 1)
329
value += value;
330
rgb.push(parseInt(value, 16));
331
}
332
return rgb;
333
} else {
334
hex = parseInt(hex.slice(1), 16);
335
return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
336
}
337
};
338
339
$.destroy = function(elem) {
340
$.clean(elem);
341
if (elem.parentNode)
342
elem.parentNode.removeChild(elem);
343
if (elem.clearAttributes)
344
elem.clearAttributes();
345
};
346
347
$.clean = function(elem) {
348
for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
349
$.destroy(ch[i]);
350
}
351
};
352
353
/*
354
Method: addEvent
355
356
Cross-browser add event listener.
357
358
Parameters:
359
360
obj - (obj) The Element to attach the listener to.
361
type - (string) The listener type. For example 'click', or 'mousemove'.
362
fn - (function) The callback function to be used when the event is fired.
363
364
Example:
365
(start code js)
366
$jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
367
(end code)
368
*/
369
$.addEvent = function(obj, type, fn) {
370
if (obj.addEventListener)
371
obj.addEventListener(type, fn, false);
372
else
373
obj.attachEvent('on' + type, fn);
374
};
375
376
$.addEvents = function(obj, typeObj) {
377
for(var type in typeObj) {
378
$.addEvent(obj, type, typeObj[type]);
379
}
380
};
381
382
$.hasClass = function(obj, klass) {
383
return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
384
};
385
386
$.addClass = function(obj, klass) {
387
if (!$.hasClass(obj, klass))
388
obj.className = (obj.className + " " + klass);
389
};
390
391
$.removeClass = function(obj, klass) {
392
obj.className = obj.className.replace(new RegExp(
393
'(^|\\s)' + klass + '(?:\\s|$)'), '$1');
394
};
395
396
$.getPos = function(elem) {
397
var offset = getOffsets(elem);
398
var scroll = getScrolls(elem);
399
return {
400
x: offset.x - scroll.x,
401
y: offset.y - scroll.y
402
};
403
404
function getOffsets(elem) {
405
var position = {
406
x: 0,
407
y: 0
408
};
409
while (elem && !isBody(elem)) {
410
position.x += elem.offsetLeft;
411
position.y += elem.offsetTop;
412
elem = elem.offsetParent;
413
}
414
return position;
415
}
416
417
function getScrolls(elem) {
418
var position = {
419
x: 0,
420
y: 0
421
};
422
while (elem && !isBody(elem)) {
423
position.x += elem.scrollLeft;
424
position.y += elem.scrollTop;
425
elem = elem.parentNode;
426
}
427
return position;
428
}
429
430
function isBody(element) {
431
return (/^(?:body|html)$/i).test(element.tagName);
432
}
433
};
434
435
$.event = {
436
get: function(e, win) {
437
win = win || window;
438
return e || win.event;
439
},
440
getWheel: function(e) {
441
return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
442
},
443
isRightClick: function(e) {
444
return (e.which == 3 || e.button == 2);
445
},
446
getPos: function(e, win) {
447
// get mouse position
448
win = win || window;
449
e = e || win.event;
450
var doc = win.document;
451
doc = doc.documentElement || doc.body;
452
//make touch event handling better
453
if(e.touches && e.touches.length) {
454
e = e.touches[0];
455
}
456
var page = {
457
x: e.pageX || (e.clientX + doc.scrollLeft),
458
y: e.pageY || (e.clientY + doc.scrollTop)
459
};
460
return page;
461
},
462
stop: function(e) {
463
if (e.stopPropagation) e.stopPropagation();
464
e.cancelBubble = true;
465
if (e.preventDefault) e.preventDefault();
466
else e.returnValue = false;
467
}
468
};
469
470
$jit.util = $jit.id = $;
471
472
var Class = function(properties) {
473
properties = properties || {};
474
var klass = function() {
475
for ( var key in this) {
476
if (typeof this[key] != 'function')
477
this[key] = $.unlink(this[key]);
478
}
479
this.constructor = klass;
480
if (Class.prototyping)
481
return this;
482
var instance = this.initialize ? this.initialize.apply(this, arguments)
483
: this;
484
//typize
485
this.$$family = 'class';
486
return instance;
487
};
488
489
for ( var mutator in Class.Mutators) {
490
if (!properties[mutator])
491
continue;
492
properties = Class.Mutators[mutator](properties, properties[mutator]);
493
delete properties[mutator];
494
}
495
496
$.extend(klass, this);
497
klass.constructor = Class;
498
klass.prototype = properties;
499
return klass;
500
};
501
502
Class.Mutators = {
503
504
Implements: function(self, klasses) {
505
$.each($.splat(klasses), function(klass) {
506
Class.prototyping = klass;
507
var instance = (typeof klass == 'function') ? new klass : klass;
508
for ( var prop in instance) {
509
if (!(prop in self)) {
510
self[prop] = instance[prop];
511
}
512
}
513
delete Class.prototyping;
514
});
515
return self;
516
}
517
518
};
519
520
$.extend(Class, {
521
522
inherit: function(object, properties) {
523
for ( var key in properties) {
524
var override = properties[key];
525
var previous = object[key];
526
var type = $.type(override);
527
if (previous && type == 'function') {
528
if (override != previous) {
529
Class.override(object, key, override);
530
}
531
} else if (type == 'object') {
532
object[key] = $.merge(previous, override);
533
} else {
534
object[key] = override;
535
}
536
}
537
return object;
538
},
539
540
override: function(object, name, method) {
541
var parent = Class.prototyping;
542
if (parent && object[name] != parent[name])
543
parent = null;
544
var override = function() {
545
var previous = this.parent;
546
this.parent = parent ? parent[name] : object[name];
547
var value = method.apply(this, arguments);
548
this.parent = previous;
549
return value;
550
};
551
object[name] = override;
552
}
553
554
});
555
556
Class.prototype.implement = function() {
557
var proto = this.prototype;
558
$.each(Array.prototype.slice.call(arguments || []), function(properties) {
559
Class.inherit(proto, properties);
560
});
561
return this;
562
};
563
564
$jit.Class = Class;
565
566
/*
567
Object: $jit.json
568
569
Provides JSON utility functions.
570
571
Most of these functions are JSON-tree traversal and manipulation functions.
572
*/
573
$jit.json = {
574
/*
575
Method: prune
576
577
Clears all tree nodes having depth greater than maxLevel.
578
579
Parameters:
580
581
tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
582
maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
583
584
*/
585
prune: function(tree, maxLevel) {
586
this.each(tree, function(elem, i) {
587
if (i == maxLevel && elem.children) {
588
delete elem.children;
589
elem.children = [];
590
}
591
});
592
},
593
/*
594
Method: getParent
595
596
Returns the parent node of the node having _id_ as id.
597
598
Parameters:
599
600
tree - (object) A JSON tree object. See also <Loader.loadJSON>.
601
id - (string) The _id_ of the child node whose parent will be returned.
602
603
Returns:
604
605
A tree JSON node if any, or false otherwise.
606
607
*/
608
getParent: function(tree, id) {
609
if (tree.id == id)
610
return false;
611
var ch = tree.children;
612
if (ch && ch.length > 0) {
613
for ( var i = 0; i < ch.length; i++) {
614
if (ch[i].id == id)
615
return tree;
616
else {
617
var ans = this.getParent(ch[i], id);
618
if (ans)
619
return ans;
620
}
621
}
622
}
623
return false;
624
},
625
/*
626
Method: getSubtree
627
628
Returns the subtree that matches the given id.
629
630
Parameters:
631
632
tree - (object) A JSON tree object. See also <Loader.loadJSON>.
633
id - (string) A node *unique* identifier.
634
635
Returns:
636
637
A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
638
639
*/
640
getSubtree: function(tree, id) {
641
if (tree.id == id)
642
return tree;
643
for ( var i = 0, ch = tree.children; ch && i < ch.length; i++) {
644
var t = this.getSubtree(ch[i], id);
645
if (t != null)
646
return t;
647
}
648
return null;
649
},
650
/*
651
Method: eachLevel
652
653
Iterates on tree nodes with relative depth less or equal than a specified level.
654
655
Parameters:
656
657
tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
658
initLevel - (number) An integer specifying the initial relative level. Usually zero.
659
toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
660
action - (function) A function that receives a node and an integer specifying the actual level of the node.
661
662
Example:
663
(start code js)
664
$jit.json.eachLevel(tree, 0, 3, function(node, depth) {
665
alert(node.name + ' ' + depth);
666
});
667
(end code)
668
*/
669
eachLevel: function(tree, initLevel, toLevel, action) {
670
if (initLevel <= toLevel) {
671
action(tree, initLevel);
672
if(!tree.children) return;
673
for ( var i = 0, ch = tree.children; i < ch.length; i++) {
674
this.eachLevel(ch[i], initLevel + 1, toLevel, action);
675
}
676
}
677
},
678
/*
679
Method: each
680
681
A JSON tree iterator.
682
683
Parameters:
684
685
tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
686
action - (function) A function that receives a node.
687
688
Example:
689
(start code js)
690
$jit.json.each(tree, function(node) {
691
alert(node.name);
692
});
693
(end code)
694
695
*/
696
each: function(tree, action) {
697
this.eachLevel(tree, 0, Number.MAX_VALUE, action);
698
}
699
};
700
701
702
/*
703
An object containing multiple type of transformations.
704
*/
705
706
$jit.Trans = {
707
$extend: true,
708
709
linear: function(p){
710
return p;
711
}
712
};
713
714
var Trans = $jit.Trans;
715
716
(function(){
717
718
var makeTrans = function(transition, params){
719
params = $.splat(params);
720
return $.extend(transition, {
721
easeIn: function(pos){
722
return transition(pos, params);
723
},
724
easeOut: function(pos){
725
return 1 - transition(1 - pos, params);
726
},
727
easeInOut: function(pos){
728
return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
729
2 * (1 - pos), params)) / 2;
730
}
731
});
732
};
733
734
var transitions = {
735
736
Pow: function(p, x){
737
return Math.pow(p, x[0] || 6);
738
},
739
740
Expo: function(p){
741
return Math.pow(2, 8 * (p - 1));
742
},
743
744
Circ: function(p){
745
return 1 - Math.sin(Math.acos(p));
746
},
747
748
Sine: function(p){
749
return 1 - Math.sin((1 - p) * Math.PI / 2);
750
},
751
752
Back: function(p, x){
753
x = x[0] || 1.618;
754
return Math.pow(p, 2) * ((x + 1) * p - x);
755
},
756
757
Bounce: function(p){
758
var value;
759
for ( var a = 0, b = 1; 1; a += b, b /= 2) {
760
if (p >= (7 - 4 * a) / 11) {
761
value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
762
break;
763
}
764
}
765
return value;
766
},
767
768
Elastic: function(p, x){
769
return Math.pow(2, 10 * --p)
770
* Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
771
}
772
773
};
774
775
$.each(transitions, function(val, key){
776
Trans[key] = makeTrans(val);
777
});
778
779
$.each( [
780
'Quad', 'Cubic', 'Quart', 'Quint'
781
], function(elem, i){
782
Trans[elem] = makeTrans(function(p){
783
return Math.pow(p, [
784
i + 2
785
]);
786
});
787
});
788
789
})();
790
791
/*
792
A Class that can perform animations for generic objects.
793
794
If you are looking for animation transitions please take a look at the <Trans> object.
795
796
Used by:
797
798
<Graph.Plot>
799
800
Based on:
801
802
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>.
803
804
*/
805
806
var Animation = new Class( {
807
808
initialize: function(options){
809
this.setOptions(options);
810
},
811
812
setOptions: function(options){
813
var opt = {
814
duration: 2500,
815
fps: 40,
816
transition: Trans.Quart.easeInOut,
817
compute: $.empty,
818
complete: $.empty,
819
link: 'ignore'
820
};
821
this.opt = $.merge(opt, options || {});
822
return this;
823
},
824
825
step: function(){
826
var time = $.time(), opt = this.opt;
827
if (time < this.time + opt.duration) {
828
var delta = opt.transition((time - this.time) / opt.duration);
829
opt.compute(delta);
830
} else {
831
this.timer = clearInterval(this.timer);
832
opt.compute(1);
833
opt.complete();
834
}
835
},
836
837
start: function(){
838
if (!this.check())
839
return this;
840
this.time = 0;
841
this.startTimer();
842
return this;
843
},
844
845
startTimer: function(){
846
var that = this, fps = this.opt.fps;
847
if (this.timer)
848
return false;
849
this.time = $.time() - this.time;
850
this.timer = setInterval((function(){
851
that.step();
852
}), Math.round(1000 / fps));
853
return true;
854
},
855
856
pause: function(){
857
this.stopTimer();
858
return this;
859
},
860
861
resume: function(){
862
this.startTimer();
863
return this;
864
},
865
866
stopTimer: function(){
867
if (!this.timer)
868
return false;
869
this.time = $.time() - this.time;
870
this.timer = clearInterval(this.timer);
871
return true;
872
},
873
874
check: function(){
875
if (!this.timer)
876
return true;
877
if (this.opt.link == 'cancel') {
878
this.stopTimer();
879
return true;
880
}
881
return false;
882
}
883
});
884
885
886
var Options = function() {
887
var args = arguments;
888
for(var i=0, l=args.length, ans={}; i<l; i++) {
889
var opt = Options[args[i]];
890
if(opt.$extend) {
891
$.extend(ans, opt);
892
} else {
893
ans[args[i]] = opt;
894
}
895
}
896
return ans;
897
};
898
899
/*
900
* File: Options.AreaChart.js
901
*
902
*/
903
904
/*
905
Object: Options.AreaChart
906
907
<AreaChart> options.
908
Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
909
910
Syntax:
911
912
(start code js)
913
914
Options.AreaChart = {
915
animate: true,
916
labelOffset: 3,
917
type: 'stacked',
918
selectOnHover: true,
919
showAggregates: true,
920
showLabels: true,
921
filterOnClick: false,
922
restoreOnRightClick: false
923
};
924
925
(end code)
926
927
Example:
928
929
(start code js)
930
931
var areaChart = new $jit.AreaChart({
932
animate: true,
933
type: 'stacked:gradient',
934
selectOnHover: true,
935
filterOnClick: true,
936
restoreOnRightClick: true
937
});
938
939
(end code)
940
941
Parameters:
942
943
animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
944
labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
945
type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
946
selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
947
showAggregates - (boolean, function) Default's *true*. Display the values of the stacks. Can also be a function that returns *true* or *false* to display or filter some values. That same function can also return a string with the formatted value.
948
showLabels - (boolean, function) Default's *true*. Display the name of the slots. Can also be a function that returns *true* or *false* to display or not each label.
949
filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
950
restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
951
952
*/
953
954
Options.AreaChart = {
955
$extend: true,
956
957
animate: true,
958
labelOffset: 3, // label offset
959
type: 'stacked', // gradient
960
Tips: {
961
enable: false,
962
onShow: $.empty,
963
onHide: $.empty
964
},
965
Events: {
966
enable: false,
967
onClick: $.empty
968
},
969
selectOnHover: true,
970
showAggregates: true,
971
showLabels: true,
972
filterOnClick: false,
973
restoreOnRightClick: false
974
};
975
976
/*
977
* File: Options.Margin.js
978
*
979
*/
980
981
/*
982
Object: Options.Margin
983
984
Canvas drawing margins.
985
986
Syntax:
987
988
(start code js)
989
990
Options.Margin = {
991
top: 0,
992
left: 0,
993
right: 0,
994
bottom: 0
995
};
996
997
(end code)
998
999
Example:
1000
1001
(start code js)
1002
1003
var viz = new $jit.Viz({
1004
Margin: {
1005
right: 10,
1006
bottom: 20
1007
}
1008
});
1009
1010
(end code)
1011
1012
Parameters:
1013
1014
top - (number) Default's *0*. Top margin.
1015
left - (number) Default's *0*. Left margin.
1016
right - (number) Default's *0*. Right margin.
1017
bottom - (number) Default's *0*. Bottom margin.
1018
1019
*/
1020
1021
Options.Margin = {
1022
$extend: false,
1023
1024
top: 0,
1025
left: 0,
1026
right: 0,
1027
bottom: 0
1028
};
1029
1030
/*
1031
* File: Options.Canvas.js
1032
*
1033
*/
1034
1035
/*
1036
Object: Options.Canvas
1037
1038
These are Canvas general options, like where to append it in the DOM, its dimensions, background,
1039
and other more advanced options.
1040
1041
Syntax:
1042
1043
(start code js)
1044
1045
Options.Canvas = {
1046
injectInto: 'id',
1047
type: '2D', //'3D'
1048
width: false,
1049
height: false,
1050
useCanvas: false,
1051
withLabels: true,
1052
background: false
1053
};
1054
(end code)
1055
1056
Example:
1057
1058
(start code js)
1059
var viz = new $jit.Viz({
1060
injectInto: 'someContainerId',
1061
width: 500,
1062
height: 700
1063
});
1064
(end code)
1065
1066
Parameters:
1067
1068
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.
1069
type - (string) Context type. Default's 2D but can be 3D for webGL enabled browsers.
1070
width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1071
height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1072
useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1073
withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1074
background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1075
*/
1076
1077
Options.Canvas = {
1078
$extend: true,
1079
1080
injectInto: 'id',
1081
type: '2D',
1082
width: false,
1083
height: false,
1084
useCanvas: false,
1085
withLabels: true,
1086
background: false,
1087
1088
Scene: {
1089
Lighting: {
1090
enable: false,
1091
ambient: [1, 1, 1],
1092
directional: {
1093
direction: { x: -100, y: -100, z: -100 },
1094
color: [0.5, 0.3, 0.1]
1095
}
1096
}
1097
}
1098
};
1099
1100
/*
1101
* File: Options.Tree.js
1102
*
1103
*/
1104
1105
/*
1106
Object: Options.Tree
1107
1108
Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1109
1110
Syntax:
1111
1112
(start code js)
1113
Options.Tree = {
1114
orientation: "left",
1115
subtreeOffset: 8,
1116
siblingOffset: 5,
1117
indent:10,
1118
multitree: false,
1119
align:"center"
1120
};
1121
(end code)
1122
1123
Example:
1124
1125
(start code js)
1126
var st = new $jit.ST({
1127
orientation: 'left',
1128
subtreeOffset: 1,
1129
siblingOFfset: 5,
1130
multitree: true
1131
});
1132
(end code)
1133
1134
Parameters:
1135
1136
subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1137
siblingOffset - (number) Default's 5. Separation offset between siblings.
1138
orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1139
align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1140
indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1141
multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1142
1143
*/
1144
Options.Tree = {
1145
$extend: true,
1146
1147
orientation: "left",
1148
subtreeOffset: 8,
1149
siblingOffset: 5,
1150
indent:10,
1151
multitree: false,
1152
align:"center"
1153
};
1154
1155
1156
/*
1157
* File: Options.Node.js
1158
*
1159
*/
1160
1161
/*
1162
Object: Options.Node
1163
1164
Provides Node rendering options for Tree and Graph based visualizations.
1165
1166
Syntax:
1167
1168
(start code js)
1169
Options.Node = {
1170
overridable: false,
1171
type: 'circle',
1172
color: '#ccb',
1173
alpha: 1,
1174
dim: 3,
1175
height: 20,
1176
width: 90,
1177
autoHeight: false,
1178
autoWidth: false,
1179
lineWidth: 1,
1180
transform: true,
1181
align: "center",
1182
angularWidth:1,
1183
span:1,
1184
CanvasStyles: {}
1185
};
1186
(end code)
1187
1188
Example:
1189
1190
(start code js)
1191
var viz = new $jit.Viz({
1192
Node: {
1193
overridable: true,
1194
width: 30,
1195
autoHeight: true,
1196
type: 'rectangle'
1197
}
1198
});
1199
(end code)
1200
1201
Parameters:
1202
1203
overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1204
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.
1205
color - (string) Default's *#ccb*. Node color.
1206
alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1207
dim - (number) Default's *3*. An extra parameter used by 'circle', 'square', 'triangle' and 'star' node types. Depending on each shape, this parameter can set the radius of a circle, half the length of the side of a square, half the base and half the height of a triangle or the length of a side of a star (concave decagon).
1208
height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1209
width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1210
autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1211
autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1212
lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1213
transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1214
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.
1215
angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1216
span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1217
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.
1218
1219
*/
1220
Options.Node = {
1221
$extend: false,
1222
overridable: false,
1223
type: 'circle',
1224
color: '#fff000', // node Color
1225
alpha: 1,
1226
dim: 3,
1227
height: 20,
1228
width: 90,
1229
autoHeight: false,
1230
autoWidth: false,
1231
lineWidth: 1,
1232
transform: true,
1233
align: "center",
1234
angularWidth:1,
1235
span:1,
1236
//Raw canvas styles to be
1237
//applied to the context instance
1238
//before plotting a node
1239
CanvasStyles: {}
1240
};
1241
1242
1243
/*
1244
* File: Options.Edge.js
1245
*
1246
*/
1247
1248
/*
1249
Object: Options.Edge
1250
1251
Provides Edge rendering options for Tree and Graph based visualizations.
1252
1253
Syntax:
1254
1255
(start code js)
1256
Options.Edge = {
1257
overridable: false,
1258
type: 'line',
1259
color: '#ccb',
1260
lineWidth: 1,
1261
dim:15,
1262
alpha: 1,
1263
CanvasStyles: {}
1264
};
1265
(end code)
1266
1267
Example:
1268
1269
(start code js)
1270
var viz = new $jit.Viz({
1271
Edge: {
1272
overridable: true,
1273
type: 'line',
1274
color: '#fff',
1275
CanvasStyles: {
1276
shadowColor: '#ccc',
1277
shadowBlur: 10
1278
}
1279
}
1280
});
1281
(end code)
1282
1283
Parameters:
1284
1285
overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1286
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.
1287
color - (string) Default's '#ccb'. Edge color.
1288
lineWidth - (number) Default's *1*. Line/Edge width.
1289
alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1290
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.
1291
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*.
1292
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.
1293
1294
See also:
1295
1296
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.
1297
*/
1298
Options.Edge = {
1299
$extend: false,
1300
1301
overridable: false,
1302
type: 'line',
1303
color: '#ccb',
1304
lineWidth: 1,
1305
dim:15,
1306
alpha: 1,
1307
epsilon: 7,
1308
1309
//Raw canvas styles to be
1310
//applied to the context instance
1311
//before plotting an edge
1312
CanvasStyles: {}
1313
};
1314
1315
1316
/*
1317
* File: Options.Fx.js
1318
*
1319
*/
1320
1321
/*
1322
Object: Options.Fx
1323
1324
Provides animation options like duration of the animations, frames per second and animation transitions.
1325
1326
Syntax:
1327
1328
(start code js)
1329
Options.Fx = {
1330
fps:40,
1331
duration: 2500,
1332
transition: $jit.Trans.Quart.easeInOut,
1333
clearCanvas: true
1334
};
1335
(end code)
1336
1337
Example:
1338
1339
(start code js)
1340
var viz = new $jit.Viz({
1341
duration: 1000,
1342
fps: 35,
1343
transition: $jit.Trans.linear
1344
});
1345
(end code)
1346
1347
Parameters:
1348
1349
clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1350
duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1351
fps - (number) Default's *40*. Frames per second.
1352
transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1353
1354
Object: $jit.Trans
1355
1356
This object is used for specifying different animation transitions in all visualizations.
1357
1358
There are many different type of animation transitions.
1359
1360
linear:
1361
1362
Displays a linear transition
1363
1364
>Trans.linear
1365
1366
(see Linear.png)
1367
1368
Quad:
1369
1370
Displays a Quadratic transition.
1371
1372
>Trans.Quad.easeIn
1373
>Trans.Quad.easeOut
1374
>Trans.Quad.easeInOut
1375
1376
(see Quad.png)
1377
1378
Cubic:
1379
1380
Displays a Cubic transition.
1381
1382
>Trans.Cubic.easeIn
1383
>Trans.Cubic.easeOut
1384
>Trans.Cubic.easeInOut
1385
1386
(see Cubic.png)
1387
1388
Quart:
1389
1390
Displays a Quartetic transition.
1391
1392
>Trans.Quart.easeIn
1393
>Trans.Quart.easeOut
1394
>Trans.Quart.easeInOut
1395
1396
(see Quart.png)
1397
1398
Quint:
1399
1400
Displays a Quintic transition.
1401
1402
>Trans.Quint.easeIn
1403
>Trans.Quint.easeOut
1404
>Trans.Quint.easeInOut
1405
1406
(see Quint.png)
1407
1408
Expo:
1409
1410
Displays an Exponential transition.
1411
1412
>Trans.Expo.easeIn
1413
>Trans.Expo.easeOut
1414
>Trans.Expo.easeInOut
1415
1416
(see Expo.png)
1417
1418
Circ:
1419
1420
Displays a Circular transition.
1421
1422
>Trans.Circ.easeIn
1423
>Trans.Circ.easeOut
1424
>Trans.Circ.easeInOut
1425
1426
(see Circ.png)
1427
1428
Sine:
1429
1430
Displays a Sineousidal transition.
1431
1432
>Trans.Sine.easeIn
1433
>Trans.Sine.easeOut
1434
>Trans.Sine.easeInOut
1435
1436
(see Sine.png)
1437
1438
Back:
1439
1440
>Trans.Back.easeIn
1441
>Trans.Back.easeOut
1442
>Trans.Back.easeInOut
1443
1444
(see Back.png)
1445
1446
Bounce:
1447
1448
Bouncy transition.
1449
1450
>Trans.Bounce.easeIn
1451
>Trans.Bounce.easeOut
1452
>Trans.Bounce.easeInOut
1453
1454
(see Bounce.png)
1455
1456
Elastic:
1457
1458
Elastic curve.
1459
1460
>Trans.Elastic.easeIn
1461
>Trans.Elastic.easeOut
1462
>Trans.Elastic.easeInOut
1463
1464
(see Elastic.png)
1465
1466
Based on:
1467
1468
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>.
1469
1470
1471
*/
1472
Options.Fx = {
1473
$extend: true,
1474
1475
fps:40,
1476
duration: 2500,
1477
transition: $jit.Trans.Quart.easeInOut,
1478
clearCanvas: true
1479
};
1480
1481
/*
1482
* File: Options.Label.js
1483
*
1484
*/
1485
/*
1486
Object: Options.Label
1487
1488
Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
1489
1490
Syntax:
1491
1492
(start code js)
1493
Options.Label = {
1494
overridable: false,
1495
type: 'HTML', //'SVG', 'Native'
1496
style: ' ',
1497
size: 10,
1498
family: 'sans-serif',
1499
textAlign: 'center',
1500
textBaseline: 'alphabetic',
1501
color: '#fff'
1502
};
1503
(end code)
1504
1505
Example:
1506
1507
(start code js)
1508
var viz = new $jit.Viz({
1509
Label: {
1510
type: 'Native',
1511
size: 11,
1512
color: '#ccc'
1513
}
1514
});
1515
(end code)
1516
1517
Parameters:
1518
1519
overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1520
type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1521
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.
1522
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.
1523
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.
1524
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.
1525
*/
1526
Options.Label = {
1527
$extend: false,
1528
overridable: false,
1529
type: 'HTML', //'SVG', 'Native'
1530
style: 'bold',
1531
size: 12,
1532
family: ' Courier, Courier New, monospace',
1533
textAlign: 'center',
1534
textBaseline: 'alphabetic',
1535
color: 'white'
1536
};
1537
1538
1539
/*
1540
* File: Options.Tips.js
1541
*
1542
*/
1543
1544
/*
1545
Object: Options.Tips
1546
1547
Tips options
1548
1549
Syntax:
1550
1551
(start code js)
1552
Options.Tips = {
1553
enable: false,
1554
type: 'auto',
1555
offsetX: 20,
1556
offsetY: 20,
1557
onShow: $.empty,
1558
onHide: $.empty
1559
};
1560
(end code)
1561
1562
Example:
1563
1564
(start code js)
1565
var viz = new $jit.Viz({
1566
Tips: {
1567
enable: true,
1568
type: 'Native',
1569
offsetX: 10,
1570
offsetY: 10,
1571
onShow: function(tip, node) {
1572
tip.innerHTML = node.name;
1573
}
1574
}
1575
});
1576
(end code)
1577
1578
Parameters:
1579
1580
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.
1581
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.
1582
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.
1583
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.
1584
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.
1585
onHide() - This callack is used when hiding a tooltip.
1586
1587
*/
1588
Options.Tips = {
1589
$extend: false,
1590
1591
enable: false,
1592
type: 'auto',
1593
offsetX: 20,
1594
offsetY: 20,
1595
force: false,
1596
onShow: $.empty,
1597
onHide: $.empty
1598
};
1599
1600
1601
/*
1602
* File: Options.NodeStyles.js
1603
*
1604
*/
1605
1606
/*
1607
Object: Options.NodeStyles
1608
1609
Apply different styles when a node is hovered or selected.
1610
1611
Syntax:
1612
1613
(start code js)
1614
Options.NodeStyles = {
1615
enable: false,
1616
type: 'auto',
1617
stylesHover: false,
1618
stylesClick: false
1619
};
1620
(end code)
1621
1622
Example:
1623
1624
(start code js)
1625
var viz = new $jit.Viz({
1626
NodeStyles: {
1627
enable: true,
1628
type: 'Native',
1629
stylesHover: {
1630
dim: 30,
1631
color: '#fcc'
1632
},
1633
duration: 600
1634
}
1635
});
1636
(end code)
1637
1638
Parameters:
1639
1640
enable - (boolean) Default's *false*. Whether to enable this option.
1641
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>.
1642
stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1643
stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1644
*/
1645
1646
Options.NodeStyles = {
1647
$extend: false,
1648
1649
enable: false,
1650
type: 'auto',
1651
stylesHover: false,
1652
stylesClick: false
1653
};
1654
1655
1656
/*
1657
* File: Options.Events.js
1658
*
1659
*/
1660
1661
/*
1662
Object: Options.Events
1663
1664
Configuration for adding mouse/touch event handlers to Nodes.
1665
1666
Syntax:
1667
1668
(start code js)
1669
Options.Events = {
1670
enable: false,
1671
enableForEdges: false,
1672
type: 'auto',
1673
onClick: $.empty,
1674
onRightClick: $.empty,
1675
onMouseMove: $.empty,
1676
onMouseEnter: $.empty,
1677
onMouseLeave: $.empty,
1678
onDragStart: $.empty,
1679
onDragMove: $.empty,
1680
onDragCancel: $.empty,
1681
onDragEnd: $.empty,
1682
onTouchStart: $.empty,
1683
onTouchMove: $.empty,
1684
onTouchEnd: $.empty,
1685
onTouchCancel: $.empty,
1686
onMouseWheel: $.empty
1687
};
1688
(end code)
1689
1690
Example:
1691
1692
(start code js)
1693
var viz = new $jit.Viz({
1694
Events: {
1695
enable: true,
1696
onClick: function(node, eventInfo, e) {
1697
viz.doSomething();
1698
},
1699
onMouseEnter: function(node, eventInfo, e) {
1700
viz.canvas.getElement().style.cursor = 'pointer';
1701
},
1702
onMouseLeave: function(node, eventInfo, e) {
1703
viz.canvas.getElement().style.cursor = '';
1704
}
1705
}
1706
});
1707
(end code)
1708
1709
Parameters:
1710
1711
enable - (boolean) Default's *false*. Whether to enable the Event system.
1712
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*.
1713
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.
1714
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.
1715
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.
1716
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.
1717
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.
1718
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.
1719
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.
1720
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.
1721
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.
1722
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.
1723
onTouchStart(node, eventInfo, e) - Behaves just like onDragStart.
1724
onTouchMove(node, eventInfo, e) - Behaves just like onDragMove.
1725
onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd.
1726
onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1727
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.
1728
*/
1729
1730
Options.Events = {
1731
$extend: false,
1732
1733
enable: false,
1734
enableForEdges: false,
1735
type: 'auto',
1736
onClick: $.empty,
1737
onRightClick: $.empty,
1738
onMouseMove: $.empty,
1739
onMouseEnter: $.empty,
1740
onMouseLeave: $.empty,
1741
onDragStart: $.empty,
1742
onDragMove: $.empty,
1743
onDragCancel: $.empty,
1744
onDragEnd: $.empty,
1745
onTouchStart: $.empty,
1746
onTouchMove: $.empty,
1747
onTouchEnd: $.empty,
1748
onMouseWheel: $.empty
1749
};
1750
1751
/*
1752
* File: Options.Navigation.js
1753
*
1754
*/
1755
1756
/*
1757
Object: Options.Navigation
1758
1759
Panning and zooming options for Graph/Tree based visualizations. These options are implemented
1760
by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1761
1762
Syntax:
1763
1764
(start code js)
1765
1766
Options.Navigation = {
1767
enable: false,
1768
type: 'auto',
1769
panning: false, //true, 'avoid nodes'
1770
zooming: false
1771
};
1772
1773
(end code)
1774
1775
Example:
1776
1777
(start code js)
1778
var viz = new $jit.Viz({
1779
Navigation: {
1780
enable: true,
1781
panning: 'avoid nodes',
1782
zooming: 20
1783
}
1784
});
1785
(end code)
1786
1787
Parameters:
1788
1789
enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1790
type - (string) Default's 'auto'. Whether to attach the navigation events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. When 'auto' set when you let the <Options.Label> *type* parameter decide this.
1791
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>.
1792
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.
1793
1794
*/
1795
1796
Options.Navigation = {
1797
$extend: false,
1798
1799
enable: false,
1800
type: 'auto',
1801
panning: false, //true | 'avoid nodes'
1802
zooming: false
1803
};
1804
1805
/*
1806
* File: Options.Controller.js
1807
*
1808
*/
1809
1810
/*
1811
Object: Options.Controller
1812
1813
Provides controller methods. Controller methods are callback functions that get called at different stages
1814
of the animation, computing or plotting of the visualization.
1815
1816
Implemented by:
1817
1818
All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1819
1820
Syntax:
1821
1822
(start code js)
1823
1824
Options.Controller = {
1825
onBeforeCompute: $.empty,
1826
onAfterCompute: $.empty,
1827
onCreateLabel: $.empty,
1828
onPlaceLabel: $.empty,
1829
onComplete: $.empty,
1830
onBeforePlotLine:$.empty,
1831
onAfterPlotLine: $.empty,
1832
onBeforePlotNode:$.empty,
1833
onAfterPlotNode: $.empty,
1834
request: false
1835
};
1836
1837
(end code)
1838
1839
Example:
1840
1841
(start code js)
1842
var viz = new $jit.Viz({
1843
onBeforePlotNode: function(node) {
1844
if(node.selected) {
1845
node.setData('color', '#ffc');
1846
} else {
1847
node.removeData('color');
1848
}
1849
},
1850
onBeforePlotLine: function(adj) {
1851
if(adj.nodeFrom.selected && adj.nodeTo.selected) {
1852
adj.setData('color', '#ffc');
1853
} else {
1854
adj.removeData('color');
1855
}
1856
},
1857
onAfterCompute: function() {
1858
alert("computed!");
1859
}
1860
});
1861
(end code)
1862
1863
Parameters:
1864
1865
onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
1866
onAfterCompute() - This method is triggered after all animations or computations ended.
1867
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.
1868
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.
1869
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.
1870
onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
1871
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.
1872
onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
1873
1874
*Used in <ST>, <TM.Base> and <Icicle> visualizations*
1875
1876
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.
1877
1878
*/
1879
Options.Controller = {
1880
$extend: true,
1881
1882
onBeforeCompute: $.empty,
1883
onAfterCompute: $.empty,
1884
onCreateLabel: $.empty,
1885
onPlaceLabel: $.empty,
1886
onComplete: $.empty,
1887
onBeforePlotLine:$.empty,
1888
onAfterPlotLine: $.empty,
1889
onBeforePlotNode:$.empty,
1890
onAfterPlotNode: $.empty,
1891
request: false
1892
};
1893
1894
1895
/*
1896
* File: Extras.js
1897
*
1898
* Provides Extras such as Tips and Style Effects.
1899
*
1900
* Description:
1901
*
1902
* Provides the <Tips> and <NodeStyles> classes and functions.
1903
*
1904
*/
1905
1906
/*
1907
* Manager for mouse events (clicking and mouse moving).
1908
*
1909
* This class is used for registering objects implementing onClick
1910
* and onMousemove methods. These methods are called when clicking or
1911
* moving the mouse around the Canvas.
1912
* For now, <Tips> and <NodeStyles> are classes implementing these methods.
1913
*
1914
*/
1915
var ExtrasInitializer = {
1916
initialize: function(className, viz) {
1917
this.viz = viz;
1918
this.canvas = viz.canvas;
1919
this.config = viz.config[className];
1920
this.nodeTypes = viz.fx.nodeTypes;
1921
var type = this.config.type;
1922
this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
1923
this.labelContainer = this.dom && viz.labels.getLabelContainer();
1924
this.isEnabled() && this.initializePost();
1925
},
1926
initializePost: $.empty,
1927
setAsProperty: $.lambda(false),
1928
isEnabled: function() {
1929
return this.config.enable;
1930
},
1931
isLabel: function(e, win, group) {
1932
e = $.event.get(e, win);
1933
var labelContainer = this.labelContainer,
1934
target = e.target || e.srcElement,
1935
related = e.relatedTarget;
1936
if(group) {
1937
return related && related == this.viz.canvas.getCtx().canvas
1938
&& !!target && this.isDescendantOf(target, labelContainer);
1939
} else {
1940
return this.isDescendantOf(target, labelContainer);
1941
}
1942
},
1943
isDescendantOf: function(elem, par) {
1944
while(elem && elem.parentNode) {
1945
if(elem.parentNode == par)
1946
return elem;
1947
elem = elem.parentNode;
1948
}
1949
return false;
1950
}
1951
};
1952
1953
var EventsInterface = {
1954
onMouseUp: $.empty,
1955
onMouseDown: $.empty,
1956
onMouseMove: $.empty,
1957
onMouseOver: $.empty,
1958
onMouseOut: $.empty,
1959
onMouseWheel: $.empty,
1960
onTouchStart: $.empty,
1961
onTouchMove: $.empty,
1962
onTouchEnd: $.empty,
1963
onTouchCancel: $.empty
1964
};
1965
1966
var MouseEventsManager = new Class({
1967
initialize: function(viz) {
1968
this.viz = viz;
1969
this.canvas = viz.canvas;
1970
this.node = false;
1971
this.edge = false;
1972
this.registeredObjects = [];
1973
this.attachEvents();
1974
},
1975
1976
attachEvents: function() {
1977
var htmlCanvas = this.canvas.getElement(),
1978
that = this;
1979
htmlCanvas.oncontextmenu = $.lambda(false);
1980
$.addEvents(htmlCanvas, {
1981
'mouseup': function(e, win) {
1982
var event = $.event.get(e, win);
1983
that.handleEvent('MouseUp', e, win,
1984
that.makeEventObject(e, win),
1985
$.event.isRightClick(event));
1986
},
1987
'mousedown': function(e, win) {
1988
var event = $.event.get(e, win);
1989
that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win),
1990
$.event.isRightClick(event));
1991
},
1992
'mousemove': function(e, win) {
1993
that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
1994
},
1995
'mouseover': function(e, win) {
1996
that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
1997
},
1998
'mouseout': function(e, win) {
1999
that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2000
},
2001
'touchstart': function(e, win) {
2002
that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2003
},
2004
'touchmove': function(e, win) {
2005
that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2006
},
2007
'touchend': function(e, win) {
2008
that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2009
}
2010
});
2011
//attach mousewheel event
2012
var handleMouseWheel = function(e, win) {
2013
var event = $.event.get(e, win);
2014
var wheel = $.event.getWheel(event);
2015
that.handleEvent('MouseWheel', e, win, wheel);
2016
};
2017
//this is a horrible check for non-gecko browsers!
2018
if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2019
$.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2020
} else {
2021
htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2022
}
2023
},
2024
2025
register: function(obj) {
2026
this.registeredObjects.push(obj);
2027
},
2028
2029
handleEvent: function() {
2030
var args = Array.prototype.slice.call(arguments),
2031
type = args.shift();
2032
for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2033
regs[i]['on' + type].apply(regs[i], args);
2034
}
2035
},
2036
2037
makeEventObject: function(e, win) {
2038
var that = this,
2039
graph = this.viz.graph,
2040
fx = this.viz.fx,
2041
ntypes = fx.nodeTypes,
2042
etypes = fx.edgeTypes;
2043
return {
2044
pos: false,
2045
node: false,
2046
edge: false,
2047
contains: false,
2048
getNodeCalled: false,
2049
getEdgeCalled: false,
2050
getPos: function() {
2051
//check why this can't be cache anymore when using edge detection
2052
//if(this.pos) return this.pos;
2053
var canvas = that.viz.canvas,
2054
s = canvas.getSize(),
2055
p = canvas.getPos(),
2056
ox = canvas.translateOffsetX,
2057
oy = canvas.translateOffsetY,
2058
sx = canvas.scaleOffsetX,
2059
sy = canvas.scaleOffsetY,
2060
pos = $.event.getPos(e, win);
2061
this.pos = {
2062
x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2063
y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2064
};
2065
return this.pos;
2066
},
2067
getNode: function() {
2068
if(this.getNodeCalled) return this.node;
2069
this.getNodeCalled = true;
2070
for(var id in graph.nodes) {
2071
var n = graph.nodes[id],
2072
geom = n && ntypes[n.getData('type')],
2073
contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2074
if(contains) {
2075
this.contains = contains;
2076
return that.node = this.node = n;
2077
}
2078
}
2079
return that.node = this.node = false;
2080
},
2081
getEdge: function() {
2082
if(this.getEdgeCalled) return this.edge;
2083
this.getEdgeCalled = true;
2084
var hashset = {};
2085
for(var id in graph.edges) {
2086
var edgeFrom = graph.edges[id];
2087
hashset[id] = true;
2088
for(var edgeId in edgeFrom) {
2089
if(edgeId in hashset) continue;
2090
var e = edgeFrom[edgeId],
2091
geom = e && etypes[e.getData('type')],
2092
contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2093
if(contains) {
2094
this.contains = contains;
2095
return that.edge = this.edge = e;
2096
}
2097
}
2098
}
2099
return that.edge = this.edge = false;
2100
},
2101
getContains: function() {
2102
if(this.getNodeCalled) return this.contains;
2103
this.getNode();
2104
return this.contains;
2105
}
2106
};
2107
}
2108
});
2109
2110
/*
2111
* Provides the initialization function for <NodeStyles> and <Tips> implemented
2112
* by all main visualizations.
2113
*
2114
*/
2115
var Extras = {
2116
initializeExtras: function() {
2117
var mem = new MouseEventsManager(this), that = this;
2118
$.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2119
var obj = new Extras.Classes[k](k, that);
2120
if(obj.isEnabled()) {
2121
mem.register(obj);
2122
}
2123
if(obj.setAsProperty()) {
2124
that[k.toLowerCase()] = obj;
2125
}
2126
});
2127
}
2128
};
2129
2130
Extras.Classes = {};
2131
/*
2132
Class: Events
2133
2134
This class defines an Event API to be accessed by the user.
2135
The methods implemented are the ones defined in the <Options.Events> object.
2136
*/
2137
2138
Extras.Classes.Events = new Class({
2139
Implements: [ExtrasInitializer, EventsInterface],
2140
2141
initializePost: function() {
2142
this.fx = this.viz.fx;
2143
this.ntypes = this.viz.fx.nodeTypes;
2144
this.etypes = this.viz.fx.edgeTypes;
2145
2146
this.hovered = false;
2147
this.pressed = false;
2148
this.touched = false;
2149
2150
this.touchMoved = false;
2151
this.moved = false;
2152
2153
},
2154
2155
setAsProperty: $.lambda(true),
2156
2157
onMouseUp: function(e, win, event, isRightClick) {
2158
var evt = $.event.get(e, win);
2159
if(!this.moved) {
2160
if(isRightClick) {
2161
this.config.onRightClick(this.hovered, event, evt);
2162
} else {
2163
this.config.onClick(this.pressed, event, evt);
2164
}
2165
}
2166
if(this.pressed) {
2167
if(this.moved) {
2168
this.config.onDragEnd(this.pressed, event, evt);
2169
} else {
2170
this.config.onDragCancel(this.pressed, event, evt);
2171
}
2172
this.pressed = this.moved = false;
2173
}
2174
},
2175
2176
onMouseOut: function(e, win, event) {
2177
//mouseout a label
2178
var evt = $.event.get(e, win), label;
2179
if(this.dom && (label = this.isLabel(e, win, true))) {
2180
this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2181
event, evt);
2182
this.hovered = false;
2183
return;
2184
}
2185
//mouseout canvas
2186
var rt = evt.relatedTarget,
2187
canvasWidget = this.canvas.getElement();
2188
while(rt && rt.parentNode) {
2189
if(canvasWidget == rt.parentNode) return;
2190
rt = rt.parentNode;
2191
}
2192
if(this.hovered) {
2193
this.config.onMouseLeave(this.hovered,
2194
event, evt);
2195
this.hovered = false;
2196
}
2197
},
2198
2199
onMouseOver: function(e, win, event) {
2200
//mouseover a label
2201
var evt = $.event.get(e, win), label;
2202
if(this.dom && (label = this.isLabel(e, win, true))) {
2203
this.hovered = this.viz.graph.getNode(label.id);
2204
this.config.onMouseEnter(this.hovered,
2205
event, evt);
2206
}
2207
},
2208
2209
onMouseMove: function(e, win, event) {
2210
var label, evt = $.event.get(e, win);
2211
if(this.pressed) {
2212
this.moved = true;
2213
this.config.onDragMove(this.pressed, event, evt);
2214
return;
2215
}
2216
if(this.dom) {
2217
this.config.onMouseMove(this.hovered,
2218
event, evt);
2219
} else {
2220
if(this.hovered) {
2221
var hn = this.hovered;
2222
var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2223
var contains = geom && geom.contains
2224
&& geom.contains.call(this.fx, hn, event.getPos());
2225
if(contains) {
2226
this.config.onMouseMove(hn, event, evt);
2227
return;
2228
} else {
2229
this.config.onMouseLeave(hn, event, evt);
2230
this.hovered = false;
2231
}
2232
}
2233
if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2234
this.config.onMouseEnter(this.hovered, event, evt);
2235
} else {
2236
this.config.onMouseMove(false, event, evt);
2237
}
2238
}
2239
},
2240
2241
onMouseWheel: function(e, win, delta) {
2242
this.config.onMouseWheel(delta, $.event.get(e, win));
2243
},
2244
2245
onMouseDown: function(e, win, event) {
2246
var evt = $.event.get(e, win), label;
2247
if(this.dom) {
2248
if(label = this.isLabel(e, win)) {
2249
this.pressed = this.viz.graph.getNode(label.id);
2250
}
2251
} else {
2252
this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2253
}
2254
this.pressed && this.config.onDragStart(this.pressed, event, evt);
2255
},
2256
2257
onTouchStart: function(e, win, event) {
2258
var evt = $.event.get(e, win), label;
2259
if(this.dom && (label = this.isLabel(e, win))) {
2260
this.touched = this.viz.graph.getNode(label.id);
2261
} else {
2262
this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2263
}
2264
this.touched && this.config.onTouchStart(this.touched, event, evt);
2265
},
2266
2267
onTouchMove: function(e, win, event) {
2268
var evt = $.event.get(e, win);
2269
if(this.touched) {
2270
this.touchMoved = true;
2271
this.config.onTouchMove(this.touched, event, evt);
2272
}
2273
},
2274
2275
onTouchEnd: function(e, win, event) {
2276
var evt = $.event.get(e, win);
2277
if(this.touched) {
2278
if(this.touchMoved) {
2279
this.config.onTouchEnd(this.touched, event, evt);
2280
} else {
2281
this.config.onTouchCancel(this.touched, event, evt);
2282
}
2283
this.touched = this.touchMoved = false;
2284
}
2285
}
2286
});
2287
2288
/*
2289
Class: Tips
2290
2291
A class containing tip related functions. This class is used internally.
2292
2293
Used by:
2294
2295
<ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2296
2297
See also:
2298
2299
<Options.Tips>
2300
*/
2301
2302
Extras.Classes.Tips = new Class({
2303
Implements: [ExtrasInitializer, EventsInterface],
2304
2305
initializePost: function() {
2306
//add DOM tooltip
2307
if(document.body) {
2308
var tip = $('_tooltip') || document.createElement('div');
2309
tip.id = '_tooltip';
2310
tip.className = 'tip';
2311
$.extend(tip.style, {
2312
position: 'absolute',
2313
display: 'none',
2314
zIndex: 13000
2315
});
2316
document.body.appendChild(tip);
2317
this.tip = tip;
2318
this.node = false;
2319
}
2320
},
2321
2322
setAsProperty: $.lambda(true),
2323
2324
onMouseOut: function(e, win) {
2325
//mouseout a label
2326
var evt = $.event.get(e, win);
2327
if(this.dom && this.isLabel(e, win, true)) {
2328
this.hide(true);
2329
return;
2330
}
2331
//mouseout canvas
2332
var rt = e.relatedTarget,
2333
canvasWidget = this.canvas.getElement();
2334
while(rt && rt.parentNode) {
2335
if(canvasWidget == rt.parentNode) return;
2336
rt = rt.parentNode;
2337
}
2338
this.hide(false);
2339
},
2340
2341
onMouseOver: function(e, win) {
2342
//mouseover a label
2343
var label;
2344
if(this.dom && (label = this.isLabel(e, win, false))) {
2345
this.node = this.viz.graph.getNode(label.id);
2346
this.config.onShow(this.tip, this.node, label);
2347
}
2348
},
2349
2350
onMouseMove: function(e, win, opt) {
2351
if(this.dom && this.isLabel(e, win)) {
2352
this.setTooltipPosition($.event.getPos(e, win));
2353
}
2354
if(!this.dom) {
2355
var node = opt.getNode();
2356
if(!node) {
2357
this.hide(true);
2358
return;
2359
}
2360
if(this.config.force || !this.node || this.node.id != node.id) {
2361
this.node = node;
2362
this.config.onShow(this.tip, node, opt.getContains());
2363
}
2364
this.setTooltipPosition($.event.getPos(e, win));
2365
}
2366
},
2367
2368
setTooltipPosition: function(pos) {
2369
var tip = this.tip,
2370
style = tip.style,
2371
cont = this.config;
2372
style.display = '';
2373
//get window dimensions
2374
var win = {
2375
'height': document.body.clientHeight,
2376
'width': document.body.clientWidth
2377
};
2378
//get tooltip dimensions
2379
var obj = {
2380
'width': tip.offsetWidth,
2381
'height': tip.offsetHeight
2382
};
2383
//set tooltip position
2384
var x = cont.offsetX, y = cont.offsetY;
2385
style.top = ((pos.y + y + obj.height > win.height)?
2386
(pos.y - obj.height - y) : pos.y + y) + 'px';
2387
style.left = ((pos.x + obj.width + x > win.width)?
2388
(pos.x - obj.width - x) : pos.x + x) + 'px';
2389
},
2390
2391
hide: function(triggerCallback) {
2392
this.tip.style.display = 'none';
2393
triggerCallback && this.config.onHide();
2394
}
2395
});
2396
2397
/*
2398
Class: NodeStyles
2399
2400
Change node styles when clicking or hovering a node. This class is used internally.
2401
2402
Used by:
2403
2404
<ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2405
2406
See also:
2407
2408
<Options.NodeStyles>
2409
*/
2410
Extras.Classes.NodeStyles = new Class({
2411
Implements: [ExtrasInitializer, EventsInterface],
2412
2413
initializePost: function() {
2414
this.fx = this.viz.fx;
2415
this.types = this.viz.fx.nodeTypes;
2416
this.nStyles = this.config;
2417
this.nodeStylesOnHover = this.nStyles.stylesHover;
2418
this.nodeStylesOnClick = this.nStyles.stylesClick;
2419
this.hoveredNode = false;
2420
this.fx.nodeFxAnimation = new Animation();
2421
2422
this.down = false;
2423
this.move = false;
2424
},
2425
2426
onMouseOut: function(e, win) {
2427
this.down = this.move = false;
2428
if(!this.hoveredNode) return;
2429
//mouseout a label
2430
if(this.dom && this.isLabel(e, win, true)) {
2431
this.toggleStylesOnHover(this.hoveredNode, false);
2432
}
2433
//mouseout canvas
2434
var rt = e.relatedTarget,
2435
canvasWidget = this.canvas.getElement();
2436
while(rt && rt.parentNode) {
2437
if(canvasWidget == rt.parentNode) return;
2438
rt = rt.parentNode;
2439
}
2440
this.toggleStylesOnHover(this.hoveredNode, false);
2441
this.hoveredNode = false;
2442
},
2443
2444
onMouseOver: function(e, win) {
2445
//mouseover a label
2446
var label;
2447
if(this.dom && (label = this.isLabel(e, win, true))) {
2448
var node = this.viz.graph.getNode(label.id);
2449
if(node.selected) return;
2450
this.hoveredNode = node;
2451
this.toggleStylesOnHover(this.hoveredNode, true);
2452
}
2453
},
2454
2455
onMouseDown: function(e, win, event, isRightClick) {
2456
if(isRightClick) return;
2457
var label;
2458
if(this.dom && (label = this.isLabel(e, win))) {
2459
this.down = this.viz.graph.getNode(label.id);
2460
} else if(!this.dom) {
2461
this.down = event.getNode();
2462
}
2463
this.move = false;
2464
},
2465
2466
onMouseUp: function(e, win, event, isRightClick) {
2467
if(isRightClick) return;
2468
if(!this.move) {
2469
this.onClick(event.getNode());
2470
}
2471
this.down = this.move = false;
2472
},
2473
2474
getRestoredStyles: function(node, type) {
2475
var restoredStyles = {},
2476
nStyles = this['nodeStylesOn' + type];
2477
for(var prop in nStyles) {
2478
restoredStyles[prop] = node.styles['$' + prop];
2479
}
2480
return restoredStyles;
2481
},
2482
2483
toggleStylesOnHover: function(node, set) {
2484
if(this.nodeStylesOnHover) {
2485
this.toggleStylesOn('Hover', node, set);
2486
}
2487
},
2488
2489
toggleStylesOnClick: function(node, set) {
2490
if(this.nodeStylesOnClick) {
2491
this.toggleStylesOn('Click', node, set);
2492
}
2493
},
2494
2495
toggleStylesOn: function(type, node, set) {
2496
var viz = this.viz;
2497
var nStyles = this.nStyles;
2498
if(set) {
2499
var that = this;
2500
if(!node.styles) {
2501
node.styles = $.merge(node.data, {});
2502
}
2503
for(var s in this['nodeStylesOn' + type]) {
2504
var $s = '$' + s;
2505
if(!($s in node.styles)) {
2506
node.styles[$s] = node.getData(s);
2507
}
2508
}
2509
viz.fx.nodeFx($.extend({
2510
'elements': {
2511
'id': node.id,
2512
'properties': that['nodeStylesOn' + type]
2513
},
2514
transition: Trans.Quart.easeOut,
2515
duration:300,
2516
fps:40
2517
}, this.config));
2518
} else {
2519
var restoredStyles = this.getRestoredStyles(node, type);
2520
viz.fx.nodeFx($.extend({
2521
'elements': {
2522
'id': node.id,
2523
'properties': restoredStyles
2524
},
2525
transition: Trans.Quart.easeOut,
2526
duration:300,
2527
fps:40
2528
}, this.config));
2529
}
2530
},
2531
2532
onClick: function(node) {
2533
if(!node) return;
2534
var nStyles = this.nodeStylesOnClick;
2535
if(!nStyles) return;
2536
//if the node is selected then unselect it
2537
if(node.selected) {
2538
this.toggleStylesOnClick(node, false);
2539
delete node.selected;
2540
} else {
2541
//unselect all selected nodes...
2542
this.viz.graph.eachNode(function(n) {
2543
if(n.selected) {
2544
for(var s in nStyles) {
2545
n.setData(s, n.styles['$' + s], 'end');
2546
}
2547
delete n.selected;
2548
}
2549
});
2550
//select clicked node
2551
this.toggleStylesOnClick(node, true);
2552
node.selected = true;
2553
delete node.hovered;
2554
this.hoveredNode = false;
2555
}
2556
},
2557
2558
onMouseMove: function(e, win, event) {
2559
//if mouse button is down and moving set move=true
2560
if(this.down) this.move = true;
2561
//already handled by mouseover/out
2562
if(this.dom && this.isLabel(e, win)) return;
2563
var nStyles = this.nodeStylesOnHover;
2564
if(!nStyles) return;
2565
2566
if(!this.dom) {
2567
if(this.hoveredNode) {
2568
var geom = this.types[this.hoveredNode.getData('type')];
2569
var contains = geom && geom.contains && geom.contains.call(this.fx,
2570
this.hoveredNode, event.getPos());
2571
if(contains) return;
2572
}
2573
var node = event.getNode();
2574
//if no node is being hovered then just exit
2575
if(!this.hoveredNode && !node) return;
2576
//if the node is hovered then exit
2577
if(node.hovered) return;
2578
//select hovered node
2579
if(node && !node.selected) {
2580
//check if an animation is running and exit it
2581
this.fx.nodeFxAnimation.stopTimer();
2582
//unselect all hovered nodes...
2583
this.viz.graph.eachNode(function(n) {
2584
if(n.hovered && !n.selected) {
2585
for(var s in nStyles) {
2586
n.setData(s, n.styles['$' + s], 'end');
2587
}
2588
delete n.hovered;
2589
}
2590
});
2591
//select hovered node
2592
node.hovered = true;
2593
this.hoveredNode = node;
2594
this.toggleStylesOnHover(node, true);
2595
} else if(this.hoveredNode && !this.hoveredNode.selected) {
2596
//check if an animation is running and exit it
2597
this.fx.nodeFxAnimation.stopTimer();
2598
//unselect hovered node
2599
this.toggleStylesOnHover(this.hoveredNode, false);
2600
delete this.hoveredNode.hovered;
2601
this.hoveredNode = false;
2602
}
2603
}
2604
}
2605
});
2606
2607
Extras.Classes.Navigation = new Class({
2608
Implements: [ExtrasInitializer, EventsInterface],
2609
2610
initializePost: function() {
2611
this.pos = false;
2612
this.pressed = false;
2613
},
2614
2615
onMouseWheel: function(e, win, scroll) {
2616
if(!this.config.zooming) return;
2617
$.event.stop($.event.get(e, win));
2618
var val = this.config.zooming / 1000,
2619
ans = 1 + scroll * val;
2620
this.canvas.scale(ans, ans);
2621
},
2622
2623
onMouseDown: function(e, win, eventInfo) {
2624
if(!this.config.panning) return;
2625
if(this.config.panning == 'avoid nodes' && (this.dom? this.isLabel(e, win) : eventInfo.getNode())) return;
2626
this.pressed = true;
2627
this.pos = eventInfo.getPos();
2628
var canvas = this.canvas,
2629
ox = canvas.translateOffsetX,
2630
oy = canvas.translateOffsetY,
2631
sx = canvas.scaleOffsetX,
2632
sy = canvas.scaleOffsetY;
2633
this.pos.x *= sx;
2634
this.pos.x += ox;
2635
this.pos.y *= sy;
2636
this.pos.y += oy;
2637
},
2638
2639
onMouseMove: function(e, win, eventInfo) {
2640
if(!this.config.panning) return;
2641
if(!this.pressed) return;
2642
if(this.config.panning == 'avoid nodes' && (this.dom? this.isLabel(e, win) : eventInfo.getNode())) return;
2643
var thispos = this.pos,
2644
currentPos = eventInfo.getPos(),
2645
canvas = this.canvas,
2646
ox = canvas.translateOffsetX,
2647
oy = canvas.translateOffsetY,
2648
sx = canvas.scaleOffsetX,
2649
sy = canvas.scaleOffsetY;
2650
currentPos.x *= sx;
2651
currentPos.y *= sy;
2652
currentPos.x += ox;
2653
currentPos.y += oy;
2654
var x = currentPos.x - thispos.x,
2655
y = currentPos.y - thispos.y;
2656
this.pos = currentPos;
2657
this.canvas.translate(x * 1/sx, y * 1/sy);
2658
},
2659
2660
onMouseUp: function(e, win, eventInfo, isRightClick) {
2661
if(!this.config.panning) return;
2662
this.pressed = false;
2663
}
2664
});
2665
2666
2667
/*
2668
* File: Canvas.js
2669
*
2670
*/
2671
2672
/*
2673
Class: Canvas
2674
2675
A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to
2676
know more about <Canvas> options take a look at <Options.Canvas>.
2677
2678
A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior
2679
across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2680
2681
Example:
2682
2683
Suppose we have this HTML
2684
2685
(start code xml)
2686
<div id="infovis"></div>
2687
(end code)
2688
2689
Now we create a new Visualization
2690
2691
(start code js)
2692
var viz = new $jit.Viz({
2693
//Where to inject the canvas. Any div container will do.
2694
'injectInto':'infovis',
2695
//width and height for canvas.
2696
//Default's to the container offsetWidth and Height.
2697
'width': 900,
2698
'height':500
2699
});
2700
(end code)
2701
2702
The generated HTML will look like this
2703
2704
(start code xml)
2705
<div id="infovis">
2706
<div id="infovis-canvaswidget" style="position:relative;">
2707
<canvas id="infovis-canvas" width=900 height=500
2708
style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2709
<div id="infovis-label"
2710
style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2711
</div>
2712
</div>
2713
</div>
2714
(end code)
2715
2716
As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2717
of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2718
*/
2719
2720
var Canvas;
2721
(function() {
2722
//check for native canvas support
2723
var canvasType = typeof HTMLCanvasElement,
2724
supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2725
//create element function
2726
function $E(tag, props) {
2727
var elem = document.createElement(tag);
2728
for(var p in props) {
2729
if(typeof props[p] == "object") {
2730
$.extend(elem[p], props[p]);
2731
} else {
2732
elem[p] = props[p];
2733
}
2734
}
2735
if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2736
elem = G_vmlCanvasManager.initElement(document.body.appendChild(elem));
2737
}
2738
return elem;
2739
}
2740
//canvas widget which we will call just Canvas
2741
$jit.Canvas = Canvas = new Class({
2742
canvases: [],
2743
pos: false,
2744
element: false,
2745
labelContainer: false,
2746
translateOffsetX: 0,
2747
translateOffsetY: 0,
2748
scaleOffsetX: 1,
2749
scaleOffsetY: 1,
2750
2751
initialize: function(viz, opt) {
2752
this.viz = viz;
2753
this.opt = this.config = opt;
2754
var id = $.type(opt.injectInto) == 'string'?
2755
opt.injectInto:opt.injectInto.id,
2756
type = opt.type,
2757
idLabel = id + "-label",
2758
wrapper = $(id),
2759
width = opt.width || wrapper.offsetWidth,
2760
height = opt.height || wrapper.offsetHeight;
2761
this.id = id;
2762
//canvas options
2763
var canvasOptions = {
2764
injectInto: id,
2765
width: width,
2766
height: height
2767
};
2768
//create main wrapper
2769
this.element = $E('div', {
2770
'id': id + '-canvaswidget',
2771
'style': {
2772
'position': 'relative',
2773
'width': width + 'px',
2774
'height': height + 'px'
2775
}
2776
});
2777
//create label container
2778
this.labelContainer = this.createLabelContainer(opt.Label.type,
2779
idLabel, canvasOptions);
2780
//create primary canvas
2781
this.canvases.push(new Canvas.Base[type]({
2782
config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2783
plot: function(base) {
2784
viz.fx.plot();
2785
},
2786
resize: function() {
2787
viz.refresh();
2788
}
2789
}));
2790
//create secondary canvas
2791
var back = opt.background;
2792
if(back) {
2793
var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2794
this.canvases.push(new Canvas.Base[type](backCanvas));
2795
}
2796
//insert canvases
2797
var len = this.canvases.length;
2798
while(len--) {
2799
this.element.appendChild(this.canvases[len].canvas);
2800
if(len > 0) {
2801
this.canvases[len].plot();
2802
}
2803
}
2804
this.element.appendChild(this.labelContainer);
2805
wrapper.appendChild(this.element);
2806
//Update canvas position when the page is scrolled.
2807
var timer = null, that = this;
2808
$.addEvent(window, 'scroll', function() {
2809
clearTimeout(timer);
2810
timer = setTimeout(function() {
2811
that.getPos(true); //update canvas position
2812
}, 500);
2813
});
2814
},
2815
/*
2816
Method: getCtx
2817
2818
Returns the main canvas context object
2819
2820
Example:
2821
2822
(start code js)
2823
var ctx = canvas.getCtx();
2824
//Now I can use the native canvas context
2825
//and for example change some canvas styles
2826
ctx.globalAlpha = 1;
2827
(end code)
2828
*/
2829
getCtx: function(i) {
2830
return this.canvases[i || 0].getCtx();
2831
},
2832
/*
2833
Method: getConfig
2834
2835
Returns the current Configuration for this Canvas Widget.
2836
2837
Example:
2838
2839
(start code js)
2840
var config = canvas.getConfig();
2841
(end code)
2842
*/
2843
getConfig: function() {
2844
return this.opt;
2845
},
2846
/*
2847
Method: getElement
2848
2849
Returns the main Canvas DOM wrapper
2850
2851
Example:
2852
2853
(start code js)
2854
var wrapper = canvas.getElement();
2855
//Returns <div id="infovis-canvaswidget" ... >...</div> as element
2856
(end code)
2857
*/
2858
getElement: function() {
2859
return this.element;
2860
},
2861
/*
2862
Method: getSize
2863
2864
Returns canvas dimensions.
2865
2866
Returns:
2867
2868
An object with *width* and *height* properties.
2869
2870
Example:
2871
(start code js)
2872
canvas.getSize(); //returns { width: 900, height: 500 }
2873
(end code)
2874
*/
2875
getSize: function(i) {
2876
return this.canvases[i || 0].getSize();
2877
},
2878
/*
2879
Method: resize
2880
2881
Resizes the canvas.
2882
2883
Parameters:
2884
2885
width - New canvas width.
2886
height - New canvas height.
2887
2888
Example:
2889
2890
(start code js)
2891
canvas.resize(width, height);
2892
(end code)
2893
2894
*/
2895
resize: function(width, height) {
2896
this.getPos(true);
2897
this.translateOffsetX = this.translateOffsetY = 0;
2898
this.scaleOffsetX = this.scaleOffsetY = 1;
2899
for(var i=0, l=this.canvases.length; i<l; i++) {
2900
this.canvases[i].resize(width, height);
2901
}
2902
var style = this.element.style;
2903
style.width = width + 'px';
2904
style.height = height + 'px';
2905
if(this.labelContainer)
2906
this.labelContainer.style.width = width + 'px';
2907
},
2908
/*
2909
Method: translate
2910
2911
Applies a translation to the canvas.
2912
2913
Parameters:
2914
2915
x - (number) x offset.
2916
y - (number) y offset.
2917
disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
2918
2919
Example:
2920
2921
(start code js)
2922
canvas.translate(30, 30);
2923
(end code)
2924
2925
*/
2926
translate: function(x, y, disablePlot) {
2927
this.translateOffsetX += x*this.scaleOffsetX;
2928
this.translateOffsetY += y*this.scaleOffsetY;
2929
for(var i=0, l=this.canvases.length; i<l; i++) {
2930
this.canvases[i].translate(x, y, disablePlot);
2931
}
2932
},
2933
/*
2934
Method: scale
2935
2936
Scales the canvas.
2937
2938
Parameters:
2939
2940
x - (number) scale value.
2941
y - (number) scale value.
2942
disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
2943
2944
Example:
2945
2946
(start code js)
2947
canvas.scale(0.5, 0.5);
2948
(end code)
2949
2950
*/
2951
scale: function(x, y, disablePlot) {
2952
var px = this.scaleOffsetX * x,
2953
py = this.scaleOffsetY * y;
2954
var dx = this.translateOffsetX * (x -1) / px,
2955
dy = this.translateOffsetY * (y -1) / py;
2956
this.scaleOffsetX = px;
2957
this.scaleOffsetY = py;
2958
for(var i=0, l=this.canvases.length; i<l; i++) {
2959
this.canvases[i].scale(x, y, true);
2960
}
2961
this.translate(dx, dy, false);
2962
},
2963
/*
2964
Method: getPos
2965
2966
Returns the canvas position as an *x, y* object.
2967
2968
Parameters:
2969
2970
force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
2971
2972
Returns:
2973
2974
An object with *x* and *y* properties.
2975
2976
Example:
2977
(start code js)
2978
canvas.getPos(true); //returns { x: 900, y: 500 }
2979
(end code)
2980
*/
2981
getPos: function(force){
2982
if(force || !this.pos) {
2983
return this.pos = $.getPos(this.getElement());
2984
}
2985
return this.pos;
2986
},
2987
/*
2988
Method: clear
2989
2990
Clears the canvas.
2991
*/
2992
clear: function(i){
2993
this.canvases[i||0].clear();
2994
},
2995
2996
path: function(type, action){
2997
var ctx = this.canvases[0].getCtx();
2998
ctx.beginPath();
2999
action(ctx);
3000
ctx[type]();
3001
ctx.closePath();
3002
},
3003
3004
createLabelContainer: function(type, idLabel, dim) {
3005
var NS = 'http://www.w3.org/2000/svg';
3006
if(type == 'HTML' || type == 'Native') {
3007
return $E('div', {
3008
'id': idLabel,
3009
'style': {
3010
'overflow': 'visible',
3011
'position': 'absolute',
3012
'top': 0,
3013
'left': 0,
3014
'width': dim.width + 'px',
3015
'height': 0
3016
}
3017
});
3018
} else if(type == 'SVG') {
3019
var svgContainer = document.createElementNS(NS, 'svg:svg');
3020
svgContainer.setAttribute("width", dim.width);
3021
svgContainer.setAttribute('height', dim.height);
3022
var style = svgContainer.style;
3023
style.position = 'absolute';
3024
style.left = style.top = '0px';
3025
var labelContainer = document.createElementNS(NS, 'svg:g');
3026
labelContainer.setAttribute('width', dim.width);
3027
labelContainer.setAttribute('height', dim.height);
3028
labelContainer.setAttribute('x', 0);
3029
labelContainer.setAttribute('y', 0);
3030
labelContainer.setAttribute('id', idLabel);
3031
svgContainer.appendChild(labelContainer);
3032
return svgContainer;
3033
}
3034
}
3035
});
3036
//base canvas wrapper
3037
Canvas.Base = {};
3038
Canvas.Base['2D'] = new Class({
3039
translateOffsetX: 0,
3040
translateOffsetY: 0,
3041
scaleOffsetX: 1,
3042
scaleOffsetY: 1,
3043
3044
initialize: function(viz) {
3045
this.viz = viz;
3046
this.opt = viz.config;
3047
this.size = false;
3048
this.createCanvas();
3049
this.translateToCenter();
3050
},
3051
createCanvas: function() {
3052
var opt = this.opt,
3053
width = opt.width,
3054
height = opt.height;
3055
this.canvas = $E('canvas', {
3056
'id': opt.injectInto + opt.idSuffix,
3057
'width': width,
3058
'height': height,
3059
'style': {
3060
'position': 'absolute',
3061
'top': 0,
3062
'left': 0,
3063
'width': width + 'px',
3064
'height': height + 'px'
3065
}
3066
});
3067
},
3068
getCtx: function() {
3069
if(!this.ctx)
3070
return this.ctx = this.canvas.getContext('2d');
3071
return this.ctx;
3072
},
3073
getSize: function() {
3074
if(this.size) return this.size;
3075
var canvas = this.canvas;
3076
return this.size = {
3077
width: canvas.width,
3078
height: canvas.height
3079
};
3080
},
3081
translateToCenter: function(ps) {
3082
var size = this.getSize(),
3083
width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3084
height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3085
var ctx = this.getCtx();
3086
ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3087
ctx.translate(width/2, height/2);
3088
},
3089
resize: function(width, height) {
3090
var size = this.getSize(),
3091
canvas = this.canvas,
3092
styles = canvas.style;
3093
this.size = false;
3094
canvas.width = width;
3095
canvas.height = height;
3096
styles.width = width + "px";
3097
styles.height = height + "px";
3098
//small ExCanvas fix
3099
if(!supportsCanvas) {
3100
this.translateToCenter(size);
3101
} else {
3102
this.translateToCenter();
3103
}
3104
this.translateOffsetX =
3105
this.translateOffsetY = 0;
3106
this.scaleOffsetX =
3107
this.scaleOffsetY = 1;
3108
this.clear();
3109
this.viz.resize(width, height, this);
3110
},
3111
translate: function(x, y, disablePlot) {
3112
var sx = this.scaleOffsetX,
3113
sy = this.scaleOffsetY;
3114
this.translateOffsetX += x*sx;
3115
this.translateOffsetY += y*sy;
3116
this.getCtx().translate(x, y);
3117
!disablePlot && this.plot();
3118
},
3119
scale: function(x, y, disablePlot) {
3120
this.scaleOffsetX *= x;
3121
this.scaleOffsetY *= y;
3122
this.getCtx().scale(x, y);
3123
!disablePlot && this.plot();
3124
},
3125
clear: function(){
3126
var size = this.getSize(),
3127
ox = this.translateOffsetX,
3128
oy = this.translateOffsetY,
3129
sx = this.scaleOffsetX,
3130
sy = this.scaleOffsetY;
3131
this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
3132
(-size.height / 2 - oy) * 1/sy,
3133
size.width * 1/sx, size.height * 1/sy);
3134
},
3135
plot: function() {
3136
this.clear();
3137
this.viz.plot(this);
3138
}
3139
});
3140
//background canvases
3141
//document this!
3142
Canvas.Background = {};
3143
Canvas.Background.Circles = new Class({
3144
initialize: function(viz, options) {
3145
this.viz = viz;
3146
this.config = $.merge({
3147
idSuffix: '-bkcanvas',
3148
levelDistance: 100,
3149
numberOfCircles: 6,
3150
CanvasStyles: {},
3151
offset: 0
3152
}, options);
3153
},
3154
resize: function(width, height, base) {
3155
this.plot(base);
3156
},
3157
plot: function(base) {
3158
var canvas = base.canvas,
3159
ctx = base.getCtx(),
3160
conf = this.config,
3161
styles = conf.CanvasStyles;
3162
//set canvas styles
3163
for(var s in styles) ctx[s] = styles[s];
3164
var n = conf.numberOfCircles,
3165
rho = conf.levelDistance;
3166
for(var i=1; i<=n; i++) {
3167
ctx.beginPath();
3168
ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3169
ctx.stroke();
3170
ctx.closePath();
3171
}
3172
//print labels too!
3173
}
3174
});
3175
})();
3176
3177
3178
/*
3179
* File: Polar.js
3180
*
3181
* Defines the <Polar> class.
3182
*
3183
* Description:
3184
*
3185
* The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3186
*
3187
* See also:
3188
*
3189
* <http://en.wikipedia.org/wiki/Polar_coordinates>
3190
*
3191
*/
3192
3193
/*
3194
Class: Polar
3195
3196
A multi purpose polar representation.
3197
3198
Description:
3199
3200
The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3201
3202
See also:
3203
3204
<http://en.wikipedia.org/wiki/Polar_coordinates>
3205
3206
Parameters:
3207
3208
theta - An angle.
3209
rho - The norm.
3210
*/
3211
3212
var Polar = function(theta, rho) {
3213
this.theta = theta || 0;
3214
this.rho = rho || 0;
3215
};
3216
3217
$jit.Polar = Polar;
3218
3219
Polar.prototype = {
3220
/*
3221
Method: getc
3222
3223
Returns a complex number.
3224
3225
Parameters:
3226
3227
simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3228
3229
Returns:
3230
3231
A complex number.
3232
*/
3233
getc: function(simple) {
3234
return this.toComplex(simple);
3235
},
3236
3237
/*
3238
Method: getp
3239
3240
Returns a <Polar> representation.
3241
3242
Returns:
3243
3244
A variable in polar coordinates.
3245
*/
3246
getp: function() {
3247
return this;
3248
},
3249
3250
3251
/*
3252
Method: set
3253
3254
Sets a number.
3255
3256
Parameters:
3257
3258
v - A <Complex> or <Polar> instance.
3259
3260
*/
3261
set: function(v) {
3262
v = v.getp();
3263
this.theta = v.theta; this.rho = v.rho;
3264
},
3265
3266
/*
3267
Method: setc
3268
3269
Sets a <Complex> number.
3270
3271
Parameters:
3272
3273
x - A <Complex> number real part.
3274
y - A <Complex> number imaginary part.
3275
3276
*/
3277
setc: function(x, y) {
3278
this.rho = Math.sqrt(x * x + y * y);
3279
this.theta = Math.atan2(y, x);
3280
if(this.theta < 0) this.theta += Math.PI * 2;
3281
},
3282
3283
/*
3284
Method: setp
3285
3286
Sets a polar number.
3287
3288
Parameters:
3289
3290
theta - A <Polar> number angle property.
3291
rho - A <Polar> number rho property.
3292
3293
*/
3294
setp: function(theta, rho) {
3295
this.theta = theta;
3296
this.rho = rho;
3297
},
3298
3299
/*
3300
Method: clone
3301
3302
Returns a copy of the current object.
3303
3304
Returns:
3305
3306
A copy of the real object.
3307
*/
3308
clone: function() {
3309
return new Polar(this.theta, this.rho);
3310
},
3311
3312
/*
3313
Method: toComplex
3314
3315
Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3316
3317
Parameters:
3318
3319
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*.
3320
3321
Returns:
3322
3323
A new <Complex> instance.
3324
*/
3325
toComplex: function(simple) {
3326
var x = Math.cos(this.theta) * this.rho;
3327
var y = Math.sin(this.theta) * this.rho;
3328
if(simple) return { 'x': x, 'y': y};
3329
return new Complex(x, y);
3330
},
3331
3332
/*
3333
Method: add
3334
3335
Adds two <Polar> instances.
3336
3337
Parameters:
3338
3339
polar - A <Polar> number.
3340
3341
Returns:
3342
3343
A new Polar instance.
3344
*/
3345
add: function(polar) {
3346
return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3347
},
3348
3349
/*
3350
Method: scale
3351
3352
Scales a polar norm.
3353
3354
Parameters:
3355
3356
number - A scale factor.
3357
3358
Returns:
3359
3360
A new Polar instance.
3361
*/
3362
scale: function(number) {
3363
return new Polar(this.theta, this.rho * number);
3364
},
3365
3366
/*
3367
Method: equals
3368
3369
Comparison method.
3370
3371
Returns *true* if the theta and rho properties are equal.
3372
3373
Parameters:
3374
3375
c - A <Polar> number.
3376
3377
Returns:
3378
3379
*true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3380
*/
3381
equals: function(c) {
3382
return this.theta == c.theta && this.rho == c.rho;
3383
},
3384
3385
/*
3386
Method: $add
3387
3388
Adds two <Polar> instances affecting the current object.
3389
3390
Paramters:
3391
3392
polar - A <Polar> instance.
3393
3394
Returns:
3395
3396
The changed object.
3397
*/
3398
$add: function(polar) {
3399
this.theta = this.theta + polar.theta; this.rho += polar.rho;
3400
return this;
3401
},
3402
3403
/*
3404
Method: $madd
3405
3406
Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3407
3408
Parameters:
3409
3410
polar - A <Polar> instance.
3411
3412
Returns:
3413
3414
The changed object.
3415
*/
3416
$madd: function(polar) {
3417
this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3418
return this;
3419
},
3420
3421
3422
/*
3423
Method: $scale
3424
3425
Scales a polar instance affecting the object.
3426
3427
Parameters:
3428
3429
number - A scaling factor.
3430
3431
Returns:
3432
3433
The changed object.
3434
*/
3435
$scale: function(number) {
3436
this.rho *= number;
3437
return this;
3438
},
3439
3440
/*
3441
Method: isZero
3442
3443
Returns *true* if the number is zero.
3444
3445
*/
3446
isZero: function () {
3447
var almostZero = 0.0001, abs = Math.abs;
3448
return abs(this.theta) < almostZero && abs(this.rho) < almostZero;
3449
},
3450
3451
/*
3452
Method: interpolate
3453
3454
Calculates a polar interpolation between two points at a given delta moment.
3455
3456
Parameters:
3457
3458
elem - A <Polar> instance.
3459
delta - A delta factor ranging [0, 1].
3460
3461
Returns:
3462
3463
A new <Polar> instance representing an interpolation between _this_ and _elem_
3464
*/
3465
interpolate: function(elem, delta) {
3466
var pi = Math.PI, pi2 = pi * 2;
3467
var ch = function(t) {
3468
var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
3469
return a;
3470
};
3471
var tt = this.theta, et = elem.theta;
3472
var sum, diff = Math.abs(tt - et);
3473
if(diff == pi) {
3474
if(tt > et) {
3475
sum = ch((et + ((tt - pi2) - et) * delta)) ;
3476
} else {
3477
sum = ch((et - pi2 + (tt - (et)) * delta));
3478
}
3479
} else if(diff >= pi) {
3480
if(tt > et) {
3481
sum = ch((et + ((tt - pi2) - et) * delta)) ;
3482
} else {
3483
sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3484
}
3485
} else {
3486
sum = ch((et + (tt - et) * delta)) ;
3487
}
3488
var r = (this.rho - elem.rho) * delta + elem.rho;
3489
return {
3490
'theta': sum,
3491
'rho': r
3492
};
3493
}
3494
};
3495
3496
3497
var $P = function(a, b) { return new Polar(a, b); };
3498
3499
Polar.KER = $P(0, 0);
3500
3501
3502
3503
/*
3504
* File: Complex.js
3505
*
3506
* Defines the <Complex> class.
3507
*
3508
* Description:
3509
*
3510
* The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3511
*
3512
* See also:
3513
*
3514
* <http://en.wikipedia.org/wiki/Complex_number>
3515
*
3516
*/
3517
3518
/*
3519
Class: Complex
3520
3521
A multi-purpose Complex Class with common methods.
3522
3523
Description:
3524
3525
The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3526
3527
See also:
3528
3529
<http://en.wikipedia.org/wiki/Complex_number>
3530
3531
Parameters:
3532
3533
x - _optional_ A Complex number real part.
3534
y - _optional_ A Complex number imaginary part.
3535
3536
*/
3537
3538
var Complex = function(x, y) {
3539
this.x = x || 0;
3540
this.y = y || 0;
3541
};
3542
3543
$jit.Complex = Complex;
3544
3545
Complex.prototype = {
3546
/*
3547
Method: getc
3548
3549
Returns a complex number.
3550
3551
Returns:
3552
3553
A complex number.
3554
*/
3555
getc: function() {
3556
return this;
3557
},
3558
3559
/*
3560
Method: getp
3561
3562
Returns a <Polar> representation of this number.
3563
3564
Parameters:
3565
3566
simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3567
3568
Returns:
3569
3570
A variable in <Polar> coordinates.
3571
*/
3572
getp: function(simple) {
3573
return this.toPolar(simple);
3574
},
3575
3576
3577
/*
3578
Method: set
3579
3580
Sets a number.
3581
3582
Parameters:
3583
3584
c - A <Complex> or <Polar> instance.
3585
3586
*/
3587
set: function(c) {
3588
c = c.getc(true);
3589
this.x = c.x;
3590
this.y = c.y;
3591
},
3592
3593
/*
3594
Method: setc
3595
3596
Sets a complex number.
3597
3598
Parameters:
3599
3600
x - A <Complex> number Real part.
3601
y - A <Complex> number Imaginary part.
3602
3603
*/
3604
setc: function(x, y) {
3605
this.x = x;
3606
this.y = y;
3607
},
3608
3609
/*
3610
Method: setp
3611
3612
Sets a polar number.
3613
3614
Parameters:
3615
3616
theta - A <Polar> number theta property.
3617
rho - A <Polar> number rho property.
3618
3619
*/
3620
setp: function(theta, rho) {
3621
this.x = Math.cos(theta) * rho;
3622
this.y = Math.sin(theta) * rho;
3623
},
3624
3625
/*
3626
Method: clone
3627
3628
Returns a copy of the current object.
3629
3630
Returns:
3631
3632
A copy of the real object.
3633
*/
3634
clone: function() {
3635
return new Complex(this.x, this.y);
3636
},
3637
3638
/*
3639
Method: toPolar
3640
3641
Transforms cartesian to polar coordinates.
3642
3643
Parameters:
3644
3645
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*.
3646
3647
Returns:
3648
3649
A new <Polar> instance.
3650
*/
3651
3652
toPolar: function(simple) {
3653
var rho = this.norm();
3654
var atan = Math.atan2(this.y, this.x);
3655
if(atan < 0) atan += Math.PI * 2;
3656
if(simple) return { 'theta': atan, 'rho': rho };
3657
return new Polar(atan, rho);
3658
},
3659
/*
3660
Method: norm
3661
3662
Calculates a <Complex> number norm.
3663
3664
Returns:
3665
3666
A real number representing the complex norm.
3667
*/
3668
norm: function () {
3669
return Math.sqrt(this.squaredNorm());
3670
},
3671
3672
/*
3673
Method: squaredNorm
3674
3675
Calculates a <Complex> number squared norm.
3676
3677
Returns:
3678
3679
A real number representing the complex squared norm.
3680
*/
3681
squaredNorm: function () {
3682
return this.x*this.x + this.y*this.y;
3683
},
3684
3685
/*
3686
Method: add
3687
3688
Returns the result of adding two complex numbers.
3689
3690
Does not alter the original object.
3691
3692
Parameters:
3693
3694
pos - A <Complex> instance.
3695
3696
Returns:
3697
3698
The result of adding two complex numbers.
3699
*/
3700
add: function(pos) {
3701
return new Complex(this.x + pos.x, this.y + pos.y);
3702
},
3703
3704
/*
3705
Method: prod
3706
3707
Returns the result of multiplying two <Complex> numbers.
3708
3709
Does not alter the original object.
3710
3711
Parameters:
3712
3713
pos - A <Complex> instance.
3714
3715
Returns:
3716
3717
The result of multiplying two complex numbers.
3718
*/
3719
prod: function(pos) {
3720
return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3721
},
3722
3723
/*
3724
Method: conjugate
3725
3726
Returns the conjugate of this <Complex> number.
3727
3728
Does not alter the original object.
3729
3730
Returns:
3731
3732
The conjugate of this <Complex> number.
3733
*/
3734
conjugate: function() {
3735
return new Complex(this.x, -this.y);
3736
},
3737
3738
3739
/*
3740
Method: scale
3741
3742
Returns the result of scaling a <Complex> instance.
3743
3744
Does not alter the original object.
3745
3746
Parameters:
3747
3748
factor - A scale factor.
3749
3750
Returns:
3751
3752
The result of scaling this complex to a factor.
3753
*/
3754
scale: function(factor) {
3755
return new Complex(this.x * factor, this.y * factor);
3756
},
3757
3758
/*
3759
Method: equals
3760
3761
Comparison method.
3762
3763
Returns *true* if both real and imaginary parts are equal.
3764
3765
Parameters:
3766
3767
c - A <Complex> instance.
3768
3769
Returns:
3770
3771
A boolean instance indicating if both <Complex> numbers are equal.
3772
*/
3773
equals: function(c) {
3774
return this.x == c.x && this.y == c.y;
3775
},
3776
3777
/*
3778
Method: $add
3779
3780
Returns the result of adding two <Complex> numbers.
3781
3782
Alters the original object.
3783
3784
Parameters:
3785
3786
pos - A <Complex> instance.
3787
3788
Returns:
3789
3790
The result of adding two complex numbers.
3791
*/
3792
$add: function(pos) {
3793
this.x += pos.x; this.y += pos.y;
3794
return this;
3795
},
3796
3797
/*
3798
Method: $prod
3799
3800
Returns the result of multiplying two <Complex> numbers.
3801
3802
Alters the original object.
3803
3804
Parameters:
3805
3806
pos - A <Complex> instance.
3807
3808
Returns:
3809
3810
The result of multiplying two complex numbers.
3811
*/
3812
$prod:function(pos) {
3813
var x = this.x, y = this.y;
3814
this.x = x*pos.x - y*pos.y;
3815
this.y = y*pos.x + x*pos.y;
3816
return this;
3817
},
3818
3819
/*
3820
Method: $conjugate
3821
3822
Returns the conjugate for this <Complex>.
3823
3824
Alters the original object.
3825
3826
Returns:
3827
3828
The conjugate for this complex.
3829
*/
3830
$conjugate: function() {
3831
this.y = -this.y;
3832
return this;
3833
},
3834
3835
/*
3836
Method: $scale
3837
3838
Returns the result of scaling a <Complex> instance.
3839
3840
Alters the original object.
3841
3842
Parameters:
3843
3844
factor - A scale factor.
3845
3846
Returns:
3847
3848
The result of scaling this complex to a factor.
3849
*/
3850
$scale: function(factor) {
3851
this.x *= factor; this.y *= factor;
3852
return this;
3853
},
3854
3855
/*
3856
Method: $div
3857
3858
Returns the division of two <Complex> numbers.
3859
3860
Alters the original object.
3861
3862
Parameters:
3863
3864
pos - A <Complex> number.
3865
3866
Returns:
3867
3868
The result of scaling this complex to a factor.
3869
*/
3870
$div: function(pos) {
3871
var x = this.x, y = this.y;
3872
var sq = pos.squaredNorm();
3873
this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
3874
return this.$scale(1 / sq);
3875
},
3876
3877
/*
3878
Method: isZero
3879
3880
Returns *true* if the number is zero.
3881
3882
*/
3883
isZero: function () {
3884
var almostZero = 0.0001, abs = Math.abs;
3885
return abs(this.x) < almostZero && abs(this.y) < almostZero;
3886
}
3887
};
3888
3889
var $C = function(a, b) { return new Complex(a, b); };
3890
3891
Complex.KER = $C(0, 0);
3892
3893
3894
3895
/*
3896
* File: Graph.js
3897
*
3898
*/
3899
3900
/*
3901
Class: Graph
3902
3903
A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
3904
3905
An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
3906
3907
Example:
3908
3909
(start code js)
3910
//create new visualization
3911
var viz = new $jit.Viz(options);
3912
//load JSON data
3913
viz.loadJSON(json);
3914
//access model
3915
viz.graph; //<Graph> instance
3916
(end code)
3917
3918
Implements:
3919
3920
The following <Graph.Util> methods are implemented in <Graph>
3921
3922
- <Graph.Util.getNode>
3923
- <Graph.Util.eachNode>
3924
- <Graph.Util.computeLevels>
3925
- <Graph.Util.eachBFS>
3926
- <Graph.Util.clean>
3927
- <Graph.Util.getClosestNodeToPos>
3928
- <Graph.Util.getClosestNodeToOrigin>
3929
3930
*/
3931
3932
$jit.Graph = new Class({
3933
3934
initialize: function(opt, Node, Edge, Label) {
3935
var innerOptions = {
3936
'klass': Complex,
3937
'Node': {}
3938
};
3939
this.Node = Node;
3940
this.Edge = Edge;
3941
this.Label = Label;
3942
this.opt = $.merge(innerOptions, opt || {});
3943
this.nodes = {};
3944
this.edges = {};
3945
3946
//add nodeList methods
3947
var that = this;
3948
this.nodeList = {};
3949
for(var p in Accessors) {
3950
that.nodeList[p] = (function(p) {
3951
return function() {
3952
var args = Array.prototype.slice.call(arguments);
3953
that.eachNode(function(n) {
3954
n[p].apply(n, args);
3955
});
3956
};
3957
})(p);
3958
}
3959
3960
},
3961
3962
/*
3963
Method: getNode
3964
3965
Returns a <Graph.Node> by *id*.
3966
3967
Parameters:
3968
3969
id - (string) A <Graph.Node> id.
3970
3971
Example:
3972
3973
(start code js)
3974
var node = graph.getNode('nodeId');
3975
(end code)
3976
*/
3977
getNode: function(id) {
3978
if(this.hasNode(id)) return this.nodes[id];
3979
return false;
3980
},
3981
3982
/*
3983
Method: get
3984
3985
An alias for <Graph.Util.getNode>. Returns a node by *id*.
3986
3987
Parameters:
3988
3989
id - (string) A <Graph.Node> id.
3990
3991
Example:
3992
3993
(start code js)
3994
var node = graph.get('nodeId');
3995
(end code)
3996
*/
3997
get: function(id) {
3998
return this.getNode(id);
3999
},
4000
4001
/*
4002
Method: getByName
4003
4004
Returns a <Graph.Node> by *name*.
4005
4006
Parameters:
4007
4008
name - (string) A <Graph.Node> name.
4009
4010
Example:
4011
4012
(start code js)
4013
var node = graph.getByName('someName');
4014
(end code)
4015
*/
4016
getByName: function(name) {
4017
for(var id in this.nodes) {
4018
var n = this.nodes[id];
4019
if(n.name == name) return n;
4020
}
4021
return false;
4022
},
4023
4024
/*
4025
Method: getAdjacence
4026
4027
Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4028
4029
Parameters:
4030
4031
id - (string) A <Graph.Node> id.
4032
id2 - (string) A <Graph.Node> id.
4033
*/
4034
getAdjacence: function (id, id2) {
4035
if(id in this.edges) {
4036
return this.edges[id][id2];
4037
}
4038
return false;
4039
},
4040
4041
/*
4042
Method: addNode
4043
4044
Adds a node.
4045
4046
Parameters:
4047
4048
obj - An object with the properties described below
4049
4050
id - (string) A node id
4051
name - (string) A node's name
4052
data - (object) A node's data hash
4053
4054
See also:
4055
<Graph.Node>
4056
4057
*/
4058
addNode: function(obj) {
4059
if(!this.nodes[obj.id]) {
4060
var edges = this.edges[obj.id] = {};
4061
this.nodes[obj.id] = new Graph.Node($.extend({
4062
'id': obj.id,
4063
'name': obj.name,
4064
'data': $.merge(obj.data || {}, {}),
4065
'adjacencies': edges
4066
}, this.opt.Node),
4067
this.opt.klass,
4068
this.Node,
4069
this.Edge,
4070
this.Label);
4071
}
4072
return this.nodes[obj.id];
4073
},
4074
4075
/*
4076
Method: addAdjacence
4077
4078
Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4079
4080
Parameters:
4081
4082
obj - (object) A <Graph.Node> object.
4083
obj2 - (object) Another <Graph.Node> object.
4084
data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4085
4086
See also:
4087
4088
<Graph.Node>, <Graph.Adjacence>
4089
*/
4090
addAdjacence: function (obj, obj2, data) {
4091
if(!this.hasNode(obj.id)) { this.addNode(obj); }
4092
if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4093
obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4094
if(!obj.adjacentTo(obj2)) {
4095
var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4096
var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4097
adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4098
return adjsObj[obj2.id];
4099
}
4100
return this.edges[obj.id][obj2.id];
4101
},
4102
4103
/*
4104
Method: removeNode
4105
4106
Removes a <Graph.Node> matching the specified *id*.
4107
4108
Parameters:
4109
4110
id - (string) A node's id.
4111
4112
*/
4113
removeNode: function(id) {
4114
if(this.hasNode(id)) {
4115
delete this.nodes[id];
4116
var adjs = this.edges[id];
4117
for(var to in adjs) {
4118
delete this.edges[to][id];
4119
}
4120
delete this.edges[id];
4121
}
4122
},
4123
4124
/*
4125
Method: removeAdjacence
4126
4127
Removes a <Graph.Adjacence> matching *id1* and *id2*.
4128
4129
Parameters:
4130
4131
id1 - (string) A <Graph.Node> id.
4132
id2 - (string) A <Graph.Node> id.
4133
*/
4134
removeAdjacence: function(id1, id2) {
4135
delete this.edges[id1][id2];
4136
delete this.edges[id2][id1];
4137
},
4138
4139
/*
4140
Method: hasNode
4141
4142
Returns a boolean indicating if the node belongs to the <Graph> or not.
4143
4144
Parameters:
4145
4146
id - (string) Node id.
4147
*/
4148
hasNode: function(id) {
4149
return id in this.nodes;
4150
},
4151
4152
/*
4153
Method: empty
4154
4155
Empties the Graph
4156
4157
*/
4158
empty: function() { this.nodes = {}; this.edges = {};}
4159
4160
});
4161
4162
var Graph = $jit.Graph;
4163
4164
/*
4165
Object: Accessors
4166
4167
Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4168
4169
*/
4170
var Accessors;
4171
4172
(function () {
4173
var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4174
var data;
4175
type = type || 'current';
4176
prefix = "$" + (prefix ? prefix + "-" : "");
4177
4178
if(type == 'current') {
4179
data = this.data;
4180
} else if(type == 'start') {
4181
data = this.startData;
4182
} else if(type == 'end') {
4183
data = this.endData;
4184
}
4185
4186
var dollar = prefix + prop;
4187
4188
if(force) {
4189
return data[dollar];
4190
}
4191
4192
if(!this.Config.overridable)
4193
return prefixConfig[prop] || 0;
4194
4195
return (dollar in data) ?
4196
data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4197
}
4198
4199
var setDataInternal = function(prefix, prop, value, type) {
4200
type = type || 'current';
4201
prefix = '$' + (prefix ? prefix + '-' : '');
4202
4203
var data;
4204
4205
if(type == 'current') {
4206
data = this.data;
4207
} else if(type == 'start') {
4208
data = this.startData;
4209
} else if(type == 'end') {
4210
data = this.endData;
4211
}
4212
4213
data[prefix + prop] = value;
4214
}
4215
4216
var removeDataInternal = function(prefix, properties) {
4217
prefix = '$' + (prefix ? prefix + '-' : '');
4218
var that = this;
4219
$.each(properties, function(prop) {
4220
var pref = prefix + prop;
4221
delete that.data[pref];
4222
delete that.endData[pref];
4223
delete that.startData[pref];
4224
});
4225
}
4226
4227
Accessors = {
4228
/*
4229
Method: getData
4230
4231
Returns the specified data value property.
4232
This is useful for querying special/reserved <Graph.Node> data properties
4233
(i.e dollar prefixed properties).
4234
4235
Parameters:
4236
4237
prop - (string) The name of the property. The dollar sign is not needed. For
4238
example *getData(width)* will return *data.$width*.
4239
type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4240
data properties also. These properties are used when making animations.
4241
force - (boolean) Whether to obtain the true value of the property (equivalent to
4242
*data.$prop*) or to check for *node.overridable = true* first.
4243
4244
Returns:
4245
4246
The value of the dollar prefixed property or the global Node/Edge property
4247
value if *overridable=false*
4248
4249
Example:
4250
(start code js)
4251
node.getData('width'); //will return node.data.$width if Node.overridable=true;
4252
(end code)
4253
*/
4254
getData: function(prop, type, force) {
4255
return getDataInternal.call(this, "", prop, type, force, this.Config);
4256
},
4257
4258
4259
/*
4260
Method: setData
4261
4262
Sets the current data property with some specific value.
4263
This method is only useful for reserved (dollar prefixed) properties.
4264
4265
Parameters:
4266
4267
prop - (string) The name of the property. The dollar sign is not necessary. For
4268
example *setData(width)* will set *data.$width*.
4269
value - (mixed) The value to store.
4270
type - (string) The type of the data property to store. Default's "current" but
4271
can also be "start" or "end".
4272
4273
Example:
4274
4275
(start code js)
4276
node.setData('width', 30);
4277
(end code)
4278
4279
If we were to make an animation of a node/edge width then we could do
4280
4281
(start code js)
4282
var node = viz.getNode('nodeId');
4283
//set start and end values
4284
node.setData('width', 10, 'start');
4285
node.setData('width', 30, 'end');
4286
//will animate nodes width property
4287
viz.fx.animate({
4288
modes: ['node-property:width'],
4289
duration: 1000
4290
});
4291
(end code)
4292
*/
4293
setData: function(prop, value, type) {
4294
setDataInternal.call(this, "", prop, value, type);
4295
},
4296
4297
/*
4298
Method: setDataset
4299
4300
Convenience method to set multiple data values at once.
4301
4302
Parameters:
4303
4304
types - (array|string) A set of 'current', 'end' or 'start' values.
4305
obj - (object) A hash containing the names and values of the properties to be altered.
4306
4307
Example:
4308
(start code js)
4309
node.setDataset(['current', 'end'], {
4310
'width': [100, 5],
4311
'color': ['#fff', '#ccc']
4312
});
4313
//...or also
4314
node.setDataset('end', {
4315
'width': 5,
4316
'color': '#ccc'
4317
});
4318
(end code)
4319
4320
See also:
4321
4322
<Accessors.setData>
4323
4324
*/
4325
setDataset: function(types, obj) {
4326
types = $.splat(types);
4327
for(var attr in obj) {
4328
for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4329
this.setData(attr, val[i], types[i]);
4330
}
4331
}
4332
},
4333
4334
/*
4335
Method: removeData
4336
4337
Remove data properties.
4338
4339
Parameters:
4340
4341
One or more property names as arguments. The dollar sign is not needed.
4342
4343
Example:
4344
(start code js)
4345
node.removeData('width'); //now the default width value is returned
4346
(end code)
4347
*/
4348
removeData: function() {
4349
removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4350
},
4351
4352
/*
4353
Method: getCanvasStyle
4354
4355
Returns the specified canvas style data value property. This is useful for
4356
querying special/reserved <Graph.Node> canvas style data properties (i.e.
4357
dollar prefixed properties that match with $canvas-<name of canvas style>).
4358
4359
Parameters:
4360
4361
prop - (string) The name of the property. The dollar sign is not needed. For
4362
example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4363
type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4364
data properties also.
4365
4366
Example:
4367
(start code js)
4368
node.getCanvasStyle('shadowBlur');
4369
(end code)
4370
4371
See also:
4372
4373
<Accessors.getData>
4374
*/
4375
getCanvasStyle: function(prop, type, force) {
4376
return getDataInternal.call(
4377
this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4378
},
4379
4380
/*
4381
Method: setCanvasStyle
4382
4383
Sets the canvas style data property with some specific value.
4384
This method is only useful for reserved (dollar prefixed) properties.
4385
4386
Parameters:
4387
4388
prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4389
value - (mixed) The value to set to the property.
4390
type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4391
4392
Example:
4393
4394
(start code js)
4395
node.setCanvasStyle('shadowBlur', 30);
4396
(end code)
4397
4398
If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4399
4400
(start code js)
4401
var node = viz.getNode('nodeId');
4402
//set start and end values
4403
node.setCanvasStyle('shadowBlur', 10, 'start');
4404
node.setCanvasStyle('shadowBlur', 30, 'end');
4405
//will animate nodes canvas style property for nodes
4406
viz.fx.animate({
4407
modes: ['node-style:shadowBlur'],
4408
duration: 1000
4409
});
4410
(end code)
4411
4412
See also:
4413
4414
<Accessors.setData>.
4415
*/
4416
setCanvasStyle: function(prop, value, type) {
4417
setDataInternal.call(this, 'canvas', prop, value, type);
4418
},
4419
4420
/*
4421
Method: setCanvasStyles
4422
4423
Convenience method to set multiple styles at once.
4424
4425
Parameters:
4426
4427
types - (array|string) A set of 'current', 'end' or 'start' values.
4428
obj - (object) A hash containing the names and values of the properties to be altered.
4429
4430
See also:
4431
4432
<Accessors.setDataset>.
4433
*/
4434
setCanvasStyles: function(types, obj) {
4435
types = $.splat(types);
4436
for(var attr in obj) {
4437
for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4438
this.setCanvasStyle(attr, val[i], types[i]);
4439
}
4440
}
4441
},
4442
4443
/*
4444
Method: removeCanvasStyle
4445
4446
Remove canvas style properties from data.
4447
4448
Parameters:
4449
4450
A variable number of canvas style strings.
4451
4452
See also:
4453
4454
<Accessors.removeData>.
4455
*/
4456
removeCanvasStyle: function() {
4457
removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4458
},
4459
4460
/*
4461
Method: getLabelData
4462
4463
Returns the specified label data value property. This is useful for
4464
querying special/reserved <Graph.Node> label options (i.e.
4465
dollar prefixed properties that match with $label-<name of label style>).
4466
4467
Parameters:
4468
4469
prop - (string) The name of the property. The dollar sign prefix is not needed. For
4470
example *getLabelData(size)* will return *data[$label-size]*.
4471
type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4472
data properties also.
4473
4474
See also:
4475
4476
<Accessors.getData>.
4477
*/
4478
getLabelData: function(prop, type, force) {
4479
return getDataInternal.call(
4480
this, 'label', prop, type, force, this.Label);
4481
},
4482
4483
/*
4484
Method: setLabelData
4485
4486
Sets the current label data with some specific value.
4487
This method is only useful for reserved (dollar prefixed) properties.
4488
4489
Parameters:
4490
4491
prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4492
value - (mixed) The value to set to the property.
4493
type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4494
4495
Example:
4496
4497
(start code js)
4498
node.setLabelData('size', 30);
4499
(end code)
4500
4501
If we were to make an animation of a node label size then we could do
4502
4503
(start code js)
4504
var node = viz.getNode('nodeId');
4505
//set start and end values
4506
node.setLabelData('size', 10, 'start');
4507
node.setLabelData('size', 30, 'end');
4508
//will animate nodes label size
4509
viz.fx.animate({
4510
modes: ['label-property:size'],
4511
duration: 1000
4512
});
4513
(end code)
4514
4515
See also:
4516
4517
<Accessors.setData>.
4518
*/
4519
setLabelData: function(prop, value, type) {
4520
setDataInternal.call(this, 'label', prop, value, type);
4521
},
4522
4523
/*
4524
Method: setLabelDataset
4525
4526
Convenience function to set multiple label data at once.
4527
4528
Parameters:
4529
4530
types - (array|string) A set of 'current', 'end' or 'start' values.
4531
obj - (object) A hash containing the names and values of the properties to be altered.
4532
4533
See also:
4534
4535
<Accessors.setDataset>.
4536
*/
4537
setLabelDataset: function(types, obj) {
4538
types = $.splat(types);
4539
for(var attr in obj) {
4540
for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4541
this.setLabelData(attr, val[i], types[i]);
4542
}
4543
}
4544
},
4545
4546
/*
4547
Method: removeLabelData
4548
4549
Remove label properties from data.
4550
4551
Parameters:
4552
4553
A variable number of label property strings.
4554
4555
See also:
4556
4557
<Accessors.removeData>.
4558
*/
4559
removeLabelData: function() {
4560
removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4561
}
4562
};
4563
})();
4564
4565
/*
4566
Class: Graph.Node
4567
4568
A <Graph> node.
4569
4570
Implements:
4571
4572
<Accessors> methods.
4573
4574
The following <Graph.Util> methods are implemented by <Graph.Node>
4575
4576
- <Graph.Util.eachAdjacency>
4577
- <Graph.Util.eachLevel>
4578
- <Graph.Util.eachSubgraph>
4579
- <Graph.Util.eachSubnode>
4580
- <Graph.Util.anySubnode>
4581
- <Graph.Util.getSubnodes>
4582
- <Graph.Util.getParents>
4583
- <Graph.Util.isDescendantOf>
4584
*/
4585
Graph.Node = new Class({
4586
4587
initialize: function(opt, klass, Node, Edge, Label) {
4588
var innerOptions = {
4589
'id': '',
4590
'name': '',
4591
'data': {},
4592
'startData': {},
4593
'endData': {},
4594
'adjacencies': {},
4595
4596
'selected': false,
4597
'drawn': false,
4598
'exist': false,
4599
4600
'angleSpan': {
4601
'begin': 0,
4602
'end' : 0
4603
},
4604
4605
'pos': new klass,
4606
'startPos': new klass,
4607
'endPos': new klass
4608
};
4609
4610
$.extend(this, $.extend(innerOptions, opt));
4611
this.Config = this.Node = Node;
4612
this.Edge = Edge;
4613
this.Label = Label;
4614
},
4615
4616
/*
4617
Method: adjacentTo
4618
4619
Indicates if the node is adjacent to the node specified by id
4620
4621
Parameters:
4622
4623
id - (string) A node id.
4624
4625
Example:
4626
(start code js)
4627
node.adjacentTo('nodeId') == true;
4628
(end code)
4629
*/
4630
adjacentTo: function(node) {
4631
return node.id in this.adjacencies;
4632
},
4633
4634
/*
4635
Method: getAdjacency
4636
4637
Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4638
4639
Parameters:
4640
4641
id - (string) A node id.
4642
*/
4643
getAdjacency: function(id) {
4644
return this.adjacencies[id];
4645
},
4646
4647
/*
4648
Method: getPos
4649
4650
Returns the position of the node.
4651
4652
Parameters:
4653
4654
type - (string) Default's *current*. Possible values are "start", "end" or "current".
4655
4656
Returns:
4657
4658
A <Complex> or <Polar> instance.
4659
4660
Example:
4661
(start code js)
4662
var pos = node.getPos('end');
4663
(end code)
4664
*/
4665
getPos: function(type) {
4666
type = type || "current";
4667
if(type == "current") {
4668
return this.pos;
4669
} else if(type == "end") {
4670
return this.endPos;
4671
} else if(type == "start") {
4672
return this.startPos;
4673
}
4674
},
4675
/*
4676
Method: setPos
4677
4678
Sets the node's position.
4679
4680
Parameters:
4681
4682
value - (object) A <Complex> or <Polar> instance.
4683
type - (string) Default's *current*. Possible values are "start", "end" or "current".
4684
4685
Example:
4686
(start code js)
4687
node.setPos(new $jit.Complex(0, 0), 'end');
4688
(end code)
4689
*/
4690
setPos: function(value, type) {
4691
type = type || "current";
4692
var pos;
4693
if(type == "current") {
4694
pos = this.pos;
4695
} else if(type == "end") {
4696
pos = this.endPos;
4697
} else if(type == "start") {
4698
pos = this.startPos;
4699
}
4700
pos.set(value);
4701
}
4702
});
4703
4704
Graph.Node.implement(Accessors);
4705
4706
/*
4707
Class: Graph.Adjacence
4708
4709
A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4710
4711
Implements:
4712
4713
<Accessors> methods.
4714
4715
See also:
4716
4717
<Graph>, <Graph.Node>
4718
4719
Properties:
4720
4721
nodeFrom - A <Graph.Node> connected by this edge.
4722
nodeTo - Another <Graph.Node> connected by this edge.
4723
data - Node data property containing a hash (i.e {}) with custom options.
4724
*/
4725
Graph.Adjacence = new Class({
4726
4727
initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4728
this.nodeFrom = nodeFrom;
4729
this.nodeTo = nodeTo;
4730
this.data = data || {};
4731
this.startData = {};
4732
this.endData = {};
4733
this.Config = this.Edge = Edge;
4734
this.Label = Label;
4735
}
4736
});
4737
4738
Graph.Adjacence.implement(Accessors);
4739
4740
/*
4741
Object: Graph.Util
4742
4743
<Graph> traversal and processing utility object.
4744
4745
Note:
4746
4747
For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4748
*/
4749
Graph.Util = {
4750
/*
4751
filter
4752
4753
For internal use only. Provides a filtering function based on flags.
4754
*/
4755
filter: function(param) {
4756
if(!param || !($.type(param) == 'string')) return function() { return true; };
4757
var props = param.split(" ");
4758
return function(elem) {
4759
for(var i=0; i<props.length; i++) {
4760
if(elem[props[i]]) {
4761
return false;
4762
}
4763
}
4764
return true;
4765
};
4766
},
4767
/*
4768
Method: getNode
4769
4770
Returns a <Graph.Node> by *id*.
4771
4772
Also implemented by:
4773
4774
<Graph>
4775
4776
Parameters:
4777
4778
graph - (object) A <Graph> instance.
4779
id - (string) A <Graph.Node> id.
4780
4781
Example:
4782
4783
(start code js)
4784
$jit.Graph.Util.getNode(graph, 'nodeid');
4785
//or...
4786
graph.getNode('nodeid');
4787
(end code)
4788
*/
4789
getNode: function(graph, id) {
4790
return graph.nodes[id];
4791
},
4792
4793
/*
4794
Method: eachNode
4795
4796
Iterates over <Graph> nodes performing an *action*.
4797
4798
Also implemented by:
4799
4800
<Graph>.
4801
4802
Parameters:
4803
4804
graph - (object) A <Graph> instance.
4805
action - (function) A callback function having a <Graph.Node> as first formal parameter.
4806
4807
Example:
4808
(start code js)
4809
$jit.Graph.Util.eachNode(graph, function(node) {
4810
alert(node.name);
4811
});
4812
//or...
4813
graph.eachNode(function(node) {
4814
alert(node.name);
4815
});
4816
(end code)
4817
*/
4818
eachNode: function(graph, action, flags) {
4819
var filter = this.filter(flags);
4820
for(var i in graph.nodes) {
4821
if(filter(graph.nodes[i])) action(graph.nodes[i]);
4822
}
4823
},
4824
4825
/*
4826
Method: each
4827
4828
Iterates over <Graph> nodes performing an *action*. It's an alias for <Graph.Util.eachNode>.
4829
4830
Also implemented by:
4831
4832
<Graph>.
4833
4834
Parameters:
4835
4836
graph - (object) A <Graph> instance.
4837
action - (function) A callback function having a <Graph.Node> as first formal parameter.
4838
4839
Example:
4840
(start code js)
4841
$jit.Graph.Util.each(graph, function(node) {
4842
alert(node.name);
4843
});
4844
//or...
4845
graph.each(function(node) {
4846
alert(node.name);
4847
});
4848
(end code)
4849
*/
4850
each: function(graph, action, flags) {
4851
this.eachNode(graph, action, flags);
4852
},
4853
4854
/*
4855
Method: eachAdjacency
4856
4857
Iterates over <Graph.Node> adjacencies applying the *action* function.
4858
4859
Also implemented by:
4860
4861
<Graph.Node>.
4862
4863
Parameters:
4864
4865
node - (object) A <Graph.Node>.
4866
action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4867
4868
Example:
4869
(start code js)
4870
$jit.Graph.Util.eachAdjacency(node, function(adj) {
4871
alert(adj.nodeTo.name);
4872
});
4873
//or...
4874
node.eachAdjacency(function(adj) {
4875
alert(adj.nodeTo.name);
4876
});
4877
(end code)
4878
*/
4879
eachAdjacency: function(node, action, flags) {
4880
var adj = node.adjacencies, filter = this.filter(flags);
4881
for(var id in adj) {
4882
var a = adj[id];
4883
if(filter(a)) {
4884
if(a.nodeFrom != node) {
4885
var tmp = a.nodeFrom;
4886
a.nodeFrom = a.nodeTo;
4887
a.nodeTo = tmp;
4888
}
4889
action(a, id);
4890
}
4891
}
4892
},
4893
4894
/*
4895
Method: computeLevels
4896
4897
Performs a BFS traversal setting the correct depth for each node.
4898
4899
Also implemented by:
4900
4901
<Graph>.
4902
4903
Note:
4904
4905
The depth of each node can then be accessed by
4906
>node._depth
4907
4908
Parameters:
4909
4910
graph - (object) A <Graph>.
4911
id - (string) A starting node id for the BFS traversal.
4912
startDepth - (optional|number) A minimum depth value. Default's 0.
4913
4914
*/
4915
computeLevels: function(graph, id, startDepth, flags) {
4916
startDepth = startDepth || 0;
4917
var filter = this.filter(flags);
4918
this.eachNode(graph, function(elem) {
4919
elem._flag = false;
4920
elem._depth = -1;
4921
}, flags);
4922
var root = graph.getNode(id);
4923
root._depth = startDepth;
4924
var queue = [root];
4925
while(queue.length != 0) {
4926
var node = queue.pop();
4927
node._flag = true;
4928
this.eachAdjacency(node, function(adj) {
4929
var n = adj.nodeTo;
4930
if(n._flag == false && filter(n)) {
4931
if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
4932
queue.unshift(n);
4933
}
4934
}, flags);
4935
}
4936
},
4937
4938
/*
4939
Method: eachBFS
4940
4941
Performs a BFS traversal applying *action* to each <Graph.Node>.
4942
4943
Also implemented by:
4944
4945
<Graph>.
4946
4947
Parameters:
4948
4949
graph - (object) A <Graph>.
4950
id - (string) A starting node id for the BFS traversal.
4951
action - (function) A callback function having a <Graph.Node> as first formal parameter.
4952
4953
Example:
4954
(start code js)
4955
$jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
4956
alert(node.name);
4957
});
4958
//or...
4959
graph.eachBFS('mynodeid', function(node) {
4960
alert(node.name);
4961
});
4962
(end code)
4963
*/
4964
eachBFS: function(graph, id, action, flags) {
4965
var filter = this.filter(flags);
4966
this.clean(graph);
4967
var queue = [graph.getNode(id)];
4968
while(queue.length != 0) {
4969
var node = queue.pop();
4970
node._flag = true;
4971
action(node, node._depth);
4972
this.eachAdjacency(node, function(adj) {
4973
var n = adj.nodeTo;
4974
if(n._flag == false && filter(n)) {
4975
n._flag = true;
4976
queue.unshift(n);
4977
}
4978
}, flags);
4979
}
4980
},
4981
4982
/*
4983
Method: eachLevel
4984
4985
Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
4986
4987
Also implemented by:
4988
4989
<Graph.Node>.
4990
4991
Parameters:
4992
4993
node - (object) A <Graph.Node>.
4994
levelBegin - (number) A relative level value.
4995
levelEnd - (number) A relative level value.
4996
action - (function) A callback function having a <Graph.Node> as first formal parameter.
4997
4998
*/
4999
eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5000
var d = node._depth, filter = this.filter(flags), that = this;
5001
levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5002
(function loopLevel(node, levelBegin, levelEnd) {
5003
var d = node._depth;
5004
if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5005
if(d < levelEnd) {
5006
that.eachAdjacency(node, function(adj) {
5007
var n = adj.nodeTo;
5008
if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5009
});
5010
}
5011
})(node, levelBegin + d, levelEnd + d);
5012
},
5013
5014
/*
5015
Method: eachSubgraph
5016
5017
Iterates over a node's children recursively.
5018
5019
Also implemented by:
5020
5021
<Graph.Node>.
5022
5023
Parameters:
5024
node - (object) A <Graph.Node>.
5025
action - (function) A callback function having a <Graph.Node> as first formal parameter.
5026
5027
Example:
5028
(start code js)
5029
$jit.Graph.Util.eachSubgraph(node, function(node) {
5030
alert(node.name);
5031
});
5032
//or...
5033
node.eachSubgraph(function(node) {
5034
alert(node.name);
5035
});
5036
(end code)
5037
*/
5038
eachSubgraph: function(node, action, flags) {
5039
this.eachLevel(node, 0, false, action, flags);
5040
},
5041
5042
/*
5043
Method: eachSubnode
5044
5045
Iterates over a node's children (without deeper recursion).
5046
5047
Also implemented by:
5048
5049
<Graph.Node>.
5050
5051
Parameters:
5052
node - (object) A <Graph.Node>.
5053
action - (function) A callback function having a <Graph.Node> as first formal parameter.
5054
5055
Example:
5056
(start code js)
5057
$jit.Graph.Util.eachSubnode(node, function(node) {
5058
alert(node.name);
5059
});
5060
//or...
5061
node.eachSubnode(function(node) {
5062
alert(node.name);
5063
});
5064
(end code)
5065
*/
5066
eachSubnode: function(node, action, flags) {
5067
this.eachLevel(node, 1, 1, action, flags);
5068
},
5069
5070
/*
5071
Method: anySubnode
5072
5073
Returns *true* if any subnode matches the given condition.
5074
5075
Also implemented by:
5076
5077
<Graph.Node>.
5078
5079
Parameters:
5080
node - (object) A <Graph.Node>.
5081
cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5082
5083
Example:
5084
(start code js)
5085
$jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5086
//or...
5087
node.anySubnode(function(node) { return node.name == 'mynodename'; });
5088
(end code)
5089
*/
5090
anySubnode: function(node, cond, flags) {
5091
var flag = false;
5092
cond = cond || $.lambda(true);
5093
var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5094
this.eachSubnode(node, function(elem) {
5095
if(c(elem)) flag = true;
5096
}, flags);
5097
return flag;
5098
},
5099
5100
/*
5101
Method: getSubnodes
5102
5103
Collects all subnodes for a specified node.
5104
The *level* parameter filters nodes having relative depth of *level* from the root node.
5105
5106
Also implemented by:
5107
5108
<Graph.Node>.
5109
5110
Parameters:
5111
node - (object) A <Graph.Node>.
5112
level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5113
5114
Returns:
5115
An array of nodes.
5116
5117
*/
5118
getSubnodes: function(node, level, flags) {
5119
var ans = [], that = this;
5120
level = level || 0;
5121
var levelStart, levelEnd;
5122
if($.type(level) == 'array') {
5123
levelStart = level[0];
5124
levelEnd = level[1];
5125
} else {
5126
levelStart = level;
5127
levelEnd = Number.MAX_VALUE - node._depth;
5128
}
5129
this.eachLevel(node, levelStart, levelEnd, function(n) {
5130
ans.push(n);
5131
}, flags);
5132
return ans;
5133
},
5134
5135
5136
/*
5137
Method: getParents
5138
5139
Returns an Array of <Graph.Nodes> which are parents of the given node.
5140
5141
Also implemented by:
5142
5143
<Graph.Node>.
5144
5145
Parameters:
5146
node - (object) A <Graph.Node>.
5147
5148
Returns:
5149
An Array of <Graph.Nodes>.
5150
5151
Example:
5152
(start code js)
5153
var pars = $jit.Graph.Util.getParents(node);
5154
//or...
5155
var pars = node.getParents();
5156
5157
if(pars.length > 0) {
5158
//do stuff with parents
5159
}
5160
(end code)
5161
*/
5162
getParents: function(node) {
5163
var ans = [];
5164
this.eachAdjacency(node, function(adj) {
5165
var n = adj.nodeTo;
5166
if(n._depth < node._depth) ans.push(n);
5167
});
5168
return ans;
5169
},
5170
5171
/*
5172
Method: isDescendantOf
5173
5174
Returns a boolean indicating if some node is descendant of the node with the given id.
5175
5176
Also implemented by:
5177
5178
<Graph.Node>.
5179
5180
5181
Parameters:
5182
node - (object) A <Graph.Node>.
5183
id - (string) A <Graph.Node> id.
5184
5185
Example:
5186
(start code js)
5187
$jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5188
//or...
5189
node.isDescendantOf('nodeid');//true|false
5190
(end code)
5191
*/
5192
isDescendantOf: function(node, id) {
5193
if(node.id == id) return true;
5194
var pars = this.getParents(node), ans = false;
5195
for ( var i = 0; !ans && i < pars.length; i++) {
5196
ans = ans || this.isDescendantOf(pars[i], id);
5197
}
5198
return ans;
5199
},
5200
5201
/*
5202
Method: clean
5203
5204
Cleans flags from nodes.
5205
5206
Also implemented by:
5207
5208
<Graph>.
5209
5210
Parameters:
5211
graph - A <Graph> instance.
5212
*/
5213
clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5214
5215
/*
5216
Method: getClosestNodeToOrigin
5217
5218
Returns the closest node to the center of canvas.
5219
5220
Also implemented by:
5221
5222
<Graph>.
5223
5224
Parameters:
5225
5226
graph - (object) A <Graph> instance.
5227
prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5228
5229
*/
5230
getClosestNodeToOrigin: function(graph, prop, flags) {
5231
return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5232
},
5233
5234
/*
5235
Method: getClosestNodeToPos
5236
5237
Returns the closest node to the given position.
5238
5239
Also implemented by:
5240
5241
<Graph>.
5242
5243
Parameters:
5244
5245
graph - (object) A <Graph> instance.
5246
pos - (object) A <Complex> or <Polar> instance.
5247
prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5248
5249
*/
5250
getClosestNodeToPos: function(graph, pos, prop, flags) {
5251
var node = null;
5252
prop = prop || 'current';
5253
pos = pos && pos.getc(true) || Complex.KER;
5254
var distance = function(a, b) {
5255
var d1 = a.x - b.x, d2 = a.y - b.y;
5256
return d1 * d1 + d2 * d2;
5257
};
5258
this.eachNode(graph, function(elem) {
5259
node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5260
node.getPos(prop).getc(true), pos)) ? elem : node;
5261
}, flags);
5262
return node;
5263
}
5264
};
5265
5266
//Append graph methods to <Graph>
5267
$.each(['get', 'getNode', 'each', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5268
Graph.prototype[m] = function() {
5269
return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5270
};
5271
});
5272
5273
//Append node methods to <Graph.Node>
5274
$.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5275
Graph.Node.prototype[m] = function() {
5276
return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5277
};
5278
});
5279
5280
/*
5281
* File: Graph.Op.js
5282
*
5283
*/
5284
5285
/*
5286
Object: Graph.Op
5287
5288
Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5289
morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5290
5291
*/
5292
Graph.Op = {
5293
5294
options: {
5295
type: 'nothing',
5296
duration: 2000,
5297
hideLabels: true,
5298
fps:30
5299
},
5300
5301
initialize: function(viz) {
5302
this.viz = viz;
5303
},
5304
5305
/*
5306
Method: removeNode
5307
5308
Removes one or more <Graph.Nodes> from the visualization.
5309
It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5310
5311
Parameters:
5312
5313
node - (string|array) The node's id. Can also be an array having many ids.
5314
opt - (object) Animation options. It's an object with optional properties described below
5315
type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5316
duration - Described in <Options.Fx>.
5317
fps - Described in <Options.Fx>.
5318
transition - Described in <Options.Fx>.
5319
hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5320
5321
Example:
5322
(start code js)
5323
var viz = new $jit.Viz(options);
5324
viz.op.removeNode('nodeId', {
5325
type: 'fade:seq',
5326
duration: 1000,
5327
hideLabels: false,
5328
transition: $jit.Trans.Quart.easeOut
5329
});
5330
//or also
5331
viz.op.removeNode(['someId', 'otherId'], {
5332
type: 'fade:con',
5333
duration: 1500
5334
});
5335
(end code)
5336
*/
5337
5338
removeNode: function(node, opt) {
5339
var viz = this.viz;
5340
var options = $.merge(this.options, viz.controller, opt);
5341
var n = $.splat(node);
5342
var i, that, nodeObj;
5343
switch(options.type) {
5344
case 'nothing':
5345
for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5346
break;
5347
5348
case 'replot':
5349
this.removeNode(n, { type: 'nothing' });
5350
viz.labels.clearLabels();
5351
viz.refresh(true);
5352
break;
5353
5354
case 'fade:seq': case 'fade':
5355
that = this;
5356
//set alpha to 0 for nodes to remove.
5357
for(i=0; i<n.length; i++) {
5358
nodeObj = viz.graph.getNode(n[i]);
5359
nodeObj.setData('alpha', 0, 'end');
5360
}
5361
viz.fx.animate($.merge(options, {
5362
modes: ['node-property:alpha'],
5363
onComplete: function() {
5364
that.removeNode(n, { type: 'nothing' });
5365
viz.labels.clearLabels();
5366
viz.reposition();
5367
viz.fx.animate($.merge(options, {
5368
modes: ['linear']
5369
}));
5370
}
5371
}));
5372
break;
5373
5374
case 'fade:con':
5375
that = this;
5376
//set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5377
for(i=0; i<n.length; i++) {
5378
nodeObj = viz.graph.getNode(n[i]);
5379
nodeObj.setData('alpha', 0, 'end');
5380
nodeObj.ignore = true;
5381
}
5382
viz.reposition();
5383
viz.fx.animate($.merge(options, {
5384
modes: ['node-property:alpha', 'linear'],
5385
onComplete: function() {
5386
that.removeNode(n, { type: 'nothing' });
5387
options.onComplete && options.onComplete();
5388
}
5389
}));
5390
break;
5391
5392
case 'iter':
5393
that = this;
5394
viz.fx.sequence({
5395
condition: function() { return n.length != 0; },
5396
step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5397
onComplete: function() { options.onComplete && options.onComplete(); },
5398
duration: Math.ceil(options.duration / n.length)
5399
});
5400
break;
5401
5402
default: this.doError();
5403
}
5404
},
5405
5406
/*
5407
Method: removeEdge
5408
5409
Removes one or more <Graph.Adjacences> from the visualization.
5410
It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5411
5412
Parameters:
5413
5414
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'], ...]).
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.removeEdge(['nodeId', 'otherId'], {
5426
type: 'fade:seq',
5427
duration: 1000,
5428
hideLabels: false,
5429
transition: $jit.Trans.Quart.easeOut
5430
});
5431
//or also
5432
viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5433
type: 'fade:con',
5434
duration: 1500
5435
});
5436
(end code)
5437
5438
*/
5439
removeEdge: function(vertex, opt) {
5440
var viz = this.viz;
5441
var options = $.merge(this.options, viz.controller, opt);
5442
var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5443
var i, that, adj;
5444
switch(options.type) {
5445
case 'nothing':
5446
for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5447
break;
5448
5449
case 'replot':
5450
this.removeEdge(v, { type: 'nothing' });
5451
viz.refresh(true);
5452
break;
5453
5454
case 'fade:seq': case 'fade':
5455
that = this;
5456
//set alpha to 0 for edges to remove.
5457
for(i=0; i<v.length; i++) {
5458
adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5459
if(adj) {
5460
adj.setData('alpha', 0,'end');
5461
}
5462
}
5463
viz.fx.animate($.merge(options, {
5464
modes: ['edge-property:alpha'],
5465
onComplete: function() {
5466
that.removeEdge(v, { type: 'nothing' });
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 when computing positions.
5478
for(i=0; i<v.length; i++) {
5479
adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5480
if(adj) {
5481
adj.setData('alpha',0 ,'end');
5482
adj.ignore = true;
5483
}
5484
}
5485
viz.reposition();
5486
viz.fx.animate($.merge(options, {
5487
modes: ['edge-property:alpha', 'linear'],
5488
onComplete: function() {
5489
that.removeEdge(v, { type: 'nothing' });
5490
options.onComplete && options.onComplete();
5491
}
5492
}));
5493
break;
5494
5495
case 'iter':
5496
that = this;
5497
viz.fx.sequence({
5498
condition: function() { return v.length != 0; },
5499
step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5500
onComplete: function() { options.onComplete(); },
5501
duration: Math.ceil(options.duration / v.length)
5502
});
5503
break;
5504
5505
default: this.doError();
5506
}
5507
},
5508
5509
/*
5510
Method: sum
5511
5512
Adds a new graph to the visualization.
5513
The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5514
The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5515
5516
Parameters:
5517
5518
json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5519
opt - (object) Animation options. It's an object with optional properties described below
5520
type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5521
duration - Described in <Options.Fx>.
5522
fps - Described in <Options.Fx>.
5523
transition - Described in <Options.Fx>.
5524
hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5525
5526
Example:
5527
(start code js)
5528
//...json contains a tree or graph structure...
5529
5530
var viz = new $jit.Viz(options);
5531
viz.op.sum(json, {
5532
type: 'fade:seq',
5533
duration: 1000,
5534
hideLabels: false,
5535
transition: $jit.Trans.Quart.easeOut
5536
});
5537
//or also
5538
viz.op.sum(json, {
5539
type: 'fade:con',
5540
duration: 1500
5541
});
5542
(end code)
5543
5544
*/
5545
sum: function(json, opt) {
5546
var viz = this.viz;
5547
var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5548
var graph;
5549
viz.root = opt.id || viz.root;
5550
switch(options.type) {
5551
case 'nothing':
5552
graph = viz.construct(json);
5553
graph.eachNode(function(elem) {
5554
elem.eachAdjacency(function(adj) {
5555
viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5556
});
5557
});
5558
break;
5559
5560
case 'replot':
5561
viz.refresh(true);
5562
this.sum(json, { type: 'nothing' });
5563
viz.refresh(true);
5564
break;
5565
5566
case 'fade:seq': case 'fade': case 'fade:con':
5567
that = this;
5568
graph = viz.construct(json);
5569
5570
//set alpha to 0 for nodes to add.
5571
var fadeEdges = this.preprocessSum(graph);
5572
var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5573
viz.reposition();
5574
if(options.type != 'fade:con') {
5575
viz.fx.animate($.merge(options, {
5576
modes: ['linear'],
5577
onComplete: function() {
5578
viz.fx.animate($.merge(options, {
5579
modes: modes,
5580
onComplete: function() {
5581
options.onComplete();
5582
}
5583
}));
5584
}
5585
}));
5586
} else {
5587
viz.graph.eachNode(function(elem) {
5588
if (elem.id != root && elem.pos.isZero()) {
5589
elem.pos.set(elem.endPos);
5590
elem.startPos.set(elem.endPos);
5591
}
5592
});
5593
viz.fx.animate($.merge(options, {
5594
modes: ['linear'].concat(modes)
5595
}));
5596
}
5597
break;
5598
5599
default: this.doError();
5600
}
5601
},
5602
5603
/*
5604
Method: morph
5605
5606
This method will transform the current visualized graph into the new JSON representation passed in the method.
5607
The JSON object must at least have the root node in common with the current visualized graph.
5608
5609
Parameters:
5610
5611
json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5612
opt - (object) Animation options. It's an object with optional properties described below
5613
type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5614
duration - Described in <Options.Fx>.
5615
fps - Described in <Options.Fx>.
5616
transition - Described in <Options.Fx>.
5617
hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5618
id - (string) The shared <Graph.Node> id between both graphs.
5619
5620
extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5621
*endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5622
For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5623
properties as values, just like specified in <Graph.Plot.animate>.
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.morph(json, {
5631
type: 'fade',
5632
duration: 1000,
5633
hideLabels: false,
5634
transition: $jit.Trans.Quart.easeOut
5635
});
5636
//or also
5637
viz.op.morph(json, {
5638
type: 'fade',
5639
duration: 1500
5640
});
5641
//if the json data contains dollar prefixed params
5642
//like $width or $height these too can be animated
5643
viz.op.morph(json, {
5644
type: 'fade',
5645
duration: 1500
5646
}, {
5647
'node-property': ['width', 'height']
5648
});
5649
(end code)
5650
5651
*/
5652
morph: function(json, opt, extraModes) {
5653
extraModes = extraModes || {};
5654
var viz = this.viz;
5655
var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5656
var graph;
5657
//TODO(nico) this hack makes morphing work with the Hypertree.
5658
//Need to check if it has been solved and this can be removed.
5659
viz.root = opt.id || viz.root;
5660
switch(options.type) {
5661
case 'nothing':
5662
graph = viz.construct(json);
5663
graph.eachNode(function(elem) {
5664
var nodeExists = viz.graph.hasNode(elem.id);
5665
elem.eachAdjacency(function(adj) {
5666
var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5667
viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5668
//Update data properties if the node existed
5669
if(adjExists) {
5670
var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5671
for(var prop in (adj.data || {})) {
5672
addedAdj.data[prop] = adj.data[prop];
5673
}
5674
}
5675
});
5676
//Update data properties if the node existed
5677
if(nodeExists) {
5678
var addedNode = viz.graph.getNode(elem.id);
5679
for(var prop in (elem.data || {})) {
5680
addedNode.data[prop] = elem.data[prop];
5681
}
5682
}
5683
});
5684
viz.graph.eachNode(function(elem) {
5685
elem.eachAdjacency(function(adj) {
5686
if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5687
viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5688
}
5689
});
5690
if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5691
});
5692
5693
break;
5694
5695
case 'replot':
5696
viz.labels.clearLabels(true);
5697
this.morph(json, { type: 'nothing' });
5698
viz.refresh(true);
5699
viz.refresh(true);
5700
break;
5701
5702
case 'fade:seq': case 'fade': case 'fade:con':
5703
that = this;
5704
graph = viz.construct(json);
5705
//preprocessing for nodes to delete.
5706
//get node property modes to interpolate
5707
var nodeModes = ('node-property' in extraModes)
5708
&& $.map($.splat(extraModes['node-property']),
5709
function(n) { return '$' + n; });
5710
viz.graph.eachNode(function(elem) {
5711
var graphNode = graph.getNode(elem.id);
5712
if(!graphNode) {
5713
elem.setData('alpha', 1);
5714
elem.setData('alpha', 1, 'start');
5715
elem.setData('alpha', 0, 'end');
5716
elem.ignore = true;
5717
} else {
5718
//Update node data information
5719
var graphNodeData = graphNode.data;
5720
for(var prop in graphNodeData) {
5721
if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5722
elem.endData[prop] = graphNodeData[prop];
5723
} else {
5724
elem.data[prop] = graphNodeData[prop];
5725
}
5726
}
5727
}
5728
});
5729
viz.graph.eachNode(function(elem) {
5730
if(elem.ignore) return;
5731
elem.eachAdjacency(function(adj) {
5732
if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5733
var nodeFrom = graph.getNode(adj.nodeFrom.id);
5734
var nodeTo = graph.getNode(adj.nodeTo.id);
5735
if(!nodeFrom.adjacentTo(nodeTo)) {
5736
var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5737
fadeEdges = true;
5738
adj.setData('alpha', 1);
5739
adj.setData('alpha', 1, 'start');
5740
adj.setData('alpha', 0, 'end');
5741
}
5742
});
5743
});
5744
//preprocessing for adding nodes.
5745
var fadeEdges = this.preprocessSum(graph);
5746
5747
var modes = !fadeEdges? ['node-property:alpha'] :
5748
['node-property:alpha',
5749
'edge-property:alpha'];
5750
//Append extra node-property animations (if any)
5751
modes[0] = modes[0] + (('node-property' in extraModes)?
5752
(':' + $.splat(extraModes['node-property']).join(':')) : '');
5753
//Append extra edge-property animations (if any)
5754
modes[1] = (modes[1] || 'edge-property:alpha') + (('edge-property' in extraModes)?
5755
(':' + $.splat(extraModes['edge-property']).join(':')) : '');
5756
//Add label-property animations (if any)
5757
if('label-property' in extraModes) {
5758
modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5759
}
5760
//only use reposition if its implemented.
5761
if (viz.reposition) {
5762
viz.reposition();
5763
} else {
5764
viz.compute('end');
5765
}
5766
viz.graph.eachNode(function(elem) {
5767
if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5768
elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5769
}
5770
});
5771
viz.fx.animate($.merge(options, {
5772
modes: [extraModes.position || 'polar'].concat(modes),
5773
onComplete: function() {
5774
viz.graph.eachNode(function(elem) {
5775
if(elem.ignore) viz.graph.removeNode(elem.id);
5776
});
5777
viz.graph.eachNode(function(elem) {
5778
elem.eachAdjacency(function(adj) {
5779
if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5780
});
5781
});
5782
options.onComplete();
5783
}
5784
}));
5785
break;
5786
5787
default:;
5788
}
5789
},
5790
5791
5792
/*
5793
Method: contract
5794
5795
Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5796
5797
Parameters:
5798
5799
node - (object) A <Graph.Node>.
5800
opt - (object) An object containing options described below
5801
type - (string) Whether to 'replot' or 'animate' the contraction.
5802
5803
There are also a number of Animation options. For more information see <Options.Fx>.
5804
5805
Example:
5806
(start code js)
5807
var viz = new $jit.Viz(options);
5808
viz.op.contract(node, {
5809
type: 'animate',
5810
duration: 1000,
5811
hideLabels: true,
5812
transition: $jit.Trans.Quart.easeOut
5813
});
5814
(end code)
5815
5816
*/
5817
contract: function(node, opt) {
5818
var viz = this.viz;
5819
if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5820
opt = $.merge(this.options, viz.config, opt || {}, {
5821
'modes': ['node-property:alpha:span', 'linear']
5822
});
5823
node.collapsed = true;
5824
(function subn(n) {
5825
n.eachSubnode(function(ch) {
5826
ch.ignore = true;
5827
ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5828
subn(ch);
5829
});
5830
})(node);
5831
if(opt.type == 'animate') {
5832
viz.compute('end');
5833
if(viz.rotated) {
5834
viz.rotate(viz.rotated, 'none', {
5835
'property':'end'
5836
});
5837
}
5838
(function subn(n) {
5839
n.eachSubnode(function(ch) {
5840
ch.setPos(node.getPos('end'), 'end');
5841
subn(ch);
5842
});
5843
})(node);
5844
viz.fx.animate(opt);
5845
} else if(opt.type == 'replot'){
5846
viz.refresh();
5847
}
5848
},
5849
5850
/*
5851
Method: expand
5852
5853
Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5854
5855
Parameters:
5856
5857
node - (object) A <Graph.Node>.
5858
opt - (object) An object containing options described below
5859
type - (string) Whether to 'replot' or 'animate'.
5860
5861
There are also a number of Animation options. For more information see <Options.Fx>.
5862
5863
Example:
5864
(start code js)
5865
var viz = new $jit.Viz(options);
5866
viz.op.expand(node, {
5867
type: 'animate',
5868
duration: 1000,
5869
hideLabels: true,
5870
transition: $jit.Trans.Quart.easeOut
5871
});
5872
(end code)
5873
5874
*/
5875
expand: function(node, opt) {
5876
if(!('collapsed' in node)) return;
5877
var viz = this.viz;
5878
opt = $.merge(this.options, viz.config, opt || {}, {
5879
'modes': ['node-property:alpha:span', 'linear']
5880
});
5881
delete node.collapsed;
5882
(function subn(n) {
5883
n.eachSubnode(function(ch) {
5884
delete ch.ignore;
5885
ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5886
subn(ch);
5887
});
5888
})(node);
5889
if(opt.type == 'animate') {
5890
viz.compute('end');
5891
if(viz.rotated) {
5892
viz.rotate(viz.rotated, 'none', {
5893
'property':'end'
5894
});
5895
}
5896
viz.fx.animate(opt);
5897
} else if(opt.type == 'replot'){
5898
viz.refresh();
5899
}
5900
},
5901
5902
preprocessSum: function(graph) {
5903
var viz = this.viz;
5904
graph.eachNode(function(elem) {
5905
if(!viz.graph.hasNode(elem.id)) {
5906
viz.graph.addNode(elem);
5907
var n = viz.graph.getNode(elem.id);
5908
n.setData('alpha', 0);
5909
n.setData('alpha', 0, 'start');
5910
n.setData('alpha', 1, 'end');
5911
}
5912
});
5913
var fadeEdges = false;
5914
graph.eachNode(function(elem) {
5915
elem.eachAdjacency(function(adj) {
5916
var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
5917
var nodeTo = viz.graph.getNode(adj.nodeTo.id);
5918
if(!nodeFrom.adjacentTo(nodeTo)) {
5919
var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
5920
if(nodeFrom.startAlpha == nodeFrom.endAlpha
5921
&& nodeTo.startAlpha == nodeTo.endAlpha) {
5922
fadeEdges = true;
5923
adj.setData('alpha', 0);
5924
adj.setData('alpha', 0, 'start');
5925
adj.setData('alpha', 1, 'end');
5926
}
5927
}
5928
});
5929
});
5930
return fadeEdges;
5931
}
5932
};
5933
5934
5935
5936
/*
5937
File: Helpers.js
5938
5939
Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
5940
Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
5941
position is over the rendered shape.
5942
5943
Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
5944
*this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
5945
5946
Example:
5947
(start code js)
5948
//implement a new node type
5949
$jit.Viz.Plot.NodeTypes.implement({
5950
'customNodeType': {
5951
'render': function(node, canvas) {
5952
this.nodeHelper.circle.render ...
5953
},
5954
'contains': function(node, pos) {
5955
this.nodeHelper.circle.contains ...
5956
}
5957
}
5958
});
5959
//implement an edge type
5960
$jit.Viz.Plot.EdgeTypes.implement({
5961
'customNodeType': {
5962
'render': function(node, canvas) {
5963
this.edgeHelper.circle.render ...
5964
},
5965
//optional
5966
'contains': function(node, pos) {
5967
this.edgeHelper.circle.contains ...
5968
}
5969
}
5970
});
5971
(end code)
5972
5973
*/
5974
5975
/*
5976
Object: NodeHelper
5977
5978
Contains rendering and other type of primitives for simple shapes.
5979
*/
5980
var NodeHelper = {
5981
'none': {
5982
'render': $.empty,
5983
'contains': $.lambda(false)
5984
},
5985
/*
5986
Object: NodeHelper.circle
5987
*/
5988
'circle': {
5989
/*
5990
Method: render
5991
5992
Renders a circle into the canvas.
5993
5994
Parameters:
5995
5996
type - (string) Possible options are 'fill' or 'stroke'.
5997
pos - (object) An *x*, *y* object with the position of the center of the circle.
5998
radius - (number) The radius of the circle to be rendered.
5999
canvas - (object) A <Canvas> instance.
6000
6001
Example:
6002
(start code js)
6003
NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6004
(end code)
6005
*/
6006
'render': function(type, pos, radius, canvas){
6007
var ctx = canvas.getCtx();
6008
ctx.beginPath();
6009
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6010
ctx.closePath();
6011
ctx[type]();
6012
},
6013
/*
6014
Method: contains
6015
6016
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6017
6018
Parameters:
6019
6020
npos - (object) An *x*, *y* object with the <Graph.Node> position.
6021
pos - (object) An *x*, *y* object with the position to check.
6022
radius - (number) The radius of the rendered circle.
6023
6024
Example:
6025
(start code js)
6026
NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6027
(end code)
6028
*/
6029
'contains': function(npos, pos, radius){
6030
var diffx = npos.x - pos.x,
6031
diffy = npos.y - pos.y,
6032
diff = diffx * diffx + diffy * diffy;
6033
return diff <= radius * radius;
6034
}
6035
},
6036
/*
6037
Object: NodeHelper.ellipse
6038
*/
6039
'ellipse': {
6040
/*
6041
Method: render
6042
6043
Renders an ellipse into the canvas.
6044
6045
Parameters:
6046
6047
type - (string) Possible options are 'fill' or 'stroke'.
6048
pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6049
width - (number) The width of the ellipse.
6050
height - (number) The height of the ellipse.
6051
canvas - (object) A <Canvas> instance.
6052
6053
Example:
6054
(start code js)
6055
NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6056
(end code)
6057
*/
6058
'render': function(type, pos, width, height, canvas){
6059
var ctx = canvas.getCtx(),
6060
scalex = 1,
6061
scaley = 1,
6062
scaleposx = 1,
6063
scaleposy = 1,
6064
radius = 0;
6065
6066
if (width > height) {
6067
radius = width / 2;
6068
scaley = height / width;
6069
scaleposy = width / height;
6070
} else {
6071
radius = height / 2;
6072
scalex = width / height;
6073
scaleposx = height / width;
6074
}
6075
6076
ctx.save();
6077
ctx.scale(scalex, scaley);
6078
ctx.beginPath();
6079
ctx.arc(pos.x * scaleposx, pos.y * scaleposy, radius, 0, Math.PI * 2, true);
6080
ctx.closePath();
6081
ctx[type]();
6082
ctx.restore();
6083
},
6084
/*
6085
Method: contains
6086
6087
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6088
6089
Parameters:
6090
6091
npos - (object) An *x*, *y* object with the <Graph.Node> position.
6092
pos - (object) An *x*, *y* object with the position to check.
6093
width - (number) The width of the rendered ellipse.
6094
height - (number) The height of the rendered ellipse.
6095
6096
Example:
6097
(start code js)
6098
NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6099
(end code)
6100
*/
6101
'contains': function(npos, pos, width, height){
6102
var radius = 0,
6103
scalex = 1,
6104
scaley = 1,
6105
diffx = 0,
6106
diffy = 0,
6107
diff = 0;
6108
6109
if (width > height) {
6110
radius = width / 2;
6111
scaley = height / width;
6112
} else {
6113
radius = height / 2;
6114
scalex = width / height;
6115
}
6116
6117
diffx = (npos.x - pos.x) * (1 / scalex);
6118
diffy = (npos.y - pos.y) * (1 / scaley);
6119
diff = diffx * diffx + diffy * diffy;
6120
return diff <= radius * radius;
6121
}
6122
},
6123
/*
6124
Object: NodeHelper.square
6125
*/
6126
'square': {
6127
/*
6128
Method: render
6129
6130
Renders a square into the canvas.
6131
6132
Parameters:
6133
6134
type - (string) Possible options are 'fill' or 'stroke'.
6135
pos - (object) An *x*, *y* object with the position of the center of the square.
6136
dim - (number) The radius (or half-diameter) of the square.
6137
canvas - (object) A <Canvas> instance.
6138
6139
Example:
6140
(start code js)
6141
NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6142
(end code)
6143
*/
6144
'render': function(type, pos, dim, canvas){
6145
canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6146
},
6147
/*
6148
Method: contains
6149
6150
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6151
6152
Parameters:
6153
6154
npos - (object) An *x*, *y* object with the <Graph.Node> position.
6155
pos - (object) An *x*, *y* object with the position to check.
6156
dim - (number) The radius (or half-diameter) of the square.
6157
6158
Example:
6159
(start code js)
6160
NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6161
(end code)
6162
*/
6163
'contains': function(npos, pos, dim){
6164
return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6165
}
6166
},
6167
/*
6168
Object: NodeHelper.rectangle
6169
*/
6170
'rectangle': {
6171
/*
6172
Method: render
6173
6174
Renders a rectangle into the canvas.
6175
6176
Parameters:
6177
6178
type - (string) Possible options are 'fill' or 'stroke'.
6179
pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6180
width - (number) The width of the rectangle.
6181
height - (number) The height of the rectangle.
6182
canvas - (object) A <Canvas> instance.
6183
6184
Example:
6185
(start code js)
6186
NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6187
(end code)
6188
*/
6189
'render': function(type, pos, width, height, canvas){
6190
canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6191
width, height);
6192
},
6193
/*
6194
Method: contains
6195
6196
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6197
6198
Parameters:
6199
6200
npos - (object) An *x*, *y* object with the <Graph.Node> position.
6201
pos - (object) An *x*, *y* object with the position to check.
6202
width - (number) The width of the rendered rectangle.
6203
height - (number) The height of the rendered rectangle.
6204
6205
Example:
6206
(start code js)
6207
NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6208
(end code)
6209
*/
6210
'contains': function(npos, pos, width, height){
6211
return Math.abs(pos.x - npos.x) <= width / 2
6212
&& Math.abs(pos.y - npos.y) <= height / 2;
6213
}
6214
},
6215
/*
6216
Object: NodeHelper.triangle
6217
*/
6218
'triangle': {
6219
/*
6220
Method: render
6221
6222
Renders a triangle into the canvas.
6223
6224
Parameters:
6225
6226
type - (string) Possible options are 'fill' or 'stroke'.
6227
pos - (object) An *x*, *y* object with the position of the center of the triangle.
6228
dim - (number) Half the base and half the height of the triangle.
6229
canvas - (object) A <Canvas> instance.
6230
6231
Example:
6232
(start code js)
6233
NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6234
(end code)
6235
*/
6236
'render': function(type, pos, dim, canvas){
6237
var ctx = canvas.getCtx(),
6238
c1x = pos.x,
6239
c1y = pos.y - dim,
6240
c2x = c1x - dim,
6241
c2y = pos.y + dim,
6242
c3x = c1x + dim,
6243
c3y = c2y;
6244
ctx.beginPath();
6245
ctx.moveTo(c1x, c1y);
6246
ctx.lineTo(c2x, c2y);
6247
ctx.lineTo(c3x, c3y);
6248
ctx.closePath();
6249
ctx[type]();
6250
},
6251
/*
6252
Method: contains
6253
6254
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6255
6256
Parameters:
6257
6258
npos - (object) An *x*, *y* object with the <Graph.Node> position.
6259
pos - (object) An *x*, *y* object with the position to check.
6260
dim - (number) Half the base and half the height of the triangle.
6261
6262
Example:
6263
(start code js)
6264
NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6265
(end code)
6266
*/
6267
'contains': function(npos, pos, dim) {
6268
return NodeHelper.circle.contains(npos, pos, dim);
6269
}
6270
},
6271
/*
6272
Object: NodeHelper.star
6273
*/
6274
'star': {
6275
/*
6276
Method: render
6277
6278
Renders a star (concave decagon) into the canvas.
6279
6280
Parameters:
6281
6282
type - (string) Possible options are 'fill' or 'stroke'.
6283
pos - (object) An *x*, *y* object with the position of the center of the star.
6284
dim - (number) The length of a side of a concave decagon.
6285
canvas - (object) A <Canvas> instance.
6286
6287
Example:
6288
(start code js)
6289
NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6290
(end code)
6291
*/
6292
'render': function(type, pos, dim, canvas){
6293
var ctx = canvas.getCtx(),
6294
pi5 = Math.PI / 5;
6295
ctx.save();
6296
ctx.translate(pos.x, pos.y);
6297
ctx.beginPath();
6298
ctx.moveTo(dim, 0);
6299
for (var i = 0; i < 9; i++) {
6300
ctx.rotate(pi5);
6301
if (i % 2 == 0) {
6302
ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6303
} else {
6304
ctx.lineTo(dim, 0);
6305
}
6306
}
6307
ctx.closePath();
6308
ctx[type]();
6309
ctx.restore();
6310
},
6311
/*
6312
Method: contains
6313
6314
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6315
6316
Parameters:
6317
6318
npos - (object) An *x*, *y* object with the <Graph.Node> position.
6319
pos - (object) An *x*, *y* object with the position to check.
6320
dim - (number) The length of a side of a concave decagon.
6321
6322
Example:
6323
(start code js)
6324
NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6325
(end code)
6326
*/
6327
'contains': function(npos, pos, dim) {
6328
return NodeHelper.circle.contains(npos, pos, dim);
6329
}
6330
}
6331
};
6332
6333
/*
6334
Object: EdgeHelper
6335
6336
Contains rendering primitives for simple edge shapes.
6337
*/
6338
var EdgeHelper = {
6339
/*
6340
Object: EdgeHelper.line
6341
*/
6342
'line': {
6343
/*
6344
Method: render
6345
6346
Renders a line into the canvas.
6347
6348
Parameters:
6349
6350
from - (object) An *x*, *y* object with the starting position of the line.
6351
to - (object) An *x*, *y* object with the ending position of the line.
6352
canvas - (object) A <Canvas> instance.
6353
6354
Example:
6355
(start code js)
6356
EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6357
(end code)
6358
*/
6359
'render': function(from, to, canvas){
6360
var ctx = canvas.getCtx();
6361
ctx.beginPath();
6362
ctx.moveTo(from.x, from.y);
6363
ctx.lineTo(to.x, to.y);
6364
ctx.stroke();
6365
},
6366
/*
6367
Method: contains
6368
6369
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6370
6371
Parameters:
6372
6373
posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6374
posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6375
pos - (object) An *x*, *y* object with the position to check.
6376
epsilon - (number) The dimension of the shape.
6377
6378
Example:
6379
(start code js)
6380
EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6381
(end code)
6382
*/
6383
'contains': function(posFrom, posTo, pos, epsilon) {
6384
var min = Math.min,
6385
max = Math.max,
6386
minPosX = min(posFrom.x, posTo.x),
6387
maxPosX = max(posFrom.x, posTo.x),
6388
minPosY = min(posFrom.y, posTo.y),
6389
maxPosY = max(posFrom.y, posTo.y);
6390
6391
if(pos.x >= minPosX && pos.x <= maxPosX
6392
&& pos.y >= minPosY && pos.y <= maxPosY) {
6393
if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6394
return true;
6395
}
6396
var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6397
return Math.abs(dist - pos.y) <= epsilon;
6398
}
6399
return false;
6400
}
6401
},
6402
/*
6403
Object: EdgeHelper.arrow
6404
*/
6405
'arrow': {
6406
/*
6407
Method: render
6408
6409
Renders an arrow into the canvas.
6410
6411
Parameters:
6412
6413
from - (object) An *x*, *y* object with the starting position of the arrow.
6414
to - (object) An *x*, *y* object with the ending position of the arrow.
6415
dim - (number) The dimension of the arrow.
6416
swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6417
canvas - (object) A <Canvas> instance.
6418
6419
Example:
6420
(start code js)
6421
EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6422
(end code)
6423
*/
6424
'render': function(from, to, dim, swap, canvas){
6425
var ctx = canvas.getCtx();
6426
// invert edge direction
6427
if (swap) {
6428
var tmp = from;
6429
from = to;
6430
to = tmp;
6431
}
6432
var vect = new Complex(to.x - from.x, to.y - from.y);
6433
vect.$scale(dim / vect.norm());
6434
var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6435
normal = new Complex(-vect.y / 2, vect.x / 2),
6436
v1 = intermediatePoint.add(normal),
6437
v2 = intermediatePoint.$add(normal.$scale(-1));
6438
6439
ctx.beginPath();
6440
ctx.moveTo(from.x, from.y);
6441
ctx.lineTo(to.x, to.y);
6442
ctx.stroke();
6443
ctx.beginPath();
6444
ctx.moveTo(v1.x, v1.y);
6445
ctx.lineTo(v2.x, v2.y);
6446
ctx.lineTo(to.x, to.y);
6447
ctx.closePath();
6448
ctx.fill();
6449
},
6450
/*
6451
Method: contains
6452
6453
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6454
6455
Parameters:
6456
6457
posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6458
posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6459
pos - (object) An *x*, *y* object with the position to check.
6460
epsilon - (number) The dimension of the shape.
6461
6462
Example:
6463
(start code js)
6464
EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6465
(end code)
6466
*/
6467
'contains': function(posFrom, posTo, pos, epsilon) {
6468
return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6469
}
6470
},
6471
/*
6472
Object: EdgeHelper.hyperline
6473
*/
6474
'hyperline': {
6475
/*
6476
Method: render
6477
6478
Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6479
6480
Parameters:
6481
6482
from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6483
to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6484
r - (number) The scaling factor.
6485
canvas - (object) A <Canvas> instance.
6486
6487
Example:
6488
(start code js)
6489
EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6490
(end code)
6491
*/
6492
'render': function(from, to, r, canvas){
6493
var ctx = canvas.getCtx();
6494
var centerOfCircle = computeArcThroughTwoPoints(from, to);
6495
if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6496
|| centerOfCircle.ratio < 0) {
6497
ctx.beginPath();
6498
ctx.moveTo(from.x * r, from.y * r);
6499
ctx.lineTo(to.x * r, to.y * r);
6500
ctx.stroke();
6501
} else {
6502
var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6503
- centerOfCircle.x);
6504
var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6505
- centerOfCircle.x);
6506
var sense = sense(angleBegin, angleEnd);
6507
ctx.beginPath();
6508
ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6509
* r, angleBegin, angleEnd, sense);
6510
ctx.stroke();
6511
}
6512
/*
6513
Calculates the arc parameters through two points.
6514
6515
More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6516
6517
Parameters:
6518
6519
p1 - A <Complex> instance.
6520
p2 - A <Complex> instance.
6521
scale - The Disk's diameter.
6522
6523
Returns:
6524
6525
An object containing some arc properties.
6526
*/
6527
function computeArcThroughTwoPoints(p1, p2){
6528
var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6529
var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6530
// Fall back to a straight line
6531
if (aDen == 0)
6532
return {
6533
x: 0,
6534
y: 0,
6535
ratio: -1
6536
};
6537
6538
var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6539
var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6540
var x = -a / 2;
6541
var y = -b / 2;
6542
var squaredRatio = (a * a + b * b) / 4 - 1;
6543
// Fall back to a straight line
6544
if (squaredRatio < 0)
6545
return {
6546
x: 0,
6547
y: 0,
6548
ratio: -1
6549
};
6550
var ratio = Math.sqrt(squaredRatio);
6551
var out = {
6552
x: x,
6553
y: y,
6554
ratio: ratio > 1000? -1 : ratio,
6555
a: a,
6556
b: b
6557
};
6558
6559
return out;
6560
}
6561
/*
6562
Sets angle direction to clockwise (true) or counterclockwise (false).
6563
6564
Parameters:
6565
6566
angleBegin - Starting angle for drawing the arc.
6567
angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6568
6569
Returns:
6570
6571
A Boolean instance describing the sense for drawing the HyperLine.
6572
*/
6573
function sense(angleBegin, angleEnd){
6574
return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6575
: true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6576
}
6577
},
6578
/*
6579
Method: contains
6580
6581
Not Implemented
6582
6583
Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6584
6585
Parameters:
6586
6587
posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6588
posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6589
pos - (object) An *x*, *y* object with the position to check.
6590
epsilon - (number) The dimension of the shape.
6591
6592
Example:
6593
(start code js)
6594
EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6595
(end code)
6596
*/
6597
'contains': $.lambda(false)
6598
}
6599
};
6600
6601
6602
/*
6603
* File: Graph.Plot.js
6604
*/
6605
6606
/*
6607
Object: Graph.Plot
6608
6609
<Graph> rendering and animation methods.
6610
6611
Properties:
6612
6613
nodeHelper - <NodeHelper> object.
6614
edgeHelper - <EdgeHelper> object.
6615
*/
6616
Graph.Plot = {
6617
//Default initializer
6618
initialize: function(viz, klass){
6619
this.viz = viz;
6620
this.config = viz.config;
6621
this.node = viz.config.Node;
6622
this.edge = viz.config.Edge;
6623
this.animation = new Animation;
6624
this.nodeTypes = new klass.Plot.NodeTypes;
6625
this.edgeTypes = new klass.Plot.EdgeTypes;
6626
this.labels = viz.labels;
6627
},
6628
6629
//Add helpers
6630
nodeHelper: NodeHelper,
6631
edgeHelper: EdgeHelper,
6632
6633
Interpolator: {
6634
//node/edge property parsers
6635
'map': {
6636
'border': 'color',
6637
'color': 'color',
6638
'width': 'number',
6639
'height': 'number',
6640
'dim': 'number',
6641
'alpha': 'number',
6642
'lineWidth': 'number',
6643
'angularWidth':'number',
6644
'span':'number',
6645
'valueArray':'array-number',
6646
'dimArray':'array-number'
6647
//'colorArray':'array-color'
6648
},
6649
6650
//canvas specific parsers
6651
'canvas': {
6652
'globalAlpha': 'number',
6653
'fillStyle': 'color',
6654
'strokeStyle': 'color',
6655
'lineWidth': 'number',
6656
'shadowBlur': 'number',
6657
'shadowColor': 'color',
6658
'shadowOffsetX': 'number',
6659
'shadowOffsetY': 'number',
6660
'miterLimit': 'number'
6661
},
6662
6663
//label parsers
6664
'label': {
6665
'size': 'number',
6666
'color': 'color'
6667
},
6668
6669
//Number interpolator
6670
'compute': function(from, to, delta) {
6671
return from + (to - from) * delta;
6672
},
6673
6674
//Position interpolators
6675
'moebius': function(elem, props, delta, vector) {
6676
var v = vector.scale(-delta);
6677
if(v.norm() < 1) {
6678
var x = v.x, y = v.y;
6679
var ans = elem.startPos
6680
.getc().moebiusTransformation(v);
6681
elem.pos.setc(ans.x, ans.y);
6682
v.x = x; v.y = y;
6683
}
6684
},
6685
6686
'linear': function(elem, props, delta) {
6687
var from = elem.startPos.getc(true);
6688
var to = elem.endPos.getc(true);
6689
elem.pos.setc(this.compute(from.x, to.x, delta),
6690
this.compute(from.y, to.y, delta));
6691
},
6692
6693
'polar': function(elem, props, delta) {
6694
var from = elem.startPos.getp(true);
6695
var to = elem.endPos.getp();
6696
var ans = to.interpolate(from, delta);
6697
elem.pos.setp(ans.theta, ans.rho);
6698
},
6699
6700
//Graph's Node/Edge interpolators
6701
'number': function(elem, prop, delta, getter, setter) {
6702
var from = elem[getter](prop, 'start');
6703
var to = elem[getter](prop, 'end');
6704
elem[setter](prop, this.compute(from, to, delta));
6705
},
6706
6707
'color': function(elem, prop, delta, getter, setter) {
6708
var from = $.hexToRgb(elem[getter](prop, 'start'));
6709
var to = $.hexToRgb(elem[getter](prop, 'end'));
6710
var comp = this.compute;
6711
var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6712
parseInt(comp(from[1], to[1], delta)),
6713
parseInt(comp(from[2], to[2], delta))]);
6714
6715
elem[setter](prop, val);
6716
},
6717
6718
'array-number': function(elem, prop, delta, getter, setter) {
6719
var from = elem[getter](prop, 'start'),
6720
to = elem[getter](prop, 'end'),
6721
cur = [];
6722
for(var i=0, l=from.length; i<l; i++) {
6723
var fromi = from[i], toi = to[i];
6724
if(fromi.length) {
6725
for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6726
curi.push(this.compute(fromi[j], toi[j], delta));
6727
}
6728
cur.push(curi);
6729
} else {
6730
cur.push(this.compute(fromi, toi, delta));
6731
}
6732
}
6733
elem[setter](prop, cur);
6734
},
6735
6736
'node': function(elem, props, delta, map, getter, setter) {
6737
map = this[map];
6738
if(props) {
6739
var len = props.length;
6740
for(var i=0; i<len; i++) {
6741
var pi = props[i];
6742
this[map[pi]](elem, pi, delta, getter, setter);
6743
}
6744
} else {
6745
for(var pi in map) {
6746
this[map[pi]](elem, pi, delta, getter, setter);
6747
}
6748
}
6749
},
6750
6751
'edge': function(elem, props, delta, mapKey, getter, setter) {
6752
var adjs = elem.adjacencies;
6753
for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6754
},
6755
6756
'node-property': function(elem, props, delta) {
6757
this['node'](elem, props, delta, 'map', 'getData', 'setData');
6758
},
6759
6760
'edge-property': function(elem, props, delta) {
6761
this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6762
},
6763
6764
'label-property': function(elem, props, delta) {
6765
this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6766
},
6767
6768
'node-style': function(elem, props, delta) {
6769
this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6770
},
6771
6772
'edge-style': function(elem, props, delta) {
6773
this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6774
}
6775
},
6776
6777
6778
/*
6779
sequence
6780
6781
Iteratively performs an action while refreshing the state of the visualization.
6782
6783
Parameters:
6784
6785
options - (object) An object containing some sequence options described below
6786
condition - (function) A function returning a boolean instance in order to stop iterations.
6787
step - (function) A function to execute on each step of the iteration.
6788
onComplete - (function) A function to execute when the sequence finishes.
6789
duration - (number) Duration (in milliseconds) of each step.
6790
6791
Example:
6792
(start code js)
6793
var rg = new $jit.RGraph(options);
6794
var i = 0;
6795
rg.fx.sequence({
6796
condition: function() {
6797
return i == 10;
6798
},
6799
step: function() {
6800
alert(i++);
6801
},
6802
onComplete: function() {
6803
alert('done!');
6804
}
6805
});
6806
(end code)
6807
6808
*/
6809
sequence: function(options) {
6810
var that = this;
6811
options = $.merge({
6812
condition: $.lambda(false),
6813
step: $.empty,
6814
onComplete: $.empty,
6815
duration: 200
6816
}, options || {});
6817
6818
var interval = setInterval(function() {
6819
if(options.condition()) {
6820
options.step();
6821
} else {
6822
clearInterval(interval);
6823
options.onComplete();
6824
}
6825
that.viz.refresh(true);
6826
}, options.duration);
6827
},
6828
6829
/*
6830
prepare
6831
6832
Prepare graph position and other attribute values before performing an Animation.
6833
This method is used internally by the Toolkit.
6834
6835
See also:
6836
6837
<Animation>, <Graph.Plot.animate>
6838
6839
*/
6840
prepare: function(modes) {
6841
var graph = this.viz.graph,
6842
accessors = {
6843
'node-property': {
6844
'getter': 'getData',
6845
'setter': 'setData'
6846
},
6847
'edge-property': {
6848
'getter': 'getData',
6849
'setter': 'setData'
6850
},
6851
'node-style': {
6852
'getter': 'getCanvasStyle',
6853
'setter': 'setCanvasStyle'
6854
},
6855
'edge-style': {
6856
'getter': 'getCanvasStyle',
6857
'setter': 'setCanvasStyle'
6858
}
6859
};
6860
6861
//parse modes
6862
var m = {};
6863
if($.type(modes) == 'array') {
6864
for(var i=0, len=modes.length; i < len; i++) {
6865
var elems = modes[i].split(':');
6866
m[elems.shift()] = elems;
6867
}
6868
} else {
6869
for(var p in modes) {
6870
if(p == 'position') {
6871
m[modes.position] = [];
6872
} else {
6873
m[p] = $.splat(modes[p]);
6874
}
6875
}
6876
}
6877
6878
graph.eachNode(function(node) {
6879
node.startPos.set(node.pos);
6880
$.each(['node-property', 'node-style'], function(p) {
6881
if(p in m) {
6882
var prop = m[p];
6883
for(var i=0, l=prop.length; i < l; i++) {
6884
node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6885
}
6886
}
6887
});
6888
$.each(['edge-property', 'edge-style'], function(p) {
6889
if(p in m) {
6890
var prop = m[p];
6891
node.eachAdjacency(function(adj) {
6892
for(var i=0, l=prop.length; i < l; i++) {
6893
adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6894
}
6895
});
6896
}
6897
});
6898
});
6899
return m;
6900
},
6901
6902
/*
6903
Method: animate
6904
6905
Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6906
6907
Parameters:
6908
6909
opt - (object) Animation options. The object properties are described below
6910
duration - (optional) Described in <Options.Fx>.
6911
fps - (optional) Described in <Options.Fx>.
6912
hideLabels - (optional|boolean) Whether to hide labels during the animation.
6913
modes - (required|object) An object with animation modes (described below).
6914
6915
Animation modes:
6916
6917
Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6918
They are represented by an object that has as keys main categories of properties to animate and as values a list
6919
of these specific properties. The properties are described below
6920
6921
position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6922
node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6923
edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6924
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.
6925
node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6926
edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6927
6928
Example:
6929
(start code js)
6930
var viz = new $jit.Viz(options);
6931
//...tweak some Data, CanvasStyles or LabelData properties...
6932
viz.fx.animate({
6933
modes: {
6934
'position': 'linear',
6935
'node-property': ['width', 'height'],
6936
'node-style': 'shadowColor',
6937
'label-property': 'size'
6938
},
6939
hideLabels: false
6940
});
6941
//...can also be written like this...
6942
viz.fx.animate({
6943
modes: ['linear',
6944
'node-property:width:height',
6945
'node-style:shadowColor',
6946
'label-property:size'],
6947
hideLabels: false
6948
});
6949
(end code)
6950
*/
6951
animate: function(opt, versor) {
6952
opt = $.merge(this.viz.config, opt || {});
6953
var that = this,
6954
viz = this.viz,
6955
graph = viz.graph,
6956
interp = this.Interpolator,
6957
animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
6958
//prepare graph values
6959
var m = this.prepare(opt.modes);
6960
6961
//animate
6962
if(opt.hideLabels) this.labels.hideLabels(true);
6963
animation.setOptions($.extend(opt, {
6964
$animating: false,
6965
compute: function(delta) {
6966
graph.eachNode(function(node) {
6967
for(var p in m) {
6968
interp[p](node, m[p], delta, versor);
6969
}
6970
});
6971
that.plot(opt, this.$animating, delta);
6972
this.$animating = true;
6973
},
6974
complete: function() {
6975
if(opt.hideLabels) that.labels.hideLabels(false);
6976
that.plot(opt);
6977
opt.onComplete();
6978
//This shouldn't be here!
6979
//opt.onAfterCompute();
6980
}
6981
})).start();
6982
},
6983
6984
/*
6985
nodeFx
6986
6987
Apply animation to node properties like color, width, height, dim, etc.
6988
6989
Parameters:
6990
6991
options - Animation options. This object properties is described below
6992
elements - The Elements to be transformed. This is an object that has a properties
6993
6994
(start code js)
6995
'elements': {
6996
//can also be an array of ids
6997
'id': 'id-of-node-to-transform',
6998
//properties to be modified. All properties are optional.
6999
'properties': {
7000
'color': '#ccc', //some color
7001
'width': 10, //some width
7002
'height': 10, //some height
7003
'dim': 20, //some dim
7004
'lineWidth': 10 //some line width
7005
}
7006
}
7007
(end code)
7008
7009
- _reposition_ Whether to recalculate positions and add a motion animation.
7010
This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7011
7012
- _onComplete_ A method that is called when the animation completes.
7013
7014
...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7015
7016
Example:
7017
(start code js)
7018
var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7019
rg.fx.nodeFx({
7020
'elements': {
7021
'id':'mynodeid',
7022
'properties': {
7023
'color':'#ccf'
7024
},
7025
'transition': Trans.Quart.easeOut
7026
}
7027
});
7028
(end code)
7029
*/
7030
nodeFx: function(opt) {
7031
var viz = this.viz,
7032
graph = viz.graph,
7033
animation = this.nodeFxAnimation,
7034
options = $.merge(this.viz.config, {
7035
'elements': {
7036
'id': false,
7037
'properties': {}
7038
},
7039
'reposition': false
7040
});
7041
opt = $.merge(options, opt || {}, {
7042
onBeforeCompute: $.empty,
7043
onAfterCompute: $.empty
7044
});
7045
//check if an animation is running
7046
animation.stopTimer();
7047
var props = opt.elements.properties;
7048
//set end values for nodes
7049
if(!opt.elements.id) {
7050
graph.eachNode(function(n) {
7051
for(var prop in props) {
7052
n.setData(prop, props[prop], 'end');
7053
}
7054
});
7055
} else {
7056
var ids = $.splat(opt.elements.id);
7057
$.each(ids, function(id) {
7058
var n = graph.getNode(id);
7059
if(n) {
7060
for(var prop in props) {
7061
n.setData(prop, props[prop], 'end');
7062
}
7063
}
7064
});
7065
}
7066
//get keys
7067
var propnames = [];
7068
for(var prop in props) propnames.push(prop);
7069
//add node properties modes
7070
var modes = ['node-property:' + propnames.join(':')];
7071
//set new node positions
7072
if(opt.reposition) {
7073
modes.push('linear');
7074
viz.compute('end');
7075
}
7076
//animate
7077
this.animate($.merge(opt, {
7078
modes: modes,
7079
type: 'nodefx'
7080
}));
7081
},
7082
7083
7084
/*
7085
Method: plot
7086
7087
Plots a <Graph>.
7088
7089
Parameters:
7090
7091
opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7092
7093
Example:
7094
7095
(start code js)
7096
var viz = new $jit.Viz(options);
7097
viz.fx.plot();
7098
(end code)
7099
7100
*/
7101
plot: function(opt, animating) {
7102
var viz = this.viz,
7103
aGraph = viz.graph,
7104
canvas = viz.canvas,
7105
id = viz.root,
7106
that = this,
7107
ctx = canvas.getCtx(),
7108
min = Math.min,
7109
opt = opt || this.viz.controller;
7110
7111
opt.clearCanvas && canvas.clear();
7112
7113
var root = aGraph.getNode(id);
7114
if(!root) return;
7115
7116
var T = !!root.visited;
7117
aGraph.eachNode(function(node) {
7118
var nodeAlpha = node.getData('alpha');
7119
node.eachAdjacency(function(adj) {
7120
var nodeTo = adj.nodeTo;
7121
if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7122
!animating && opt.onBeforePlotLine(adj);
7123
that.plotLine(adj, canvas, animating);
7124
!animating && opt.onAfterPlotLine(adj);
7125
}
7126
});
7127
if(node.drawn) {
7128
!animating && opt.onBeforePlotNode(node);
7129
that.plotNode(node, canvas, animating);
7130
!animating && opt.onAfterPlotNode(node);
7131
}
7132
if(!that.labelsHidden && opt.withLabels) {
7133
if(node.drawn && nodeAlpha >= 0.95) {
7134
that.labels.plotLabel(canvas, node, opt);
7135
} else {
7136
that.labels.hideLabel(node, false);
7137
}
7138
}
7139
node.visited = !T;
7140
});
7141
},
7142
7143
/*
7144
Plots a Subtree.
7145
*/
7146
plotTree: function(node, opt, animating) {
7147
var that = this,
7148
viz = this.viz,
7149
canvas = viz.canvas,
7150
config = this.config,
7151
ctx = canvas.getCtx();
7152
var nodeAlpha = node.getData('alpha');
7153
node.eachSubnode(function(elem) {
7154
if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7155
var adj = node.getAdjacency(elem.id);
7156
!animating && opt.onBeforePlotLine(adj);
7157
that.plotLine(adj, canvas, animating);
7158
!animating && opt.onAfterPlotLine(adj);
7159
that.plotTree(elem, opt, animating);
7160
}
7161
});
7162
if(node.drawn) {
7163
!animating && opt.onBeforePlotNode(node);
7164
this.plotNode(node, canvas, animating);
7165
!animating && opt.onAfterPlotNode(node);
7166
if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7167
this.labels.plotLabel(canvas, node, opt);
7168
else
7169
this.labels.hideLabel(node, false);
7170
} else {
7171
this.labels.hideLabel(node, true);
7172
}
7173
},
7174
7175
/*
7176
Method: plotNode
7177
7178
Plots a <Graph.Node>.
7179
7180
Parameters:
7181
7182
node - (object) A <Graph.Node>.
7183
canvas - (object) A <Canvas> element.
7184
7185
*/
7186
plotNode: function(node, canvas, animating) {
7187
var f = node.getData('type'),
7188
ctxObj = this.node.CanvasStyles;
7189
if(f != 'none') {
7190
var width = node.getData('lineWidth'),
7191
color = node.getData('color'),
7192
alpha = node.getData('alpha'),
7193
ctx = canvas.getCtx();
7194
ctx.save();
7195
ctx.lineWidth = width;
7196
ctx.fillStyle = ctx.strokeStyle = color;
7197
ctx.globalAlpha = alpha;
7198
7199
for(var s in ctxObj) {
7200
ctx[s] = node.getCanvasStyle(s);
7201
}
7202
7203
this.nodeTypes[f].render.call(this, node, canvas, animating);
7204
ctx.restore();
7205
}
7206
},
7207
7208
/*
7209
Method: plotLine
7210
7211
Plots a <Graph.Adjacence>.
7212
7213
Parameters:
7214
7215
adj - (object) A <Graph.Adjacence>.
7216
canvas - (object) A <Canvas> instance.
7217
7218
*/
7219
plotLine: function(adj, canvas, animating) {
7220
var f = adj.getData('type'),
7221
ctxObj = this.edge.CanvasStyles;
7222
if(f != 'none') {
7223
var width = adj.getData('lineWidth'),
7224
color = adj.getData('color'),
7225
ctx = canvas.getCtx(),
7226
nodeFrom = adj.nodeFrom,
7227
nodeTo = adj.nodeTo;
7228
7229
ctx.save();
7230
ctx.lineWidth = width;
7231
ctx.fillStyle = ctx.strokeStyle = color;
7232
ctx.globalAlpha = Math.min(nodeFrom.getData('alpha'),
7233
nodeTo.getData('alpha'),
7234
adj.getData('alpha'));
7235
7236
for(var s in ctxObj) {
7237
ctx[s] = adj.getCanvasStyle(s);
7238
}
7239
7240
this.edgeTypes[f].render.call(this, adj, canvas, animating);
7241
ctx.restore();
7242
}
7243
}
7244
7245
};
7246
7247
/*
7248
Object: Graph.Plot3D
7249
7250
<Graph> 3D rendering and animation methods.
7251
7252
Properties:
7253
7254
nodeHelper - <NodeHelper> object.
7255
edgeHelper - <EdgeHelper> object.
7256
7257
*/
7258
Graph.Plot3D = $.merge(Graph.Plot, {
7259
Interpolator: {
7260
'linear': function(elem, props, delta) {
7261
var from = elem.startPos.getc(true);
7262
var to = elem.endPos.getc(true);
7263
elem.pos.setc(this.compute(from.x, to.x, delta),
7264
this.compute(from.y, to.y, delta),
7265
this.compute(from.z, to.z, delta));
7266
}
7267
},
7268
7269
plotNode: function(node, canvas) {
7270
if(node.getData('type') == 'none') return;
7271
this.plotElement(node, canvas, {
7272
getAlpha: function() {
7273
return node.getData('alpha');
7274
}
7275
});
7276
},
7277
7278
plotLine: function(adj, canvas) {
7279
if(adj.getData('type') == 'none') return;
7280
this.plotElement(adj, canvas, {
7281
getAlpha: function() {
7282
return Math.min(adj.nodeFrom.getData('alpha'),
7283
adj.nodeTo.getData('alpha'),
7284
adj.getData('alpha'));
7285
}
7286
});
7287
},
7288
7289
plotElement: function(elem, canvas, opt) {
7290
var gl = canvas.getCtx(),
7291
viewMatrix = new Matrix4,
7292
lighting = canvas.config.Scene.Lighting,
7293
wcanvas = canvas.canvases[0],
7294
program = wcanvas.program,
7295
camera = wcanvas.camera;
7296
7297
if(!elem.geometry) {
7298
elem.geometry = new O3D[elem.getData('type')];
7299
}
7300
elem.geometry.update(elem);
7301
if(!elem.webGLVertexBuffer) {
7302
var vertices = [],
7303
faces = [],
7304
normals = [],
7305
vertexIndex = 0,
7306
geom = elem.geometry;
7307
7308
for(var i=0, vs=geom.vertices, fs=geom.faces, fsl=fs.length; i<fsl; i++) {
7309
var face = fs[i],
7310
v1 = vs[face.a],
7311
v2 = vs[face.b],
7312
v3 = vs[face.c],
7313
v4 = face.d? vs[face.d] : false,
7314
n = face.normal;
7315
7316
vertices.push(v1.x, v1.y, v1.z);
7317
vertices.push(v2.x, v2.y, v2.z);
7318
vertices.push(v3.x, v3.y, v3.z);
7319
if(v4) vertices.push(v4.x, v4.y, v4.z);
7320
7321
normals.push(n.x, n.y, n.z);
7322
normals.push(n.x, n.y, n.z);
7323
normals.push(n.x, n.y, n.z);
7324
if(v4) normals.push(n.x, n.y, n.z);
7325
7326
faces.push(vertexIndex, vertexIndex +1, vertexIndex +2);
7327
if(v4) {
7328
faces.push(vertexIndex, vertexIndex +2, vertexIndex +3);
7329
vertexIndex += 4;
7330
} else {
7331
vertexIndex += 3;
7332
}
7333
}
7334
//create and store vertex data
7335
elem.webGLVertexBuffer = gl.createBuffer();
7336
gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7337
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
7338
//create and store faces index data
7339
elem.webGLFaceBuffer = gl.createBuffer();
7340
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer);
7341
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(faces), gl.STATIC_DRAW);
7342
elem.webGLFaceCount = faces.length;
7343
//calculate vertex normals and store them
7344
elem.webGLNormalBuffer = gl.createBuffer();
7345
gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7346
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
7347
}
7348
viewMatrix.multiply(camera.matrix, elem.geometry.matrix);
7349
//send matrix data
7350
gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.flatten());
7351
gl.uniformMatrix4fv(program.projectionMatrix, false, camera.projectionMatrix.flatten());
7352
//send normal matrix for lighting
7353
var normalMatrix = Matrix4.makeInvert(viewMatrix);
7354
normalMatrix.$transpose();
7355
gl.uniformMatrix4fv(program.normalMatrix, false, normalMatrix.flatten());
7356
//send color data
7357
var color = $.hexToRgb(elem.getData('color'));
7358
color.push(opt.getAlpha());
7359
gl.uniform4f(program.color, color[0] / 255, color[1] / 255, color[2] / 255, color[3]);
7360
//send lighting data
7361
gl.uniform1i(program.enableLighting, lighting.enable);
7362
if(lighting.enable) {
7363
//set ambient light color
7364
if(lighting.ambient) {
7365
var acolor = lighting.ambient;
7366
gl.uniform3f(program.ambientColor, acolor[0], acolor[1], acolor[2]);
7367
}
7368
//set directional light
7369
if(lighting.directional) {
7370
var dir = lighting.directional,
7371
color = dir.color,
7372
pos = dir.direction,
7373
vd = new Vector3(pos.x, pos.y, pos.z).normalize().$scale(-1);
7374
gl.uniform3f(program.lightingDirection, vd.x, vd.y, vd.z);
7375
gl.uniform3f(program.directionalColor, color[0], color[1], color[2]);
7376
}
7377
}
7378
//send vertices data
7379
gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7380
gl.vertexAttribPointer(program.position, 3, gl.FLOAT, false, 0, 0);
7381
//send normals data
7382
gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7383
gl.vertexAttribPointer(program.normal, 3, gl.FLOAT, false, 0, 0);
7384
//draw!
7385
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer );
7386
gl.drawElements(gl.TRIANGLES, elem.webGLFaceCount, gl.UNSIGNED_SHORT, 0);
7387
}
7388
});
7389
7390
7391
/*
7392
* File: Graph.Label.js
7393
*
7394
*/
7395
7396
/*
7397
Object: Graph.Label
7398
7399
An interface for plotting/hiding/showing labels.
7400
7401
Description:
7402
7403
This is a generic interface for plotting/hiding/showing labels.
7404
The <Graph.Label> interface is implemented in multiple ways to provide
7405
different label types.
7406
7407
For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7408
HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7409
The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7410
7411
All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7412
*/
7413
7414
Graph.Label = {};
7415
7416
/*
7417
Class: Graph.Label.Native
7418
7419
Implements labels natively, using the Canvas text API.
7420
*/
7421
Graph.Label.Native = new Class({
7422
initialize: function(viz) {
7423
this.viz = viz;
7424
},
7425
7426
/*
7427
Method: plotLabel
7428
7429
Plots a label for a given node.
7430
7431
Parameters:
7432
7433
canvas - (object) A <Canvas> instance.
7434
node - (object) A <Graph.Node>.
7435
controller - (object) A configuration object.
7436
7437
Example:
7438
7439
(start code js)
7440
var viz = new $jit.Viz(options);
7441
var node = viz.graph.getNode('nodeId');
7442
viz.labels.plotLabel(viz.canvas, node, viz.config);
7443
(end code)
7444
*/
7445
plotLabel: function(canvas, node, controller) {
7446
var ctx = canvas.getCtx();
7447
var pos = node.pos.getc(true);
7448
7449
ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7450
ctx.textAlign = node.getLabelData('textAlign');
7451
ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7452
ctx.textBaseline = node.getLabelData('textBaseline');
7453
7454
this.renderLabel(canvas, node, controller);
7455
},
7456
7457
/*
7458
renderLabel
7459
7460
Does the actual rendering of the label in the canvas. The default
7461
implementation renders the label close to the position of the node, this
7462
method should be overriden to position the labels differently.
7463
7464
Parameters:
7465
7466
canvas - A <Canvas> instance.
7467
node - A <Graph.Node>.
7468
controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7469
*/
7470
renderLabel: function(canvas, node, controller) {
7471
var ctx = canvas.getCtx();
7472
var pos = node.pos.getc(true);
7473
ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7474
},
7475
7476
hideLabel: $.empty,
7477
hideLabels: $.empty
7478
});
7479
7480
/*
7481
Class: Graph.Label.DOM
7482
7483
Abstract Class implementing some DOM label methods.
7484
7485
Implemented by:
7486
7487
<Graph.Label.HTML> and <Graph.Label.SVG>.
7488
7489
*/
7490
Graph.Label.DOM = new Class({
7491
//A flag value indicating if node labels are being displayed or not.
7492
labelsHidden: false,
7493
//Label container
7494
labelContainer: false,
7495
//Label elements hash.
7496
labels: {},
7497
7498
/*
7499
Method: getLabelContainer
7500
7501
Lazy fetcher for the label container.
7502
7503
Returns:
7504
7505
The label container DOM element.
7506
7507
Example:
7508
7509
(start code js)
7510
var viz = new $jit.Viz(options);
7511
var labelContainer = viz.labels.getLabelContainer();
7512
alert(labelContainer.innerHTML);
7513
(end code)
7514
*/
7515
getLabelContainer: function() {
7516
return this.labelContainer ?
7517
this.labelContainer :
7518
this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7519
},
7520
7521
/*
7522
Method: getLabel
7523
7524
Lazy fetcher for the label element.
7525
7526
Parameters:
7527
7528
id - (string) The label id (which is also a <Graph.Node> id).
7529
7530
Returns:
7531
7532
The label element.
7533
7534
Example:
7535
7536
(start code js)
7537
var viz = new $jit.Viz(options);
7538
var label = viz.labels.getLabel('someid');
7539
alert(label.innerHTML);
7540
(end code)
7541
7542
*/
7543
getLabel: function(id) {
7544
return (id in this.labels && this.labels[id] != null) ?
7545
this.labels[id] :
7546
this.labels[id] = document.getElementById(id);
7547
},
7548
7549
/*
7550
Method: hideLabels
7551
7552
Hides all labels (by hiding the label container).
7553
7554
Parameters:
7555
7556
hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7557
7558
Example:
7559
(start code js)
7560
var viz = new $jit.Viz(options);
7561
rg.labels.hideLabels(true);
7562
(end code)
7563
7564
*/
7565
hideLabels: function (hide) {
7566
var container = this.getLabelContainer();
7567
if(hide)
7568
container.style.display = 'none';
7569
else
7570
container.style.display = '';
7571
this.labelsHidden = hide;
7572
},
7573
7574
/*
7575
Method: clearLabels
7576
7577
Clears the label container.
7578
7579
Useful when using a new visualization with the same canvas element/widget.
7580
7581
Parameters:
7582
7583
force - (boolean) Forces deletion of all labels.
7584
7585
Example:
7586
(start code js)
7587
var viz = new $jit.Viz(options);
7588
viz.labels.clearLabels();
7589
(end code)
7590
*/
7591
clearLabels: function(force) {
7592
for(var id in this.labels) {
7593
if (force || !this.viz.graph.hasNode(id)) {
7594
this.disposeLabel(id);
7595
delete this.labels[id];
7596
}
7597
}
7598
},
7599
7600
/*
7601
Method: disposeLabel
7602
7603
Removes a label.
7604
7605
Parameters:
7606
7607
id - (string) A label id (which generally is also a <Graph.Node> id).
7608
7609
Example:
7610
(start code js)
7611
var viz = new $jit.Viz(options);
7612
viz.labels.disposeLabel('labelid');
7613
(end code)
7614
*/
7615
disposeLabel: function(id) {
7616
var elem = this.getLabel(id);
7617
if(elem && elem.parentNode) {
7618
elem.parentNode.removeChild(elem);
7619
}
7620
},
7621
7622
/*
7623
Method: hideLabel
7624
7625
Hides the corresponding <Graph.Node> label.
7626
7627
Parameters:
7628
7629
node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7630
show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7631
7632
Example:
7633
(start code js)
7634
var rg = new $jit.Viz(options);
7635
viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7636
(end code)
7637
*/
7638
hideLabel: function(node, show) {
7639
node = $.splat(node);
7640
var st = show ? "" : "none", lab, that = this;
7641
$.each(node, function(n) {
7642
var lab = that.getLabel(n.id);
7643
if (lab) {
7644
lab.style.display = st;
7645
}
7646
});
7647
},
7648
7649
/*
7650
fitsInCanvas
7651
7652
Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7653
7654
Parameters:
7655
7656
pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7657
canvas - A <Canvas> instance.
7658
7659
Returns:
7660
7661
A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7662
7663
*/
7664
fitsInCanvas: function(pos, canvas) {
7665
var size = canvas.getSize();
7666
if(pos.x >= size.width || pos.x < 0
7667
|| pos.y >= size.height || pos.y < 0) return false;
7668
return true;
7669
}
7670
});
7671
7672
/*
7673
Class: Graph.Label.HTML
7674
7675
Implements HTML labels.
7676
7677
Extends:
7678
7679
All <Graph.Label.DOM> methods.
7680
7681
*/
7682
Graph.Label.HTML = new Class({
7683
Implements: Graph.Label.DOM,
7684
7685
/*
7686
Method: plotLabel
7687
7688
Plots a label for a given node.
7689
7690
Parameters:
7691
7692
canvas - (object) A <Canvas> instance.
7693
node - (object) A <Graph.Node>.
7694
controller - (object) A configuration object.
7695
7696
Example:
7697
7698
(start code js)
7699
var viz = new $jit.Viz(options);
7700
var node = viz.graph.getNode('nodeId');
7701
viz.labels.plotLabel(viz.canvas, node, viz.config);
7702
(end code)
7703
7704
7705
*/
7706
plotLabel: function(canvas, node, controller) {
7707
var id = node.id, tag = this.getLabel(id);
7708
7709
if(!tag && !(tag = document.getElementById(id))) {
7710
tag = document.createElement('div');
7711
var container = this.getLabelContainer();
7712
tag.id = id;
7713
tag.className = 'node';
7714
tag.style.position = 'absolute';
7715
controller.onCreateLabel(tag, node);
7716
container.appendChild(tag);
7717
this.labels[node.id] = tag;
7718
}
7719
7720
this.placeLabel(tag, node, controller);
7721
}
7722
});
7723
7724
/*
7725
Class: Graph.Label.SVG
7726
7727
Implements SVG labels.
7728
7729
Extends:
7730
7731
All <Graph.Label.DOM> methods.
7732
*/
7733
Graph.Label.SVG = new Class({
7734
Implements: Graph.Label.DOM,
7735
7736
/*
7737
Method: plotLabel
7738
7739
Plots a label for a given node.
7740
7741
Parameters:
7742
7743
canvas - (object) A <Canvas> instance.
7744
node - (object) A <Graph.Node>.
7745
controller - (object) A configuration object.
7746
7747
Example:
7748
7749
(start code js)
7750
var viz = new $jit.Viz(options);
7751
var node = viz.graph.getNode('nodeId');
7752
viz.labels.plotLabel(viz.canvas, node, viz.config);
7753
(end code)
7754
7755
7756
*/
7757
plotLabel: function(canvas, node, controller) {
7758
var id = node.id, tag = this.getLabel(id);
7759
if(!tag && !(tag = document.getElementById(id))) {
7760
var ns = 'http://www.w3.org/2000/svg';
7761
tag = document.createElementNS(ns, 'svg:text');
7762
var tspan = document.createElementNS(ns, 'svg:tspan');
7763
tag.appendChild(tspan);
7764
var container = this.getLabelContainer();
7765
tag.setAttribute('id', id);
7766
tag.setAttribute('class', 'node');
7767
container.appendChild(tag);
7768
controller.onCreateLabel(tag, node);
7769
this.labels[node.id] = tag;
7770
}
7771
this.placeLabel(tag, node, controller);
7772
}
7773
});
7774
7775
7776
7777
Graph.Geom = new Class({
7778
7779
initialize: function(viz) {
7780
this.viz = viz;
7781
this.config = viz.config;
7782
this.node = viz.config.Node;
7783
this.edge = viz.config.Edge;
7784
},
7785
/*
7786
Applies a translation to the tree.
7787
7788
Parameters:
7789
7790
pos - A <Complex> number specifying translation vector.
7791
prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7792
7793
Example:
7794
7795
(start code js)
7796
st.geom.translate(new Complex(300, 100), 'end');
7797
(end code)
7798
*/
7799
translate: function(pos, prop) {
7800
prop = $.splat(prop);
7801
this.viz.graph.eachNode(function(elem) {
7802
$.each(prop, function(p) { elem.getPos(p).$add(pos); });
7803
});
7804
},
7805
/*
7806
Hides levels of the tree until it properly fits in canvas.
7807
*/
7808
setRightLevelToShow: function(node, canvas, callback) {
7809
var level = this.getRightLevelToShow(node, canvas),
7810
fx = this.viz.labels,
7811
opt = $.merge({
7812
execShow:true,
7813
execHide:true,
7814
onHide: $.empty,
7815
onShow: $.empty
7816
}, callback || {});
7817
node.eachLevel(0, this.config.levelsToShow, function(n) {
7818
var d = n._depth - node._depth;
7819
if(d > level) {
7820
opt.onHide(n);
7821
if(opt.execHide) {
7822
n.drawn = false;
7823
n.exist = false;
7824
fx.hideLabel(n, false);
7825
}
7826
} else {
7827
opt.onShow(n);
7828
if(opt.execShow) {
7829
n.exist = true;
7830
}
7831
}
7832
});
7833
node.drawn= true;
7834
},
7835
/*
7836
Returns the right level to show for the current tree in order to fit in canvas.
7837
*/
7838
getRightLevelToShow: function(node, canvas) {
7839
var config = this.config;
7840
var level = config.levelsToShow;
7841
var constrained = config.constrained;
7842
if(!constrained) return level;
7843
while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7844
return level;
7845
}
7846
});
7847
7848
/*
7849
* File: Loader.js
7850
*
7851
*/
7852
7853
/*
7854
Object: Loader
7855
7856
Provides methods for loading and serving JSON data.
7857
*/
7858
var Loader = {
7859
construct: function(json) {
7860
var isGraph = ($.type(json) == 'array');
7861
var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7862
if(!isGraph)
7863
//make tree
7864
(function (ans, json) {
7865
ans.addNode(json);
7866
if(json.children) {
7867
for(var i=0, ch = json.children; i<ch.length; i++) {
7868
ans.addAdjacence(json, ch[i]);
7869
arguments.callee(ans, ch[i]);
7870
}
7871
}
7872
})(ans, json);
7873
else
7874
//make graph
7875
(function (ans, json) {
7876
var getNode = function(id) {
7877
for(var i=0, l=json.length; i<l; i++) {
7878
if(json[i].id == id) {
7879
return json[i];
7880
}
7881
}
7882
// The node was not defined in the JSON
7883
// Let's create it
7884
var newNode = {
7885
"id" : id,
7886
"name" : id
7887
};
7888
return ans.addNode(newNode);
7889
};
7890
7891
for(var i=0, l=json.length; i<l; i++) {
7892
ans.addNode(json[i]);
7893
var adj = json[i].adjacencies;
7894
if (adj) {
7895
for(var j=0, lj=adj.length; j<lj; j++) {
7896
var node = adj[j], data = {};
7897
if(typeof adj[j] != 'string') {
7898
data = $.merge(node.data, {});
7899
node = node.nodeTo;
7900
}
7901
ans.addAdjacence(json[i], getNode(node), data);
7902
}
7903
}
7904
}
7905
})(ans, json);
7906
7907
return ans;
7908
},
7909
7910
/*
7911
Method: loadJSON
7912
7913
Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7914
7915
A JSON tree or graph structure consists of nodes, each having as properties
7916
7917
id - (string) A unique identifier for the node
7918
name - (string) A node's name
7919
data - (object) The data optional property contains a hash (i.e {})
7920
where you can store all the information you want about this node.
7921
7922
For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7923
7924
Example:
7925
7926
(start code js)
7927
var json = {
7928
"id": "aUniqueIdentifier",
7929
"name": "usually a nodes name",
7930
"data": {
7931
"some key": "some value",
7932
"some other key": "some other value"
7933
},
7934
"children": [ *other nodes or empty* ]
7935
};
7936
(end code)
7937
7938
JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7939
For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7940
7941
There are two types of *Graph* structures, *simple* and *extended* graph structures.
7942
7943
For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7944
id of the node connected to the main node.
7945
7946
Example:
7947
7948
(start code js)
7949
var json = [
7950
{
7951
"id": "aUniqueIdentifier",
7952
"name": "usually a nodes name",
7953
"data": {
7954
"some key": "some value",
7955
"some other key": "some other value"
7956
},
7957
"adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7958
},
7959
7960
'other nodes go here...'
7961
];
7962
(end code)
7963
7964
For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7965
7966
nodeTo - (string) The other node connected by this adjacency.
7967
data - (object) A data property, where we can store custom key/value information.
7968
7969
Example:
7970
7971
(start code js)
7972
var json = [
7973
{
7974
"id": "aUniqueIdentifier",
7975
"name": "usually a nodes name",
7976
"data": {
7977
"some key": "some value",
7978
"some other key": "some other value"
7979
},
7980
"adjacencies": [
7981
{
7982
nodeTo:"aNodeId",
7983
data: {} //put whatever you want here
7984
},
7985
'other adjacencies go here...'
7986
},
7987
7988
'other nodes go here...'
7989
];
7990
(end code)
7991
7992
About the data property:
7993
7994
As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
7995
You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
7996
have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7997
7998
For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
7999
<Options.Node> will override the general value for that option with that particular value. For this to work
8000
however, you do have to set *overridable = true* in <Options.Node>.
8001
8002
The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
8003
if <Options.Edge> has *overridable = true*.
8004
8005
When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
8006
since this is the value which will be taken into account when creating the layout.
8007
The same thing goes for the *$color* parameter.
8008
8009
In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
8010
*$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
8011
canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
8012
to the *shadowBlur* property.
8013
8014
These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
8015
by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
8016
8017
Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
8018
information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
8019
8020
loadJSON Parameters:
8021
8022
json - A JSON Tree or Graph structure.
8023
i - For Graph structures only. Sets the indexed node as root for the visualization.
8024
8025
*/
8026
loadJSON: function(json, i) {
8027
this.json = json;
8028
//if they're canvas labels erase them.
8029
if(this.labels && this.labels.clearLabels) {
8030
this.labels.clearLabels(true);
8031
}
8032
this.graph = this.construct(json);
8033
if($.type(json) != 'array'){
8034
this.root = json.id;
8035
} else {
8036
this.root = json[i? i : 0].id;
8037
}
8038
},
8039
8040
/*
8041
Method: toJSON
8042
8043
Returns a JSON tree/graph structure from the visualization's <Graph>.
8044
See <Loader.loadJSON> for the graph formats available.
8045
8046
See also:
8047
8048
<Loader.loadJSON>
8049
8050
Parameters:
8051
8052
type - (string) Default's "tree". The type of the JSON structure to be returned.
8053
Possible options are "tree" or "graph".
8054
*/
8055
toJSON: function(type) {
8056
type = type || "tree";
8057
if(type == 'tree') {
8058
var ans = {};
8059
var rootNode = this.graph.getNode(this.root);
8060
var ans = (function recTree(node) {
8061
var ans = {};
8062
ans.id = node.id;
8063
ans.name = node.name;
8064
ans.data = node.data;
8065
var ch =[];
8066
node.eachSubnode(function(n) {
8067
ch.push(recTree(n));
8068
});
8069
ans.children = ch;
8070
return ans;
8071
})(rootNode);
8072
return ans;
8073
} else {
8074
var ans = [];
8075
var T = !!this.graph.getNode(this.root).visited;
8076
this.graph.eachNode(function(node) {
8077
var ansNode = {};
8078
ansNode.id = node.id;
8079
ansNode.name = node.name;
8080
ansNode.data = node.data;
8081
var adjs = [];
8082
node.eachAdjacency(function(adj) {
8083
var nodeTo = adj.nodeTo;
8084
if(!!nodeTo.visited === T) {
8085
var ansAdj = {};
8086
ansAdj.nodeTo = nodeTo.id;
8087
ansAdj.data = adj.data;
8088
adjs.push(ansAdj);
8089
}
8090
});
8091
ansNode.adjacencies = adjs;
8092
ans.push(ansNode);
8093
node.visited = !T;
8094
});
8095
return ans;
8096
}
8097
}
8098
};
8099
8100
8101
8102
/*
8103
* File: Layouts.js
8104
*
8105
* Implements base Tree and Graph layouts.
8106
*
8107
* Description:
8108
*
8109
* Implements base Tree and Graph layouts like Radial, Tree, etc.
8110
*
8111
*/
8112
8113
/*
8114
* Object: Layouts
8115
*
8116
* Parent object for common layouts.
8117
*
8118
*/
8119
var Layouts = $jit.Layouts = {};
8120
8121
8122
//Some util shared layout functions are defined here.
8123
var NodeDim = {
8124
label: null,
8125
8126
compute: function(graph, prop, opt) {
8127
this.initializeLabel(opt);
8128
var label = this.label, style = label.style;
8129
graph.eachNode(function(n) {
8130
var autoWidth = n.getData('autoWidth'),
8131
autoHeight = n.getData('autoHeight');
8132
if(autoWidth || autoHeight) {
8133
//delete dimensions since these are
8134
//going to be overridden now.
8135
delete n.data.$width;
8136
delete n.data.$height;
8137
delete n.data.$dim;
8138
8139
var width = n.getData('width'),
8140
height = n.getData('height');
8141
//reset label dimensions
8142
style.width = autoWidth? 'auto' : width + 'px';
8143
style.height = autoHeight? 'auto' : height + 'px';
8144
8145
//TODO(nico) should let the user choose what to insert here.
8146
label.innerHTML = n.name;
8147
8148
var offsetWidth = label.offsetWidth,
8149
offsetHeight = label.offsetHeight;
8150
var type = n.getData('type');
8151
if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8152
n.setData('width', offsetWidth);
8153
n.setData('height', offsetHeight);
8154
} else {
8155
var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8156
n.setData('width', dim);
8157
n.setData('height', dim);
8158
n.setData('dim', dim);
8159
}
8160
}
8161
});
8162
},
8163
8164
initializeLabel: function(opt) {
8165
if(!this.label) {
8166
this.label = document.createElement('div');
8167
document.body.appendChild(this.label);
8168
}
8169
this.setLabelStyles(opt);
8170
},
8171
8172
setLabelStyles: function(opt) {
8173
$.extend(this.label.style, {
8174
'visibility': 'hidden',
8175
'position': 'absolute',
8176
'width': 'auto',
8177
'height': 'auto'
8178
});
8179
this.label.className = 'jit-autoadjust-label';
8180
}
8181
};
8182
8183
8184
/*
8185
* Class: Layouts.Tree
8186
*
8187
* Implements a Tree Layout.
8188
*
8189
* Implemented By:
8190
*
8191
* <ST>
8192
*
8193
* Inspired by:
8194
*
8195
* Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8196
*
8197
*/
8198
Layouts.Tree = (function() {
8199
//Layout functions
8200
var slice = Array.prototype.slice;
8201
8202
/*
8203
Calculates the max width and height nodes for a tree level
8204
*/
8205
function getBoundaries(graph, config, level, orn, prop) {
8206
var dim = config.Node;
8207
var multitree = config.multitree;
8208
if (dim.overridable) {
8209
var w = -1, h = -1;
8210
graph.eachNode(function(n) {
8211
if (n._depth == level
8212
&& (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8213
var dw = n.getData('width', prop);
8214
var dh = n.getData('height', prop);
8215
w = (w < dw) ? dw : w;
8216
h = (h < dh) ? dh : h;
8217
}
8218
});
8219
return {
8220
'width' : w < 0 ? dim.width : w,
8221
'height' : h < 0 ? dim.height : h
8222
};
8223
} else {
8224
return dim;
8225
}
8226
}
8227
8228
8229
function movetree(node, prop, val, orn) {
8230
var p = (orn == "left" || orn == "right") ? "y" : "x";
8231
node.getPos(prop)[p] += val;
8232
}
8233
8234
8235
function moveextent(extent, val) {
8236
var ans = [];
8237
$.each(extent, function(elem) {
8238
elem = slice.call(elem);
8239
elem[0] += val;
8240
elem[1] += val;
8241
ans.push(elem);
8242
});
8243
return ans;
8244
}
8245
8246
8247
function merge(ps, qs) {
8248
if (ps.length == 0)
8249
return qs;
8250
if (qs.length == 0)
8251
return ps;
8252
var p = ps.shift(), q = qs.shift();
8253
return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8254
}
8255
8256
8257
function mergelist(ls, def) {
8258
def = def || [];
8259
if (ls.length == 0)
8260
return def;
8261
var ps = ls.pop();
8262
return mergelist(ls, merge(ps, def));
8263
}
8264
8265
8266
function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8267
if (ext1.length <= i || ext2.length <= i)
8268
return 0;
8269
8270
var p = ext1[i][1], q = ext2[i][0];
8271
return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8272
+ subtreeOffset, p - q + siblingOffset);
8273
}
8274
8275
8276
function fitlistl(es, subtreeOffset, siblingOffset) {
8277
function $fitlistl(acc, es, i) {
8278
if (es.length <= i)
8279
return [];
8280
var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8281
return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8282
}
8283
;
8284
return $fitlistl( [], es, 0);
8285
}
8286
8287
8288
function fitlistr(es, subtreeOffset, siblingOffset) {
8289
function $fitlistr(acc, es, i) {
8290
if (es.length <= i)
8291
return [];
8292
var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8293
return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8294
}
8295
;
8296
es = slice.call(es);
8297
var ans = $fitlistr( [], es.reverse(), 0);
8298
return ans.reverse();
8299
}
8300
8301
8302
function fitlist(es, subtreeOffset, siblingOffset, align) {
8303
var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8304
subtreeOffset, siblingOffset);
8305
8306
if (align == "left")
8307
esr = esl;
8308
else if (align == "right")
8309
esl = esr;
8310
8311
for ( var i = 0, ans = []; i < esl.length; i++) {
8312
ans[i] = (esl[i] + esr[i]) / 2;
8313
}
8314
return ans;
8315
}
8316
8317
8318
function design(graph, node, prop, config, orn) {
8319
var multitree = config.multitree;
8320
var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8321
var ind = +(orn == "left" || orn == "right");
8322
var p = auxp[ind], notp = auxp[1 - ind];
8323
8324
var cnode = config.Node;
8325
var s = auxs[ind], nots = auxs[1 - ind];
8326
8327
var siblingOffset = config.siblingOffset;
8328
var subtreeOffset = config.subtreeOffset;
8329
var align = config.align;
8330
8331
function $design(node, maxsize, acum) {
8332
var sval = node.getData(s, prop);
8333
var notsval = maxsize
8334
|| (node.getData(nots, prop));
8335
8336
var trees = [], extents = [], chmaxsize = false;
8337
var chacum = notsval + config.levelDistance;
8338
node.eachSubnode(function(n) {
8339
if (n.exist
8340
&& (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8341
8342
if (!chmaxsize)
8343
chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8344
8345
var s = $design(n, chmaxsize[nots], acum + chacum);
8346
trees.push(s.tree);
8347
extents.push(s.extent);
8348
}
8349
});
8350
var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8351
for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8352
movetree(trees[i], prop, positions[i], orn);
8353
pextents.push(moveextent(extents[i], positions[i]));
8354
}
8355
var resultextent = [ [ -sval / 2, sval / 2 ] ]
8356
.concat(mergelist(pextents));
8357
node.getPos(prop)[p] = 0;
8358
8359
if (orn == "top" || orn == "left") {
8360
node.getPos(prop)[notp] = acum;
8361
} else {
8362
node.getPos(prop)[notp] = -acum;
8363
}
8364
8365
return {
8366
tree : node,
8367
extent : resultextent
8368
};
8369
}
8370
8371
$design(node, false, 0);
8372
}
8373
8374
8375
return new Class({
8376
/*
8377
Method: compute
8378
8379
Computes nodes' positions.
8380
8381
*/
8382
compute : function(property, computeLevels) {
8383
var prop = property || 'start';
8384
var node = this.graph.getNode(this.root);
8385
$.extend(node, {
8386
'drawn' : true,
8387
'exist' : true,
8388
'selected' : true
8389
});
8390
NodeDim.compute(this.graph, prop, this.config);
8391
if (!!computeLevels || !("_depth" in node)) {
8392
this.graph.computeLevels(this.root, 0, "ignore");
8393
}
8394
8395
this.computePositions(node, prop);
8396
},
8397
8398
computePositions : function(node, prop) {
8399
var config = this.config;
8400
var multitree = config.multitree;
8401
var align = config.align;
8402
var indent = align !== 'center' && config.indent;
8403
var orn = config.orientation;
8404
var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8405
var that = this;
8406
$.each(orns, function(orn) {
8407
//calculate layout
8408
design(that.graph, node, prop, that.config, orn, prop);
8409
var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8410
//absolutize
8411
(function red(node) {
8412
node.eachSubnode(function(n) {
8413
if (n.exist
8414
&& (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8415
8416
n.getPos(prop)[i] += node.getPos(prop)[i];
8417
if (indent) {
8418
n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8419
}
8420
red(n);
8421
}
8422
});
8423
})(node);
8424
});
8425
}
8426
});
8427
8428
})();
8429
8430
/*
8431
* File: Spacetree.js
8432
*/
8433
8434
/*
8435
Class: ST
8436
8437
A Tree layout with advanced contraction and expansion animations.
8438
8439
Inspired by:
8440
8441
SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8442
<http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8443
8444
Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8445
8446
Note:
8447
8448
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.
8449
8450
Implements:
8451
8452
All <Loader> methods
8453
8454
Constructor Options:
8455
8456
Inherits options from
8457
8458
- <Options.Canvas>
8459
- <Options.Controller>
8460
- <Options.Tree>
8461
- <Options.Node>
8462
- <Options.Edge>
8463
- <Options.Label>
8464
- <Options.Events>
8465
- <Options.Tips>
8466
- <Options.NodeStyles>
8467
- <Options.Navigation>
8468
8469
Additionally, there are other parameters and some default values changed
8470
8471
constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8472
levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8473
levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8474
Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8475
offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8476
offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8477
duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8478
8479
Instance Properties:
8480
8481
canvas - Access a <Canvas> instance.
8482
graph - Access a <Graph> instance.
8483
op - Access a <ST.Op> instance.
8484
fx - Access a <ST.Plot> instance.
8485
labels - Access a <ST.Label> interface implementation.
8486
8487
*/
8488
8489
$jit.ST= (function() {
8490
// Define some private methods first...
8491
// Nodes in path
8492
var nodesInPath = [];
8493
// Nodes to contract
8494
function getNodesToHide(node) {
8495
node = node || this.clickedNode;
8496
if(!this.config.constrained) {
8497
return [];
8498
}
8499
var Geom = this.geom;
8500
var graph = this.graph;
8501
var canvas = this.canvas;
8502
var level = node._depth, nodeArray = [];
8503
graph.eachNode(function(n) {
8504
if(n.exist && !n.selected) {
8505
if(n.isDescendantOf(node.id)) {
8506
if(n._depth <= level) nodeArray.push(n);
8507
} else {
8508
nodeArray.push(n);
8509
}
8510
}
8511
});
8512
var leafLevel = Geom.getRightLevelToShow(node, canvas);
8513
node.eachLevel(leafLevel, leafLevel, function(n) {
8514
if(n.exist && !n.selected) nodeArray.push(n);
8515
});
8516
8517
for (var i = 0; i < nodesInPath.length; i++) {
8518
var n = this.graph.getNode(nodesInPath[i]);
8519
if(!n.isDescendantOf(node.id)) {
8520
nodeArray.push(n);
8521
}
8522
}
8523
return nodeArray;
8524
};
8525
// Nodes to expand
8526
function getNodesToShow(node) {
8527
var nodeArray = [], config = this.config;
8528
node = node || this.clickedNode;
8529
this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8530
if(config.multitree && !('$orn' in n.data)
8531
&& n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8532
nodeArray.push(n);
8533
} else if(n.drawn && !n.anySubnode("drawn")) {
8534
nodeArray.push(n);
8535
}
8536
});
8537
return nodeArray;
8538
};
8539
// Now define the actual class.
8540
return new Class({
8541
8542
Implements: [Loader, Extras, Layouts.Tree],
8543
8544
initialize: function(controller) {
8545
var $ST = $jit.ST;
8546
8547
var config= {
8548
levelsToShow: 2,
8549
levelDistance: 30,
8550
constrained: true,
8551
Node: {
8552
type: 'rectangle'
8553
},
8554
duration: 700,
8555
offsetX: 0,
8556
offsetY: 0
8557
};
8558
8559
this.controller = this.config = $.merge(
8560
Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8561
"Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8562
8563
var canvasConfig = this.config;
8564
if(canvasConfig.useCanvas) {
8565
this.canvas = canvasConfig.useCanvas;
8566
this.config.labelContainer = this.canvas.id + '-label';
8567
} else {
8568
if(canvasConfig.background) {
8569
canvasConfig.background = $.merge({
8570
type: 'Circles'
8571
}, canvasConfig.background);
8572
}
8573
this.canvas = new Canvas(this, canvasConfig);
8574
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8575
}
8576
8577
this.graphOptions = {
8578
'klass': Complex
8579
};
8580
this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8581
this.labels = new $ST.Label[canvasConfig.Label.type](this);
8582
this.fx = new $ST.Plot(this, $ST);
8583
this.op = new $ST.Op(this);
8584
this.group = new $ST.Group(this);
8585
this.geom = new $ST.Geom(this);
8586
this.clickedNode= null;
8587
// initialize extras
8588
this.initializeExtras();
8589
},
8590
8591
/*
8592
Method: plot
8593
8594
Plots the <ST>. This is a shortcut to *fx.plot*.
8595
8596
*/
8597
plot: function() { this.fx.plot(this.controller); },
8598
8599
8600
/*
8601
Method: switchPosition
8602
8603
Switches the tree orientation.
8604
8605
Parameters:
8606
8607
pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8608
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.
8609
onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8610
8611
Example:
8612
8613
(start code js)
8614
st.switchPosition("right", "animate", {
8615
onComplete: function() {
8616
alert('completed!');
8617
}
8618
});
8619
(end code)
8620
*/
8621
switchPosition: function(pos, method, onComplete) {
8622
var Geom = this.geom, Plot = this.fx, that = this;
8623
if(!Plot.busy) {
8624
Plot.busy = true;
8625
this.contract({
8626
onComplete: function() {
8627
Geom.switchOrientation(pos);
8628
that.compute('end', false);
8629
Plot.busy = false;
8630
if(method == 'animate') {
8631
that.onClick(that.clickedNode.id, onComplete);
8632
} else if(method == 'replot') {
8633
that.select(that.clickedNode.id, onComplete);
8634
}
8635
}
8636
}, pos);
8637
}
8638
},
8639
8640
/*
8641
Method: switchAlignment
8642
8643
Switches the tree alignment.
8644
8645
Parameters:
8646
8647
align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8648
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.
8649
onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8650
8651
Example:
8652
8653
(start code js)
8654
st.switchAlignment("right", "animate", {
8655
onComplete: function() {
8656
alert('completed!');
8657
}
8658
});
8659
(end code)
8660
*/
8661
switchAlignment: function(align, method, onComplete) {
8662
this.config.align = align;
8663
if(method == 'animate') {
8664
this.select(this.clickedNode.id, onComplete);
8665
} else if(method == 'replot') {
8666
this.onClick(this.clickedNode.id, onComplete);
8667
}
8668
},
8669
8670
/*
8671
Method: addNodeInPath
8672
8673
Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8674
8675
8676
Parameters:
8677
8678
id - (string) A <Graph.Node> id.
8679
8680
Example:
8681
8682
(start code js)
8683
st.addNodeInPath("nodeId");
8684
(end code)
8685
*/
8686
addNodeInPath: function(id) {
8687
nodesInPath.push(id);
8688
this.select((this.clickedNode && this.clickedNode.id) || this.root);
8689
},
8690
8691
/*
8692
Method: clearNodesInPath
8693
8694
Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8695
8696
See also:
8697
8698
<ST.addNodeInPath>
8699
8700
Example:
8701
8702
(start code js)
8703
st.clearNodesInPath();
8704
(end code)
8705
*/
8706
clearNodesInPath: function(id) {
8707
nodesInPath.length = 0;
8708
this.select((this.clickedNode && this.clickedNode.id) || this.root);
8709
},
8710
8711
/*
8712
Method: refresh
8713
8714
Computes positions and plots the tree.
8715
8716
*/
8717
refresh: function() {
8718
this.reposition();
8719
this.select((this.clickedNode && this.clickedNode.id) || this.root);
8720
},
8721
8722
reposition: function() {
8723
this.graph.computeLevels(this.root, 0, "ignore");
8724
this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8725
this.graph.eachNode(function(n) {
8726
if(n.exist) n.drawn = true;
8727
});
8728
this.compute('end');
8729
},
8730
8731
requestNodes: function(node, onComplete) {
8732
var handler = $.merge(this.controller, onComplete),
8733
lev = this.config.levelsToShow;
8734
if(handler.request) {
8735
var leaves = [], d = node._depth;
8736
node.eachLevel(0, lev, function(n) {
8737
if(n.drawn &&
8738
!n.anySubnode()) {
8739
leaves.push(n);
8740
n._level = lev - (n._depth - d);
8741
}
8742
});
8743
this.group.requestNodes(leaves, handler);
8744
}
8745
else
8746
handler.onComplete();
8747
},
8748
8749
contract: function(onComplete, switched) {
8750
var orn = this.config.orientation;
8751
var Geom = this.geom, Group = this.group;
8752
if(switched) Geom.switchOrientation(switched);
8753
var nodes = getNodesToHide.call(this);
8754
if(switched) Geom.switchOrientation(orn);
8755
Group.contract(nodes, $.merge(this.controller, onComplete));
8756
},
8757
8758
move: function(node, onComplete) {
8759
this.compute('end', false);
8760
var move = onComplete.Move, offset = {
8761
'x': move.offsetX,
8762
'y': move.offsetY
8763
};
8764
if(move.enable) {
8765
this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8766
}
8767
this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8768
},
8769
8770
expand: function (node, onComplete) {
8771
var nodeArray = getNodesToShow.call(this, node);
8772
this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8773
},
8774
8775
selectPath: function(node) {
8776
var that = this;
8777
this.graph.eachNode(function(n) { n.selected = false; });
8778
function path(node) {
8779
if(node == null || node.selected) return;
8780
node.selected = true;
8781
$.each(that.group.getSiblings([node])[node.id],
8782
function(n) {
8783
n.exist = true;
8784
n.drawn = true;
8785
});
8786
var parents = node.getParents();
8787
parents = (parents.length > 0)? parents[0] : null;
8788
path(parents);
8789
};
8790
for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8791
path(this.graph.getNode(ns[i]));
8792
}
8793
},
8794
8795
/*
8796
Method: setRoot
8797
8798
Switches the current root node. Changes the topology of the Tree.
8799
8800
Parameters:
8801
id - (string) The id of the node to be set as root.
8802
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.
8803
onComplete - (optional|object) An action to perform after the animation (if any).
8804
8805
Example:
8806
8807
(start code js)
8808
st.setRoot('nodeId', 'animate', {
8809
onComplete: function() {
8810
alert('complete!');
8811
}
8812
});
8813
(end code)
8814
*/
8815
setRoot: function(id, method, onComplete) {
8816
if(this.busy) return;
8817
this.busy = true;
8818
var that = this, canvas = this.canvas;
8819
var rootNode = this.graph.getNode(this.root);
8820
var clickedNode = this.graph.getNode(id);
8821
function $setRoot() {
8822
if(this.config.multitree && clickedNode.data.$orn) {
8823
var orn = clickedNode.data.$orn;
8824
var opp = {
8825
'left': 'right',
8826
'right': 'left',
8827
'top': 'bottom',
8828
'bottom': 'top'
8829
}[orn];
8830
rootNode.data.$orn = opp;
8831
(function tag(rootNode) {
8832
rootNode.eachSubnode(function(n) {
8833
if(n.id != id) {
8834
n.data.$orn = opp;
8835
tag(n);
8836
}
8837
});
8838
})(rootNode);
8839
delete clickedNode.data.$orn;
8840
}
8841
this.root = id;
8842
this.clickedNode = clickedNode;
8843
this.graph.computeLevels(this.root, 0, "ignore");
8844
this.geom.setRightLevelToShow(clickedNode, canvas, {
8845
execHide: false,
8846
onShow: function(node) {
8847
if(!node.drawn) {
8848
node.drawn = true;
8849
node.setData('alpha', 1, 'end');
8850
node.setData('alpha', 0);
8851
node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8852
}
8853
}
8854
});
8855
this.compute('end');
8856
this.busy = true;
8857
this.fx.animate({
8858
modes: ['linear', 'node-property:alpha'],
8859
onComplete: function() {
8860
that.busy = false;
8861
that.onClick(id, {
8862
onComplete: function() {
8863
onComplete && onComplete.onComplete();
8864
}
8865
});
8866
}
8867
});
8868
}
8869
8870
// delete previous orientations (if any)
8871
delete rootNode.data.$orns;
8872
8873
if(method == 'animate') {
8874
$setRoot.call(this);
8875
that.selectPath(clickedNode);
8876
} else if(method == 'replot') {
8877
$setRoot.call(this);
8878
this.select(this.root);
8879
}
8880
},
8881
8882
/*
8883
Method: addSubtree
8884
8885
Adds a subtree.
8886
8887
Parameters:
8888
subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8889
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.
8890
onComplete - (optional|object) An action to perform after the animation (if any).
8891
8892
Example:
8893
8894
(start code js)
8895
st.addSubtree(json, 'animate', {
8896
onComplete: function() {
8897
alert('complete!');
8898
}
8899
});
8900
(end code)
8901
*/
8902
addSubtree: function(subtree, method, onComplete) {
8903
if(method == 'replot') {
8904
this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8905
} else if (method == 'animate') {
8906
this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8907
}
8908
},
8909
8910
/*
8911
Method: removeSubtree
8912
8913
Removes a subtree.
8914
8915
Parameters:
8916
id - (string) The _id_ of the subtree to be removed.
8917
removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8918
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.
8919
onComplete - (optional|object) An action to perform after the animation (if any).
8920
8921
Example:
8922
8923
(start code js)
8924
st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8925
onComplete: function() {
8926
alert('complete!');
8927
}
8928
});
8929
(end code)
8930
8931
*/
8932
removeSubtree: function(id, removeRoot, method, onComplete) {
8933
var node = this.graph.getNode(id), subids = [];
8934
node.eachLevel(+!removeRoot, false, function(n) {
8935
subids.push(n.id);
8936
});
8937
if(method == 'replot') {
8938
this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8939
} else if (method == 'animate') {
8940
this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8941
}
8942
},
8943
8944
/*
8945
Method: select
8946
8947
Selects a node in the <ST> without performing an animation. Useful when selecting
8948
nodes which are currently hidden or deep inside the tree.
8949
8950
Parameters:
8951
id - (string) The id of the node to select.
8952
onComplete - (optional|object) an onComplete callback.
8953
8954
Example:
8955
(start code js)
8956
st.select('mynodeid', {
8957
onComplete: function() {
8958
alert('complete!');
8959
}
8960
});
8961
(end code)
8962
*/
8963
select: function(id, onComplete) {
8964
var group = this.group, geom = this.geom;
8965
var node= this.graph.getNode(id), canvas = this.canvas;
8966
var root = this.graph.getNode(this.root);
8967
var complete = $.merge(this.controller, onComplete);
8968
var that = this;
8969
8970
complete.onBeforeCompute(node);
8971
this.selectPath(node);
8972
this.clickedNode= node;
8973
this.requestNodes(node, {
8974
onComplete: function(){
8975
group.hide(group.prepare(getNodesToHide.call(that)), complete);
8976
geom.setRightLevelToShow(node, canvas);
8977
that.compute("current");
8978
that.graph.eachNode(function(n) {
8979
var pos = n.pos.getc(true);
8980
n.startPos.setc(pos.x, pos.y);
8981
n.endPos.setc(pos.x, pos.y);
8982
n.visited = false;
8983
});
8984
var offset = { x: complete.offsetX, y: complete.offsetY };
8985
that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8986
group.show(getNodesToShow.call(that));
8987
that.plot();
8988
complete.onAfterCompute(that.clickedNode);
8989
complete.onComplete();
8990
}
8991
});
8992
},
8993
8994
/*
8995
Method: onClick
8996
8997
Animates the <ST> to center the node specified by *id*.
8998
8999
Parameters:
9000
9001
id - (string) A node id.
9002
options - (optional|object) A group of options and callbacks described below.
9003
onComplete - (object) An object callback called when the animation finishes.
9004
Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
9005
9006
Example:
9007
9008
(start code js)
9009
st.onClick('mynodeid', {
9010
Move: {
9011
enable: true,
9012
offsetX: 30,
9013
offsetY: 5
9014
},
9015
onComplete: function() {
9016
alert('yay!');
9017
}
9018
});
9019
(end code)
9020
9021
*/
9022
onClick: function (id, options) {
9023
var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
9024
var innerController = {
9025
Move: {
9026
enable: true,
9027
offsetX: config.offsetX || 0,
9028
offsetY: config.offsetY || 0
9029
},
9030
setRightLevelToShowConfig: false,
9031
onBeforeRequest: $.empty,
9032
onBeforeContract: $.empty,
9033
onBeforeMove: $.empty,
9034
onBeforeExpand: $.empty
9035
};
9036
var complete = $.merge(this.controller, innerController, options);
9037
9038
if(!this.busy) {
9039
this.busy = true;
9040
var node = this.graph.getNode(id);
9041
this.selectPath(node, this.clickedNode);
9042
this.clickedNode = node;
9043
complete.onBeforeCompute(node);
9044
complete.onBeforeRequest(node);
9045
this.requestNodes(node, {
9046
onComplete: function() {
9047
complete.onBeforeContract(node);
9048
that.contract({
9049
onComplete: function() {
9050
Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
9051
complete.onBeforeMove(node);
9052
that.move(node, {
9053
Move: complete.Move,
9054
onComplete: function() {
9055
complete.onBeforeExpand(node);
9056
that.expand(node, {
9057
onComplete: function() {
9058
that.busy = false;
9059
complete.onAfterCompute(id);
9060
complete.onComplete();
9061
}
9062
}); // expand
9063
}
9064
}); // move
9065
}
9066
});// contract
9067
}
9068
});// request
9069
}
9070
}
9071
});
9072
9073
})();
9074
9075
$jit.ST.$extend = true;
9076
9077
/*
9078
Class: ST.Op
9079
9080
Custom extension of <Graph.Op>.
9081
9082
Extends:
9083
9084
All <Graph.Op> methods
9085
9086
See also:
9087
9088
<Graph.Op>
9089
9090
*/
9091
$jit.ST.Op = new Class({
9092
9093
Implements: Graph.Op
9094
9095
});
9096
9097
/*
9098
9099
Performs operations on group of nodes.
9100
9101
*/
9102
$jit.ST.Group = new Class({
9103
9104
initialize: function(viz) {
9105
this.viz = viz;
9106
this.canvas = viz.canvas;
9107
this.config = viz.config;
9108
this.animation = new Animation;
9109
this.nodes = null;
9110
},
9111
9112
/*
9113
9114
Calls the request method on the controller to request a subtree for each node.
9115
*/
9116
requestNodes: function(nodes, controller) {
9117
var counter = 0, len = nodes.length, nodeSelected = {};
9118
var complete = function() { controller.onComplete(); };
9119
var viz = this.viz;
9120
if(len == 0) complete();
9121
for(var i=0; i<len; i++) {
9122
nodeSelected[nodes[i].id] = nodes[i];
9123
controller.request(nodes[i].id, nodes[i]._level, {
9124
onComplete: function(nodeId, data) {
9125
if(data && data.children) {
9126
data.id = nodeId;
9127
viz.op.sum(data, { type: 'nothing' });
9128
}
9129
if(++counter == len) {
9130
viz.graph.computeLevels(viz.root, 0);
9131
complete();
9132
}
9133
}
9134
});
9135
}
9136
},
9137
9138
/*
9139
9140
Collapses group of nodes.
9141
*/
9142
contract: function(nodes, controller) {
9143
var viz = this.viz;
9144
var that = this;
9145
9146
nodes = this.prepare(nodes);
9147
this.animation.setOptions($.merge(controller, {
9148
$animating: false,
9149
compute: function(delta) {
9150
if(delta == 1) delta = 0.99;
9151
that.plotStep(1 - delta, controller, this.$animating);
9152
this.$animating = 'contract';
9153
},
9154
9155
complete: function() {
9156
that.hide(nodes, controller);
9157
}
9158
})).start();
9159
},
9160
9161
hide: function(nodes, controller) {
9162
var viz = this.viz;
9163
for(var i=0; i<nodes.length; i++) {
9164
// TODO nodes are requested on demand, but not
9165
// deleted when hidden. Would that be a good feature?
9166
// Currently that feature is buggy, so I'll turn it off
9167
// Actually this feature is buggy because trimming should take
9168
// place onAfterCompute and not right after collapsing nodes.
9169
if (true || !controller || !controller.request) {
9170
nodes[i].eachLevel(1, false, function(elem){
9171
if (elem.exist) {
9172
$.extend(elem, {
9173
'drawn': false,
9174
'exist': false
9175
});
9176
}
9177
});
9178
} else {
9179
var ids = [];
9180
nodes[i].eachLevel(1, false, function(n) {
9181
ids.push(n.id);
9182
});
9183
viz.op.removeNode(ids, { 'type': 'nothing' });
9184
viz.labels.clearLabels();
9185
}
9186
}
9187
controller.onComplete();
9188
},
9189
9190
9191
/*
9192
Expands group of nodes.
9193
*/
9194
expand: function(nodes, controller) {
9195
var that = this;
9196
this.show(nodes);
9197
this.animation.setOptions($.merge(controller, {
9198
$animating: false,
9199
compute: function(delta) {
9200
that.plotStep(delta, controller, this.$animating);
9201
this.$animating = 'expand';
9202
},
9203
9204
complete: function() {
9205
that.plotStep(undefined, controller, false);
9206
controller.onComplete();
9207
}
9208
})).start();
9209
9210
},
9211
9212
show: function(nodes) {
9213
var config = this.config;
9214
this.prepare(nodes);
9215
$.each(nodes, function(n) {
9216
// check for root nodes if multitree
9217
if(config.multitree && !('$orn' in n.data)) {
9218
delete n.data.$orns;
9219
var orns = ' ';
9220
n.eachSubnode(function(ch) {
9221
if(('$orn' in ch.data)
9222
&& orns.indexOf(ch.data.$orn) < 0
9223
&& ch.exist && !ch.drawn) {
9224
orns += ch.data.$orn + ' ';
9225
}
9226
});
9227
n.data.$orns = orns;
9228
}
9229
n.eachLevel(0, config.levelsToShow, function(n) {
9230
if(n.exist) n.drawn = true;
9231
});
9232
});
9233
},
9234
9235
prepare: function(nodes) {
9236
this.nodes = this.getNodesWithChildren(nodes);
9237
return this.nodes;
9238
},
9239
9240
/*
9241
Filters an array of nodes leaving only nodes with children.
9242
*/
9243
getNodesWithChildren: function(nodes) {
9244
var ans = [], config = this.config, root = this.viz.root;
9245
nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9246
for(var i=0; i<nodes.length; i++) {
9247
if(nodes[i].anySubnode("exist")) {
9248
for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9249
if(!config.multitree || '$orn' in nodes[j].data) {
9250
desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9251
}
9252
}
9253
if(!desc) ans.push(nodes[i]);
9254
}
9255
}
9256
return ans;
9257
},
9258
9259
plotStep: function(delta, controller, animating) {
9260
var viz = this.viz,
9261
config = this.config,
9262
canvas = viz.canvas,
9263
ctx = canvas.getCtx(),
9264
nodes = this.nodes;
9265
var i, node;
9266
// hide nodes that are meant to be collapsed/expanded
9267
var nds = {};
9268
for(i=0; i<nodes.length; i++) {
9269
node = nodes[i];
9270
nds[node.id] = [];
9271
var root = config.multitree && !('$orn' in node.data);
9272
var orns = root && node.data.$orns;
9273
node.eachSubgraph(function(n) {
9274
// Cleanup
9275
// special check for root node subnodes when
9276
// multitree is checked.
9277
if(root && orns && orns.indexOf(n.data.$orn) > 0
9278
&& n.drawn) {
9279
n.drawn = false;
9280
nds[node.id].push(n);
9281
} else if((!root || !orns) && n.drawn) {
9282
n.drawn = false;
9283
nds[node.id].push(n);
9284
}
9285
});
9286
node.drawn = true;
9287
}
9288
// plot the whole (non-scaled) tree
9289
if(nodes.length > 0) viz.fx.plot();
9290
// show nodes that were previously hidden
9291
for(i in nds) {
9292
$.each(nds[i], function(n) { n.drawn = true; });
9293
}
9294
// plot each scaled subtree
9295
for(i=0; i<nodes.length; i++) {
9296
node = nodes[i];
9297
ctx.save();
9298
viz.fx.plotSubtree(node, controller, delta, animating);
9299
ctx.restore();
9300
}
9301
},
9302
9303
getSiblings: function(nodes) {
9304
var siblings = {};
9305
$.each(nodes, function(n) {
9306
var par = n.getParents();
9307
if (par.length == 0) {
9308
siblings[n.id] = [n];
9309
} else {
9310
var ans = [];
9311
par[0].eachSubnode(function(sn) {
9312
ans.push(sn);
9313
});
9314
siblings[n.id] = ans;
9315
}
9316
});
9317
return siblings;
9318
}
9319
});
9320
9321
/*
9322
ST.Geom
9323
9324
Performs low level geometrical computations.
9325
9326
Access:
9327
9328
This instance can be accessed with the _geom_ parameter of the st instance created.
9329
9330
Example:
9331
9332
(start code js)
9333
var st = new ST(canvas, config);
9334
st.geom.translate //or can also call any other <ST.Geom> method
9335
(end code)
9336
9337
*/
9338
9339
$jit.ST.Geom = new Class({
9340
Implements: Graph.Geom,
9341
/*
9342
Changes the tree current orientation to the one specified.
9343
9344
You should usually use <ST.switchPosition> instead.
9345
*/
9346
switchOrientation: function(orn) {
9347
this.config.orientation = orn;
9348
},
9349
9350
/*
9351
Makes a value dispatch according to the current layout
9352
Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9353
*/
9354
dispatch: function() {
9355
// TODO(nico) should store Array.prototype.slice.call somewhere.
9356
var args = Array.prototype.slice.call(arguments);
9357
var s = args.shift(), len = args.length;
9358
var val = function(a) { return typeof a == 'function'? a() : a; };
9359
if(len == 2) {
9360
return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9361
} else if(len == 4) {
9362
switch(s) {
9363
case "top": return val(args[0]);
9364
case "right": return val(args[1]);
9365
case "bottom": return val(args[2]);
9366
case "left": return val(args[3]);
9367
}
9368
}
9369
return undefined;
9370
},
9371
9372
/*
9373
Returns label height or with, depending on the tree current orientation.
9374
*/
9375
getSize: function(n, invert) {
9376
var data = n.data, config = this.config;
9377
var siblingOffset = config.siblingOffset;
9378
var s = (config.multitree
9379
&& ('$orn' in data)
9380
&& data.$orn) || config.orientation;
9381
var w = n.getData('width') + siblingOffset;
9382
var h = n.getData('height') + siblingOffset;
9383
if(!invert)
9384
return this.dispatch(s, h, w);
9385
else
9386
return this.dispatch(s, w, h);
9387
},
9388
9389
/*
9390
Calculates a subtree base size. This is an utility function used by _getBaseSize_
9391
*/
9392
getTreeBaseSize: function(node, level, leaf) {
9393
var size = this.getSize(node, true), baseHeight = 0, that = this;
9394
if(leaf(level, node)) return size;
9395
if(level === 0) return 0;
9396
node.eachSubnode(function(elem) {
9397
baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9398
});
9399
return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9400
},
9401
9402
9403
/*
9404
getEdge
9405
9406
Returns a Complex instance with the begin or end position of the edge to be plotted.
9407
9408
Parameters:
9409
9410
node - A <Graph.Node> that is connected to this edge.
9411
type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9412
9413
Returns:
9414
9415
A <Complex> number specifying the begin or end position.
9416
*/
9417
getEdge: function(node, type, s) {
9418
var $C = function(a, b) {
9419
return function(){
9420
return node.pos.add(new Complex(a, b));
9421
};
9422
};
9423
var dim = this.node;
9424
var w = node.getData('width');
9425
var h = node.getData('height');
9426
9427
if(type == 'begin') {
9428
if(dim.align == "center") {
9429
return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9430
$C(0, -h/2),$C(w/2, 0));
9431
} else if(dim.align == "left") {
9432
return this.dispatch(s, $C(0, h), $C(0, 0),
9433
$C(0, 0), $C(w, 0));
9434
} else if(dim.align == "right") {
9435
return this.dispatch(s, $C(0, 0), $C(-w, 0),
9436
$C(0, -h),$C(0, 0));
9437
} else throw "align: not implemented";
9438
9439
9440
} else if(type == 'end') {
9441
if(dim.align == "center") {
9442
return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9443
$C(0, h/2), $C(-w/2, 0));
9444
} else if(dim.align == "left") {
9445
return this.dispatch(s, $C(0, 0), $C(w, 0),
9446
$C(0, h), $C(0, 0));
9447
} else if(dim.align == "right") {
9448
return this.dispatch(s, $C(0, -h),$C(0, 0),
9449
$C(0, 0), $C(-w, 0));
9450
} else throw "align: not implemented";
9451
}
9452
},
9453
9454
/*
9455
Adjusts the tree position due to canvas scaling or translation.
9456
*/
9457
getScaledTreePosition: function(node, scale) {
9458
var dim = this.node;
9459
var w = node.getData('width');
9460
var h = node.getData('height');
9461
var s = (this.config.multitree
9462
&& ('$orn' in node.data)
9463
&& node.data.$orn) || this.config.orientation;
9464
9465
var $C = function(a, b) {
9466
return function(){
9467
return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9468
};
9469
};
9470
if(dim.align == "left") {
9471
return this.dispatch(s, $C(0, h), $C(0, 0),
9472
$C(0, 0), $C(w, 0));
9473
} else if(dim.align == "center") {
9474
return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9475
$C(0, -h / 2),$C(w / 2, 0));
9476
} else if(dim.align == "right") {
9477
return this.dispatch(s, $C(0, 0), $C(-w, 0),
9478
$C(0, -h),$C(0, 0));
9479
} else throw "align: not implemented";
9480
},
9481
9482
/*
9483
treeFitsInCanvas
9484
9485
Returns a Boolean if the current subtree fits in canvas.
9486
9487
Parameters:
9488
9489
node - A <Graph.Node> which is the current root of the subtree.
9490
canvas - The <Canvas> object.
9491
level - The depth of the subtree to be considered.
9492
*/
9493
treeFitsInCanvas: function(node, canvas, level) {
9494
var csize = canvas.getSize();
9495
var s = (this.config.multitree
9496
&& ('$orn' in node.data)
9497
&& node.data.$orn) || this.config.orientation;
9498
9499
var size = this.dispatch(s, csize.width, csize.height);
9500
var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9501
return level === 0 || !node.anySubnode();
9502
});
9503
return (baseSize < size);
9504
}
9505
});
9506
9507
/*
9508
Class: ST.Plot
9509
9510
Custom extension of <Graph.Plot>.
9511
9512
Extends:
9513
9514
All <Graph.Plot> methods
9515
9516
See also:
9517
9518
<Graph.Plot>
9519
9520
*/
9521
$jit.ST.Plot = new Class({
9522
9523
Implements: Graph.Plot,
9524
9525
/*
9526
Plots a subtree from the spacetree.
9527
*/
9528
plotSubtree: function(node, opt, scale, animating) {
9529
var viz = this.viz, canvas = viz.canvas, config = viz.config;
9530
scale = Math.min(Math.max(0.001, scale), 1);
9531
if(scale >= 0) {
9532
node.drawn = false;
9533
var ctx = canvas.getCtx();
9534
var diff = viz.geom.getScaledTreePosition(node, scale);
9535
ctx.translate(diff.x, diff.y);
9536
ctx.scale(scale, scale);
9537
}
9538
this.plotTree(node, $.merge(opt, {
9539
'withLabels': true,
9540
'hideLabels': !!scale,
9541
'plotSubtree': function(n, ch) {
9542
var root = config.multitree && !('$orn' in node.data);
9543
var orns = root && node.getData('orns');
9544
return !root || orns.indexOf(node.getData('orn')) > -1;
9545
}
9546
}), animating);
9547
if(scale >= 0) node.drawn = true;
9548
},
9549
9550
/*
9551
Method: getAlignedPos
9552
9553
Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9554
9555
Parameters:
9556
9557
pos - (object) A <Graph.Node> position.
9558
width - (number) The width of the node.
9559
height - (number) The height of the node.
9560
9561
*/
9562
getAlignedPos: function(pos, width, height) {
9563
var nconfig = this.node;
9564
var square, orn;
9565
if(nconfig.align == "center") {
9566
square = {
9567
x: pos.x - width / 2,
9568
y: pos.y - height / 2
9569
};
9570
} else if (nconfig.align == "left") {
9571
orn = this.config.orientation;
9572
if(orn == "bottom" || orn == "top") {
9573
square = {
9574
x: pos.x - width / 2,
9575
y: pos.y
9576
};
9577
} else {
9578
square = {
9579
x: pos.x,
9580
y: pos.y - height / 2
9581
};
9582
}
9583
} else if(nconfig.align == "right") {
9584
orn = this.config.orientation;
9585
if(orn == "bottom" || orn == "top") {
9586
square = {
9587
x: pos.x - width / 2,
9588
y: pos.y - height
9589
};
9590
} else {
9591
square = {
9592
x: pos.x - width,
9593
y: pos.y - height / 2
9594
};
9595
}
9596
} else throw "align: not implemented";
9597
9598
return square;
9599
},
9600
9601
getOrientation: function(adj) {
9602
var config = this.config;
9603
var orn = config.orientation;
9604
9605
if(config.multitree) {
9606
var nodeFrom = adj.nodeFrom;
9607
var nodeTo = adj.nodeTo;
9608
orn = (('$orn' in nodeFrom.data)
9609
&& nodeFrom.data.$orn)
9610
|| (('$orn' in nodeTo.data)
9611
&& nodeTo.data.$orn);
9612
}
9613
9614
return orn;
9615
}
9616
});
9617
9618
/*
9619
Class: ST.Label
9620
9621
Custom extension of <Graph.Label>.
9622
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9623
9624
Extends:
9625
9626
All <Graph.Label> methods and subclasses.
9627
9628
See also:
9629
9630
<Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9631
*/
9632
$jit.ST.Label = {};
9633
9634
/*
9635
ST.Label.Native
9636
9637
Custom extension of <Graph.Label.Native>.
9638
9639
Extends:
9640
9641
All <Graph.Label.Native> methods
9642
9643
See also:
9644
9645
<Graph.Label.Native>
9646
*/
9647
$jit.ST.Label.Native = new Class({
9648
Implements: Graph.Label.Native,
9649
9650
renderLabel: function(canvas, node, controller) {
9651
var ctx = canvas.getCtx(),
9652
coord = node.pos.getc(true),
9653
width = node.getData('width'),
9654
height = node.getData('height'),
9655
pos = this.viz.fx.getAlignedPos(coord, width, height);
9656
ctx.fillText(node.name, pos.x + width / 2, pos.y + height / 2);
9657
}
9658
});
9659
9660
$jit.ST.Label.DOM = new Class({
9661
Implements: Graph.Label.DOM,
9662
9663
/*
9664
placeLabel
9665
9666
Overrides abstract method placeLabel in <Graph.Plot>.
9667
9668
Parameters:
9669
9670
tag - A DOM label element.
9671
node - A <Graph.Node>.
9672
controller - A configuration/controller object passed to the visualization.
9673
9674
*/
9675
placeLabel: function(tag, node, controller) {
9676
var pos = node.pos.getc(true),
9677
config = this.viz.config,
9678
dim = config.Node,
9679
canvas = this.viz.canvas,
9680
w = node.getData('width'),
9681
h = node.getData('height'),
9682
radius = canvas.getSize(),
9683
labelPos, orn;
9684
9685
var ox = canvas.translateOffsetX,
9686
oy = canvas.translateOffsetY,
9687
sx = canvas.scaleOffsetX,
9688
sy = canvas.scaleOffsetY,
9689
posx = pos.x * sx + ox,
9690
posy = pos.y * sy + oy;
9691
9692
if(dim.align == "center") {
9693
labelPos= {
9694
x: Math.round(posx - w / 2 + radius.width/2),
9695
y: Math.round(posy - h / 2 + radius.height/2)
9696
};
9697
} else if (dim.align == "left") {
9698
orn = config.orientation;
9699
if(orn == "bottom" || orn == "top") {
9700
labelPos= {
9701
x: Math.round(posx - w / 2 + radius.width/2),
9702
y: Math.round(posy + radius.height/2)
9703
};
9704
} else {
9705
labelPos= {
9706
x: Math.round(posx + radius.width/2),
9707
y: Math.round(posy - h / 2 + radius.height/2)
9708
};
9709
}
9710
} else if(dim.align == "right") {
9711
orn = config.orientation;
9712
if(orn == "bottom" || orn == "top") {
9713
labelPos= {
9714
x: Math.round(posx - w / 2 + radius.width/2),
9715
y: Math.round(posy - h + radius.height/2)
9716
};
9717
} else {
9718
labelPos= {
9719
x: Math.round(posx - w + radius.width/2),
9720
y: Math.round(posy - h / 2 + radius.height/2)
9721
};
9722
}
9723
} else throw "align: not implemented";
9724
9725
var style = tag.style;
9726
style.left = labelPos.x + 'px';
9727
style.top = labelPos.y + 'px';
9728
style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9729
controller.onPlaceLabel(tag, node);
9730
}
9731
});
9732
9733
/*
9734
ST.Label.SVG
9735
9736
Custom extension of <Graph.Label.SVG>.
9737
9738
Extends:
9739
9740
All <Graph.Label.SVG> methods
9741
9742
See also:
9743
9744
<Graph.Label.SVG>
9745
*/
9746
$jit.ST.Label.SVG = new Class({
9747
Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9748
9749
initialize: function(viz) {
9750
this.viz = viz;
9751
}
9752
});
9753
9754
/*
9755
ST.Label.HTML
9756
9757
Custom extension of <Graph.Label.HTML>.
9758
9759
Extends:
9760
9761
All <Graph.Label.HTML> methods.
9762
9763
See also:
9764
9765
<Graph.Label.HTML>
9766
9767
*/
9768
$jit.ST.Label.HTML = new Class({
9769
Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9770
9771
initialize: function(viz) {
9772
this.viz = viz;
9773
}
9774
});
9775
9776
9777
/*
9778
Class: ST.Plot.NodeTypes
9779
9780
This class contains a list of <Graph.Node> built-in types.
9781
Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9782
9783
You can add your custom node types, customizing your visualization to the extreme.
9784
9785
Example:
9786
9787
(start code js)
9788
ST.Plot.NodeTypes.implement({
9789
'mySpecialType': {
9790
'render': function(node, canvas) {
9791
//print your custom node to canvas
9792
},
9793
//optional
9794
'contains': function(node, pos) {
9795
//return true if pos is inside the node or false otherwise
9796
}
9797
}
9798
});
9799
(end code)
9800
9801
*/
9802
$jit.ST.Plot.NodeTypes = new Class({
9803
'none': {
9804
'render': $.empty,
9805
'contains': $.lambda(false)
9806
},
9807
'circle': {
9808
'render': function(node, canvas) {
9809
var dim = node.getData('dim'),
9810
pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9811
dim2 = dim/2;
9812
this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9813
},
9814
'contains': function(node, pos) {
9815
var dim = node.getData('dim'),
9816
npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9817
dim2 = dim/2;
9818
this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9819
}
9820
},
9821
'square': {
9822
'render': function(node, canvas) {
9823
var dim = node.getData('dim'),
9824
dim2 = dim/2,
9825
pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9826
this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9827
},
9828
'contains': function(node, pos) {
9829
var dim = node.getData('dim'),
9830
npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9831
dim2 = dim/2;
9832
this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9833
}
9834
},
9835
'ellipse': {
9836
'render': function(node, canvas) {
9837
var width = node.getData('width'),
9838
height = node.getData('height'),
9839
pos = this.getAlignedPos(node.pos.getc(true), width, height);
9840
this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9841
},
9842
'contains': function(node, pos) {
9843
var width = node.getData('width'),
9844
height = node.getData('height'),
9845
npos = this.getAlignedPos(node.pos.getc(true), width, height);
9846
this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9847
}
9848
},
9849
'rectangle': {
9850
'render': function(node, canvas) {
9851
var width = node.getData('width'),
9852
height = node.getData('height'),
9853
pos = this.getAlignedPos(node.pos.getc(true), width, height);
9854
this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9855
},
9856
'contains': function(node, pos) {
9857
var width = node.getData('width'),
9858
height = node.getData('height'),
9859
npos = this.getAlignedPos(node.pos.getc(true), width, height);
9860
this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9861
}
9862
}
9863
});
9864
9865
/*
9866
Class: ST.Plot.EdgeTypes
9867
9868
This class contains a list of <Graph.Adjacence> built-in types.
9869
Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9870
9871
You can add your custom edge types, customizing your visualization to the extreme.
9872
9873
Example:
9874
9875
(start code js)
9876
ST.Plot.EdgeTypes.implement({
9877
'mySpecialType': {
9878
'render': function(adj, canvas) {
9879
//print your custom edge to canvas
9880
},
9881
//optional
9882
'contains': function(adj, pos) {
9883
//return true if pos is inside the arc or false otherwise
9884
}
9885
}
9886
});
9887
(end code)
9888
9889
*/
9890
$jit.ST.Plot.EdgeTypes = new Class({
9891
'none': $.empty,
9892
'line': {
9893
'render': function(adj, canvas) {
9894
var orn = this.getOrientation(adj),
9895
nodeFrom = adj.nodeFrom,
9896
nodeTo = adj.nodeTo,
9897
rel = nodeFrom._depth < nodeTo._depth,
9898
from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9899
to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9900
this.edgeHelper.line.render(from, to, canvas);
9901
},
9902
'contains': function(adj, pos) {
9903
var orn = this.getOrientation(adj),
9904
nodeFrom = adj.nodeFrom,
9905
nodeTo = adj.nodeTo,
9906
rel = nodeFrom._depth < nodeTo._depth,
9907
from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9908
to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9909
return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9910
}
9911
},
9912
'arrow': {
9913
'render': function(adj, canvas) {
9914
var orn = this.getOrientation(adj),
9915
node = adj.nodeFrom,
9916
child = adj.nodeTo,
9917
dim = adj.getData('dim'),
9918
from = this.viz.geom.getEdge(node, 'begin', orn),
9919
to = this.viz.geom.getEdge(child, 'end', orn),
9920
direction = adj.data.$direction,
9921
inv = (direction && direction.length>1 && direction[0] != node.id);
9922
this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9923
},
9924
'contains': function(adj, pos) {
9925
var orn = this.getOrientation(adj),
9926
nodeFrom = adj.nodeFrom,
9927
nodeTo = adj.nodeTo,
9928
rel = nodeFrom._depth < nodeTo._depth,
9929
from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9930
to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9931
return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9932
}
9933
},
9934
'quadratic:begin': {
9935
'render': function(adj, canvas) {
9936
var orn = this.getOrientation(adj);
9937
var nodeFrom = adj.nodeFrom,
9938
nodeTo = adj.nodeTo,
9939
rel = nodeFrom._depth < nodeTo._depth,
9940
begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9941
end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9942
dim = adj.getData('dim'),
9943
ctx = canvas.getCtx();
9944
ctx.beginPath();
9945
ctx.moveTo(begin.x, begin.y);
9946
switch(orn) {
9947
case "left":
9948
ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9949
break;
9950
case "right":
9951
ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9952
break;
9953
case "top":
9954
ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9955
break;
9956
case "bottom":
9957
ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9958
break;
9959
}
9960
ctx.stroke();
9961
}
9962
},
9963
'quadratic:end': {
9964
'render': function(adj, canvas) {
9965
var orn = this.getOrientation(adj);
9966
var nodeFrom = adj.nodeFrom,
9967
nodeTo = adj.nodeTo,
9968
rel = nodeFrom._depth < nodeTo._depth,
9969
begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9970
end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9971
dim = adj.getData('dim'),
9972
ctx = canvas.getCtx();
9973
ctx.beginPath();
9974
ctx.moveTo(begin.x, begin.y);
9975
switch(orn) {
9976
case "left":
9977
ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9978
break;
9979
case "right":
9980
ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9981
break;
9982
case "top":
9983
ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9984
break;
9985
case "bottom":
9986
ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9987
break;
9988
}
9989
ctx.stroke();
9990
}
9991
},
9992
'bezier': {
9993
'render': function(adj, canvas) {
9994
var orn = this.getOrientation(adj),
9995
nodeFrom = adj.nodeFrom,
9996
nodeTo = adj.nodeTo,
9997
rel = nodeFrom._depth < nodeTo._depth,
9998
begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9999
end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10000
dim = adj.getData('dim'),
10001
ctx = canvas.getCtx();
10002
ctx.beginPath();
10003
ctx.moveTo(begin.x, begin.y);
10004
switch(orn) {
10005
case "left":
10006
ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
10007
break;
10008
case "right":
10009
ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
10010
break;
10011
case "top":
10012
ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
10013
break;
10014
case "bottom":
10015
ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
10016
break;
10017
}
10018
ctx.stroke();
10019
}
10020
}
10021
});
10022
10023
10024
10025
/*
10026
* File: AreaChart.js
10027
*
10028
*/
10029
10030
$jit.ST.Plot.NodeTypes.implement({
10031
'areachart-stacked' : {
10032
'render' : function(node, canvas) {
10033
var pos = node.pos.getc(true),
10034
width = node.getData('width'),
10035
height = node.getData('height'),
10036
algnPos = this.getAlignedPos(pos, width, height),
10037
x = algnPos.x, y = algnPos.y,
10038
stringArray = node.getData('stringArray'),
10039
dimArray = node.getData('dimArray'),
10040
valArray = node.getData('valueArray'),
10041
valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10042
valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10043
colorArray = node.getData('colorArray'),
10044
colorLength = colorArray.length,
10045
config = node.getData('config'),
10046
gradient = node.getData('gradient'),
10047
showLabels = config.showLabels,
10048
aggregates = config.showAggregates,
10049
label = config.Label,
10050
prev = node.getData('prev');
10051
10052
var ctx = canvas.getCtx(), border = node.getData('border');
10053
if (colorArray && dimArray && stringArray) {
10054
for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10055
ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10056
ctx.save();
10057
if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10058
var h1 = acumLeft + dimArray[i][0],
10059
h2 = acumRight + dimArray[i][1],
10060
alpha = Math.atan((h2 - h1) / width),
10061
delta = 55;
10062
var linear = ctx.createLinearGradient(x + width/2,
10063
y - (h1 + h2)/2,
10064
x + width/2 + delta * Math.sin(alpha),
10065
y - (h1 + h2)/2 + delta * Math.cos(alpha));
10066
var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10067
function(v) { return (v * 0.85) >> 0; }));
10068
linear.addColorStop(0, colorArray[i % colorLength]);
10069
linear.addColorStop(1, color);
10070
ctx.fillStyle = linear;
10071
}
10072
ctx.beginPath();
10073
ctx.moveTo(x, y - acumLeft);
10074
ctx.lineTo(x + width, y - acumRight);
10075
ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10076
ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10077
ctx.lineTo(x, y - acumLeft);
10078
ctx.fill();
10079
ctx.restore();
10080
if(border) {
10081
var strong = border.name == stringArray[i];
10082
var perc = strong? 0.7 : 0.8;
10083
var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10084
function(v) { return (v * perc) >> 0; }));
10085
ctx.strokeStyle = color;
10086
ctx.lineWidth = strong? 4 : 1;
10087
ctx.save();
10088
ctx.beginPath();
10089
if(border.index === 0) {
10090
ctx.moveTo(x, y - acumLeft);
10091
ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10092
} else {
10093
ctx.moveTo(x + width, y - acumRight);
10094
ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10095
}
10096
ctx.stroke();
10097
ctx.restore();
10098
}
10099
acumLeft += (dimArray[i][0] || 0);
10100
acumRight += (dimArray[i][1] || 0);
10101
10102
if(dimArray[i][0] > 0)
10103
valAcum += (valArray[i][0] || 0);
10104
}
10105
if(prev && label.type == 'Native') {
10106
ctx.save();
10107
ctx.beginPath();
10108
ctx.fillStyle = ctx.strokeStyle = label.color;
10109
ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10110
ctx.textAlign = 'center';
10111
ctx.textBaseline = 'middle';
10112
var aggValue = aggregates(node.name, valLeft, valRight, node, valAcum);
10113
if(aggValue !== false) {
10114
ctx.fillText(aggValue !== true? aggValue : valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10115
}
10116
if(showLabels(node.name, valLeft, valRight, node)) {
10117
ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10118
}
10119
ctx.restore();
10120
}
10121
}
10122
},
10123
'contains': function(node, mpos) {
10124
var pos = node.pos.getc(true),
10125
width = node.getData('width'),
10126
height = node.getData('height'),
10127
algnPos = this.getAlignedPos(pos, width, height),
10128
x = algnPos.x, y = algnPos.y,
10129
dimArray = node.getData('dimArray'),
10130
rx = mpos.x - x;
10131
//bounding box check
10132
if(mpos.x < x || mpos.x > x + width
10133
|| mpos.y > y || mpos.y < y - height) {
10134
return false;
10135
}
10136
//deep check
10137
for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10138
var dimi = dimArray[i];
10139
lAcum -= dimi[0];
10140
rAcum -= dimi[1];
10141
var intersec = lAcum + (rAcum - lAcum) * rx / width;
10142
if(mpos.y >= intersec) {
10143
var index = +(rx > width/2);
10144
return {
10145
'name': node.getData('stringArray')[i],
10146
'color': node.getData('colorArray')[i],
10147
'value': node.getData('valueArray')[i][index],
10148
'index': index
10149
};
10150
}
10151
}
10152
return false;
10153
}
10154
}
10155
});
10156
10157
/*
10158
Class: AreaChart
10159
10160
A visualization that displays stacked area charts.
10161
10162
Constructor Options:
10163
10164
See <Options.AreaChart>.
10165
10166
*/
10167
$jit.AreaChart = new Class({
10168
st: null,
10169
colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10170
selected: {},
10171
busy: false,
10172
10173
initialize: function(opt) {
10174
this.controller = this.config =
10175
$.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10176
Label: { type: 'Native' }
10177
}, opt);
10178
//set functions for showLabels and showAggregates
10179
var showLabels = this.config.showLabels,
10180
typeLabels = $.type(showLabels),
10181
showAggregates = this.config.showAggregates,
10182
typeAggregates = $.type(showAggregates);
10183
this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10184
this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10185
10186
this.initializeViz();
10187
},
10188
10189
initializeViz: function() {
10190
var config = this.config,
10191
that = this,
10192
nodeType = config.type.split(":")[0],
10193
nodeLabels = {};
10194
10195
var delegate = new $jit.ST({
10196
injectInto: config.injectInto,
10197
width: config.width,
10198
height: config.height,
10199
orientation: "bottom",
10200
levelDistance: 0,
10201
siblingOffset: 0,
10202
subtreeOffset: 0,
10203
withLabels: config.Label.type != 'Native',
10204
useCanvas: config.useCanvas,
10205
Label: {
10206
type: config.Label.type
10207
},
10208
Node: {
10209
overridable: true,
10210
type: 'areachart-' + nodeType,
10211
align: 'left',
10212
width: 1,
10213
height: 1
10214
},
10215
Edge: {
10216
type: 'none'
10217
},
10218
Tips: {
10219
enable: config.Tips.enable,
10220
type: 'Native',
10221
force: true,
10222
onShow: function(tip, node, contains) {
10223
var elem = contains;
10224
config.Tips.onShow(tip, elem, node);
10225
}
10226
},
10227
Events: {
10228
enable: true,
10229
type: 'Native',
10230
onClick: function(node, eventInfo, evt) {
10231
if(!config.filterOnClick && !config.Events.enable) return;
10232
var elem = eventInfo.getContains();
10233
if(elem) config.filterOnClick && that.filter(elem.name);
10234
config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10235
},
10236
onRightClick: function(node, eventInfo, evt) {
10237
if(!config.restoreOnRightClick) return;
10238
that.restore();
10239
},
10240
onMouseMove: function(node, eventInfo, evt) {
10241
if(!config.selectOnHover) return;
10242
if(node) {
10243
var elem = eventInfo.getContains();
10244
that.select(node.id, elem.name, elem.index);
10245
} else {
10246
that.select(false, false, false);
10247
}
10248
}
10249
},
10250
onCreateLabel: function(domElement, node) {
10251
var labelConf = config.Label,
10252
valueArray = node.getData('valueArray'),
10253
acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10254
acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10255
if(node.getData('prev')) {
10256
var nlbs = {
10257
wrapper: document.createElement('div'),
10258
aggregate: document.createElement('div'),
10259
label: document.createElement('div')
10260
};
10261
var wrapper = nlbs.wrapper,
10262
label = nlbs.label,
10263
aggregate = nlbs.aggregate,
10264
wrapperStyle = wrapper.style,
10265
labelStyle = label.style,
10266
aggregateStyle = aggregate.style;
10267
//store node labels
10268
nodeLabels[node.id] = nlbs;
10269
//append labels
10270
wrapper.appendChild(label);
10271
wrapper.appendChild(aggregate);
10272
if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10273
label.style.display = 'none';
10274
}
10275
if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10276
aggregate.style.display = 'none';
10277
}
10278
wrapperStyle.position = 'relative';
10279
wrapperStyle.overflow = 'visible';
10280
wrapperStyle.fontSize = labelConf.size + 'px';
10281
wrapperStyle.fontFamily = labelConf.family;
10282
wrapperStyle.color = labelConf.color;
10283
wrapperStyle.textAlign = 'center';
10284
aggregateStyle.position = labelStyle.position = 'absolute';
10285
10286
domElement.style.width = node.getData('width') + 'px';
10287
domElement.style.height = node.getData('height') + 'px';
10288
label.innerHTML = node.name;
10289
10290
domElement.appendChild(wrapper);
10291
}
10292
},
10293
onPlaceLabel: function(domElement, node) {
10294
if(!node.getData('prev')) return;
10295
var labels = nodeLabels[node.id],
10296
wrapperStyle = labels.wrapper.style,
10297
labelStyle = labels.label.style,
10298
aggregateStyle = labels.aggregate.style,
10299
width = node.getData('width'),
10300
height = node.getData('height'),
10301
dimArray = node.getData('dimArray'),
10302
valArray = node.getData('valueArray'),
10303
acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10304
acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10305
font = parseInt(wrapperStyle.fontSize, 10),
10306
domStyle = domElement.style;
10307
10308
if(dimArray && valArray) {
10309
if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10310
labelStyle.display = '';
10311
} else {
10312
labelStyle.display = 'none';
10313
}
10314
var aggValue = config.showAggregates(node.name, acumLeft, acumRight, node);
10315
if(aggValue !== false) {
10316
aggregateStyle.display = '';
10317
} else {
10318
aggregateStyle.display = 'none';
10319
}
10320
wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10321
aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10322
for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10323
if(dimArray[i][0] > 0) {
10324
acum+= valArray[i][0];
10325
leftAcum+= dimArray[i][0];
10326
}
10327
}
10328
aggregateStyle.top = (-font - config.labelOffset) + 'px';
10329
labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10330
domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10331
domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10332
labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
10333
}
10334
}
10335
});
10336
10337
var size = delegate.canvas.getSize(),
10338
margin = config.Margin;
10339
delegate.config.offsetY = -size.height/2 + margin.bottom
10340
+ (config.showLabels && (config.labelOffset + config.Label.size));
10341
delegate.config.offsetX = (margin.right - margin.left)/2;
10342
this.delegate = delegate;
10343
this.canvas = this.delegate.canvas;
10344
},
10345
10346
/*
10347
Method: loadJSON
10348
10349
Loads JSON data into the visualization.
10350
10351
Parameters:
10352
10353
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>.
10354
10355
Example:
10356
(start code js)
10357
var areaChart = new $jit.AreaChart(options);
10358
areaChart.loadJSON(json);
10359
(end code)
10360
*/
10361
loadJSON: function(json) {
10362
var prefix = $.time(),
10363
ch = [],
10364
delegate = this.delegate,
10365
name = $.splat(json.label),
10366
color = $.splat(json.color || this.colors),
10367
config = this.config,
10368
gradient = !!config.type.split(":")[1],
10369
animate = config.animate;
10370
10371
for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
10372
var val = values[i], prev = values[i-1], next = values[i+1];
10373
var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
10374
var valArray = $.zip(valLeft, valRight);
10375
var acumLeft = 0, acumRight = 0;
10376
ch.push({
10377
'id': prefix + val.label,
10378
'name': val.label,
10379
'data': {
10380
'value': valArray,
10381
'$valueArray': valArray,
10382
'$colorArray': color,
10383
'$stringArray': name,
10384
'$next': next.label,
10385
'$prev': prev? prev.label:false,
10386
'$config': config,
10387
'$gradient': gradient
10388
},
10389
'children': []
10390
});
10391
}
10392
var root = {
10393
'id': prefix + '$root',
10394
'name': '',
10395
'data': {
10396
'$type': 'none',
10397
'$width': 1,
10398
'$height': 1
10399
},
10400
'children': ch
10401
};
10402
delegate.loadJSON(root);
10403
10404
this.normalizeDims();
10405
delegate.compute();
10406
delegate.select(delegate.root);
10407
if(animate) {
10408
delegate.fx.animate({
10409
modes: ['node-property:height:dimArray'],
10410
duration:1500
10411
});
10412
}
10413
},
10414
10415
/*
10416
Method: updateJSON
10417
10418
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.
10419
10420
Parameters:
10421
10422
json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10423
onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10424
10425
Example:
10426
10427
(start code js)
10428
areaChart.updateJSON(json, {
10429
onComplete: function() {
10430
alert('update complete!');
10431
}
10432
});
10433
(end code)
10434
*/
10435
updateJSON: function(json, onComplete) {
10436
if(this.busy) return;
10437
this.busy = true;
10438
10439
var delegate = this.delegate,
10440
graph = delegate.graph,
10441
labels = json.label && $.splat(json.label),
10442
values = json.values,
10443
animate = this.config.animate,
10444
that = this,
10445
hashValues = {};
10446
10447
//convert the whole thing into a hash
10448
for (var i = 0, l = values.length; i < l; i++) {
10449
hashValues[values[i].label] = values[i];
10450
}
10451
10452
graph.eachNode(function(n) {
10453
var v = hashValues[n.name],
10454
stringArray = n.getData('stringArray'),
10455
valArray = n.getData('valueArray'),
10456
next = n.getData('next');
10457
10458
if (v) {
10459
v.values = $.splat(v.values);
10460
$.each(valArray, function(a, i) {
10461
a[0] = v.values[i];
10462
if(labels) stringArray[i] = labels[i];
10463
});
10464
n.setData('valueArray', valArray);
10465
}
10466
10467
if(next) {
10468
v = hashValues[next];
10469
if(v) {
10470
$.each(valArray, function(a, i) {
10471
a[1] = v.values[i];
10472
});
10473
}
10474
}
10475
});
10476
this.normalizeDims();
10477
delegate.compute();
10478
delegate.select(delegate.root);
10479
if(animate) {
10480
delegate.fx.animate({
10481
modes: ['node-property:height:dimArray'],
10482
duration:1500,
10483
onComplete: function() {
10484
that.busy = false;
10485
onComplete && onComplete.onComplete();
10486
}
10487
});
10488
}
10489
},
10490
10491
/*
10492
Method: filter
10493
10494
Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10495
10496
Parameters:
10497
10498
filters - (array) An array of strings with the name of the stacks to be filtered.
10499
callback - (object) An object with an *onComplete* callback method.
10500
10501
Example:
10502
10503
(start code js)
10504
areaChart.filter(['label A', 'label C'], {
10505
onComplete: function() {
10506
console.log('done!');
10507
}
10508
});
10509
(end code)
10510
10511
See also:
10512
10513
<AreaChart.restore>.
10514
*/
10515
filter: function(filters, callback) {
10516
if(this.busy) return;
10517
this.busy = true;
10518
if(this.config.Tips.enable) this.delegate.tips.hide();
10519
this.select(false, false, false);
10520
var args = $.splat(filters);
10521
var rt = this.delegate.graph.getNode(this.delegate.root);
10522
var that = this;
10523
this.normalizeDims();
10524
rt.eachAdjacency(function(adj) {
10525
var n = adj.nodeTo,
10526
dimArray = n.getData('dimArray', 'end'),
10527
stringArray = n.getData('stringArray');
10528
n.setData('dimArray', $.map(dimArray, function(d, i) {
10529
return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10530
}), 'end');
10531
});
10532
this.delegate.fx.animate({
10533
modes: ['node-property:dimArray'],
10534
duration:1500,
10535
onComplete: function() {
10536
that.busy = false;
10537
callback && callback.onComplete();
10538
}
10539
});
10540
},
10541
10542
/*
10543
Method: restore
10544
10545
Sets all stacks that could have been filtered visible.
10546
10547
Example:
10548
10549
(start code js)
10550
areaChart.restore();
10551
(end code)
10552
10553
See also:
10554
10555
<AreaChart.filter>.
10556
*/
10557
restore: function(callback) {
10558
if(this.busy) return;
10559
this.busy = true;
10560
if(this.config.Tips.enable) this.delegate.tips.hide();
10561
this.select(false, false, false);
10562
this.normalizeDims();
10563
var that = this;
10564
this.delegate.fx.animate({
10565
modes: ['node-property:height:dimArray'],
10566
duration:1500,
10567
onComplete: function() {
10568
that.busy = false;
10569
callback && callback.onComplete();
10570
}
10571
});
10572
},
10573
//adds the little brown bar when hovering the node
10574
select: function(id, name, index) {
10575
if(!this.config.selectOnHover) return;
10576
var s = this.selected;
10577
if(s.id != id || s.name != name
10578
|| s.index != index) {
10579
s.id = id;
10580
s.name = name;
10581
s.index = index;
10582
this.delegate.graph.eachNode(function(n) {
10583
n.setData('border', false);
10584
});
10585
if(id) {
10586
var n = this.delegate.graph.getNode(id);
10587
n.setData('border', s);
10588
var link = index === 0? 'prev':'next';
10589
link = n.getData(link);
10590
if(link) {
10591
n = this.delegate.graph.getByName(link);
10592
if(n) {
10593
n.setData('border', {
10594
name: name,
10595
index: 1-index
10596
});
10597
}
10598
}
10599
}
10600
this.delegate.plot();
10601
}
10602
},
10603
10604
/*
10605
Method: getLegend
10606
10607
Returns an object containing as keys the legend names and as values hex strings with color values.
10608
10609
Example:
10610
10611
(start code js)
10612
var legend = areaChart.getLegend();
10613
(end code)
10614
*/
10615
getLegend: function() {
10616
var legend = {};
10617
var n;
10618
this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
10619
n = adj.nodeTo;
10620
});
10621
var colors = n.getData('colorArray'),
10622
len = colors.length;
10623
$.each(n.getData('stringArray'), function(s, i) {
10624
legend[s] = colors[i % len];
10625
});
10626
return legend;
10627
},
10628
10629
/*
10630
Method: getMaxValue
10631
10632
Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10633
10634
Example:
10635
10636
(start code js)
10637
var ans = areaChart.getMaxValue();
10638
(end code)
10639
10640
In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10641
10642
Example:
10643
10644
(start code js)
10645
//will return 100 for all AreaChart instances,
10646
//displaying all of them with the same scale
10647
$jit.AreaChart.implement({
10648
'getMaxValue': function() {
10649
return 100;
10650
}
10651
});
10652
(end code)
10653
10654
*/
10655
getMaxValue: function() {
10656
var maxValue = 0;
10657
this.delegate.graph.eachNode(function(n) {
10658
var valArray = n.getData('valueArray'),
10659
acumLeft = 0, acumRight = 0;
10660
$.each(valArray, function(v) {
10661
acumLeft += +v[0];
10662
acumRight += +v[1];
10663
});
10664
var acum = acumRight>acumLeft? acumRight:acumLeft;
10665
maxValue = maxValue>acum? maxValue:acum;
10666
});
10667
return maxValue;
10668
},
10669
10670
normalizeDims: function() {
10671
//number of elements
10672
var root = this.delegate.graph.getNode(this.delegate.root), l=0;
10673
root.eachAdjacency(function() {
10674
l++;
10675
});
10676
var maxValue = this.getMaxValue() || 1,
10677
size = this.delegate.canvas.getSize(),
10678
config = this.config,
10679
margin = config.Margin,
10680
labelOffset = config.labelOffset + config.Label.size,
10681
fixedDim = (size.width - (margin.left + margin.right)) / l,
10682
animate = config.animate,
10683
height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10684
- (config.showLabels && labelOffset);
10685
this.delegate.graph.eachNode(function(n) {
10686
var acumLeft = 0, acumRight = 0, animateValue = [];
10687
$.each(n.getData('valueArray'), function(v) {
10688
acumLeft += +v[0];
10689
acumRight += +v[1];
10690
animateValue.push([0, 0]);
10691
});
10692
var acum = acumRight>acumLeft? acumRight:acumLeft;
10693
n.setData('width', fixedDim);
10694
if(animate) {
10695
n.setData('height', acum * height / maxValue, 'end');
10696
n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10697
return [n[0] * height / maxValue, n[1] * height / maxValue];
10698
}), 'end');
10699
var dimArray = n.getData('dimArray');
10700
if(!dimArray) {
10701
n.setData('dimArray', animateValue);
10702
}
10703
} else {
10704
n.setData('height', acum * height / maxValue);
10705
n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10706
return [n[0] * height / maxValue, n[1] * height / maxValue];
10707
}));
10708
}
10709
});
10710
}
10711
});
10712
10713
10714
/*
10715
* File: Options.BarChart.js
10716
*
10717
*/
10718
10719
/*
10720
Object: Options.BarChart
10721
10722
<BarChart> options.
10723
Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
10724
10725
Syntax:
10726
10727
(start code js)
10728
10729
Options.BarChart = {
10730
animate: true,
10731
labelOffset: 3,
10732
barsOffset: 0,
10733
type: 'stacked',
10734
hoveredColor: '#9fd4ff',
10735
orientation: 'horizontal',
10736
showAggregates: true,
10737
showLabels: true
10738
};
10739
10740
(end code)
10741
10742
Example:
10743
10744
(start code js)
10745
10746
var barChart = new $jit.BarChart({
10747
animate: true,
10748
barsOffset: 10,
10749
type: 'stacked:gradient'
10750
});
10751
10752
(end code)
10753
10754
Parameters:
10755
10756
animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
10757
offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
10758
labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
10759
barsOffset - (number) Default's *0*. Separation between bars.
10760
type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
10761
hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
10762
orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
10763
showAggregates - (boolean, function) Default's *true*. Display the sum the values of each bar. Can also be a function that returns *true* or *false* to display the value of the bar or not. That same function can also return a string with the formatted data to be added.
10764
showLabels - (boolean, function) Default's *true*. Display the name of the slots. Can also be a function that returns *true* or *false* for each bar to decide whether to show the label or not.
10765
10766
*/
10767
10768
Options.BarChart = {
10769
$extend: true,
10770
10771
animate: true,
10772
type: 'stacked', //stacked, grouped, : gradient
10773
labelOffset: 3, //label offset
10774
barsOffset: 0, //distance between bars
10775
hoveredColor: '#9fd4ff',
10776
orientation: 'horizontal',
10777
showAggregates: true,
10778
showLabels: true,
10779
Tips: {
10780
enable: false,
10781
onShow: $.empty,
10782
onHide: $.empty
10783
},
10784
Events: {
10785
enable: false,
10786
onClick: $.empty
10787
}
10788
};
10789
10790
/*
10791
* File: BarChart.js
10792
*
10793
*/
10794
10795
$jit.ST.Plot.NodeTypes.implement({
10796
'barchart-stacked' : {
10797
'render' : function(node, canvas) {
10798
var pos = node.pos.getc(true),
10799
width = node.getData('width'),
10800
height = node.getData('height'),
10801
algnPos = this.getAlignedPos(pos, width, height),
10802
x = algnPos.x, y = algnPos.y,
10803
dimArray = node.getData('dimArray'),
10804
valueArray = node.getData('valueArray'),
10805
colorArray = node.getData('colorArray'),
10806
colorLength = colorArray.length,
10807
stringArray = node.getData('stringArray');
10808
10809
var ctx = canvas.getCtx(),
10810
opt = {},
10811
border = node.getData('border'),
10812
gradient = node.getData('gradient'),
10813
config = node.getData('config'),
10814
horz = config.orientation == 'horizontal',
10815
aggregates = config.showAggregates,
10816
showLabels = config.showLabels,
10817
label = config.Label;
10818
10819
if (colorArray && dimArray && stringArray) {
10820
for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
10821
ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10822
if(gradient) {
10823
var linear;
10824
if(horz) {
10825
linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
10826
x + acum + dimArray[i]/2, y + height);
10827
} else {
10828
linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
10829
x + width, y - acum- dimArray[i]/2);
10830
}
10831
var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10832
function(v) { return (v * 0.5) >> 0; }));
10833
linear.addColorStop(0, color);
10834
linear.addColorStop(0.5, colorArray[i % colorLength]);
10835
linear.addColorStop(1, color);
10836
ctx.fillStyle = linear;
10837
}
10838
if(horz) {
10839
ctx.fillRect(x + acum, y, dimArray[i], height);
10840
} else {
10841
ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
10842
}
10843
if(border && border.name == stringArray[i]) {
10844
opt.acum = acum;
10845
opt.dimValue = dimArray[i];
10846
}
10847
acum += (dimArray[i] || 0);
10848
valAcum += (valueArray[i] || 0);
10849
}
10850
if(border) {
10851
ctx.save();
10852
ctx.lineWidth = 2;
10853
ctx.strokeStyle = border.color;
10854
if(horz) {
10855
ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
10856
} else {
10857
ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
10858
}
10859
ctx.restore();
10860
}
10861
if(label.type == 'Native') {
10862
ctx.save();
10863
ctx.fillStyle = ctx.strokeStyle = label.color;
10864
ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10865
ctx.textBaseline = 'middle';
10866
var aggValue = aggregates(node.name, valAcum, node);
10867
if(aggValue !== false) {
10868
aggValue = aggValue !== true? aggValue : valAcum;
10869
if(horz) {
10870
ctx.textAlign = 'right';
10871
ctx.fillText(aggValue, x + acum - config.labelOffset, y + height/2);
10872
} else {
10873
ctx.textAlign = 'center';
10874
ctx.fillText(aggValue, x + width/2, y - height - label.size/2 - config.labelOffset);
10875
}
10876
}
10877
if(showLabels(node.name, valAcum, node)) {
10878
if(horz) {
10879
ctx.textAlign = 'center';
10880
ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
10881
ctx.rotate(Math.PI / 2);
10882
ctx.fillText(node.name, 0, 0);
10883
} else {
10884
ctx.textAlign = 'center';
10885
ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
10886
}
10887
}
10888
ctx.restore();
10889
}
10890
}
10891
},
10892
'contains': function(node, mpos) {
10893
var pos = node.pos.getc(true),
10894
width = node.getData('width'),
10895
height = node.getData('height'),
10896
algnPos = this.getAlignedPos(pos, width, height),
10897
x = algnPos.x, y = algnPos.y,
10898
dimArray = node.getData('dimArray'),
10899
config = node.getData('config'),
10900
rx = mpos.x - x,
10901
horz = config.orientation == 'horizontal';
10902
//bounding box check
10903
if(horz) {
10904
if(mpos.x < x || mpos.x > x + width
10905
|| mpos.y > y + height || mpos.y < y) {
10906
return false;
10907
}
10908
} else {
10909
if(mpos.x < x || mpos.x > x + width
10910
|| mpos.y > y || mpos.y < y - height) {
10911
return false;
10912
}
10913
}
10914
//deep check
10915
for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
10916
var dimi = dimArray[i];
10917
if(horz) {
10918
acum += dimi;
10919
var intersec = acum;
10920
if(mpos.x <= intersec) {
10921
return {
10922
'name': node.getData('stringArray')[i],
10923
'color': node.getData('colorArray')[i],
10924
'value': node.getData('valueArray')[i],
10925
'label': node.name
10926
};
10927
}
10928
} else {
10929
acum -= dimi;
10930
var intersec = acum;
10931
if(mpos.y >= intersec) {
10932
return {
10933
'name': node.getData('stringArray')[i],
10934
'color': node.getData('colorArray')[i],
10935
'value': node.getData('valueArray')[i],
10936
'label': node.name
10937
};
10938
}
10939
}
10940
}
10941
return false;
10942
}
10943
},
10944
'barchart-grouped' : {
10945
'render' : function(node, canvas) {
10946
var pos = node.pos.getc(true),
10947
width = node.getData('width'),
10948
height = node.getData('height'),
10949
algnPos = this.getAlignedPos(pos, width, height),
10950
x = algnPos.x, y = algnPos.y,
10951
dimArray = node.getData('dimArray'),
10952
valueArray = node.getData('valueArray'),
10953
valueLength = valueArray.length,
10954
colorArray = node.getData('colorArray'),
10955
colorLength = colorArray.length,
10956
stringArray = node.getData('stringArray');
10957
10958
var ctx = canvas.getCtx(),
10959
opt = {},
10960
border = node.getData('border'),
10961
gradient = node.getData('gradient'),
10962
config = node.getData('config'),
10963
horz = config.orientation == 'horizontal',
10964
aggregates = config.showAggregates,
10965
showLabels = config.showLabels,
10966
label = config.Label,
10967
fixedDim = (horz? height : width) / valueLength;
10968
10969
if (colorArray && dimArray && stringArray) {
10970
for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
10971
ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10972
if(gradient) {
10973
var linear;
10974
if(horz) {
10975
linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
10976
x + dimArray[i]/2, y + fixedDim * (i + 1));
10977
} else {
10978
linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
10979
x + fixedDim * (i + 1), y - dimArray[i]/2);
10980
}
10981
var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10982
function(v) { return (v * 0.5) >> 0; }));
10983
linear.addColorStop(0, color);
10984
linear.addColorStop(0.5, colorArray[i % colorLength]);
10985
linear.addColorStop(1, color);
10986
ctx.fillStyle = linear;
10987
}
10988
if(horz) {
10989
ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
10990
} else {
10991
ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
10992
}
10993
if(border && border.name == stringArray[i]) {
10994
opt.acum = fixedDim * i;
10995
opt.dimValue = dimArray[i];
10996
}
10997
acum += (dimArray[i] || 0);
10998
valAcum += (valueArray[i] || 0);
10999
}
11000
if(border) {
11001
ctx.save();
11002
ctx.lineWidth = 2;
11003
ctx.strokeStyle = border.color;
11004
if(horz) {
11005
ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
11006
} else {
11007
ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
11008
}
11009
ctx.restore();
11010
}
11011
if(label.type == 'Native') {
11012
ctx.save();
11013
ctx.fillStyle = ctx.strokeStyle = label.color;
11014
ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11015
ctx.textBaseline = 'middle';
11016
var aggValue = aggregates(node.name, valAcum, node);
11017
if(aggValue !== false) {
11018
aggValue = aggValue !== true? aggValue : valAcum;
11019
if(horz) {
11020
ctx.textAlign = 'right';
11021
ctx.fillText(aggValue, x + Math.max.apply(null, dimArray) - config.labelOffset, y + height/2);
11022
} else {
11023
ctx.textAlign = 'center';
11024
ctx.fillText(aggValue, x + width/2, y - Math.max.apply(null, dimArray) - label.size/2 - config.labelOffset);
11025
}
11026
}
11027
if(showLabels(node.name, valAcum, node)) {
11028
if(horz) {
11029
ctx.textAlign = 'center';
11030
ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
11031
ctx.rotate(Math.PI / 2);
11032
ctx.fillText(node.name, 0, 0);
11033
} else {
11034
ctx.textAlign = 'center';
11035
ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11036
}
11037
}
11038
ctx.restore();
11039
}
11040
}
11041
},
11042
'contains': function(node, mpos) {
11043
var pos = node.pos.getc(true),
11044
width = node.getData('width'),
11045
height = node.getData('height'),
11046
algnPos = this.getAlignedPos(pos, width, height),
11047
x = algnPos.x, y = algnPos.y,
11048
dimArray = node.getData('dimArray'),
11049
len = dimArray.length,
11050
config = node.getData('config'),
11051
rx = mpos.x - x,
11052
horz = config.orientation == 'horizontal',
11053
fixedDim = (horz? height : width) / len;
11054
//bounding box check
11055
if(horz) {
11056
if(mpos.x < x || mpos.x > x + width
11057
|| mpos.y > y + height || mpos.y < y) {
11058
return false;
11059
}
11060
} else {
11061
if(mpos.x < x || mpos.x > x + width
11062
|| mpos.y > y || mpos.y < y - height) {
11063
return false;
11064
}
11065
}
11066
//deep check
11067
for(var i=0, l=dimArray.length; i<l; i++) {
11068
var dimi = dimArray[i];
11069
if(horz) {
11070
var limit = y + fixedDim * i;
11071
if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
11072
return {
11073
'name': node.getData('stringArray')[i],
11074
'color': node.getData('colorArray')[i],
11075
'value': node.getData('valueArray')[i],
11076
'label': node.name
11077
};
11078
}
11079
} else {
11080
var limit = x + fixedDim * i;
11081
if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
11082
return {
11083
'name': node.getData('stringArray')[i],
11084
'color': node.getData('colorArray')[i],
11085
'value': node.getData('valueArray')[i],
11086
'label': node.name
11087
};
11088
}
11089
}
11090
}
11091
return false;
11092
}
11093
}
11094
});
11095
11096
/*
11097
Class: BarChart
11098
11099
A visualization that displays stacked bar charts.
11100
11101
Constructor Options:
11102
11103
See <Options.BarChart>.
11104
11105
*/
11106
$jit.BarChart = new Class({
11107
st: null,
11108
colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
11109
selected: {},
11110
busy: false,
11111
11112
initialize: function(opt) {
11113
this.controller = this.config =
11114
$.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
11115
Label: { type: 'Native' }
11116
}, opt);
11117
//set functions for showLabels and showAggregates
11118
var showLabels = this.config.showLabels,
11119
typeLabels = $.type(showLabels),
11120
showAggregates = this.config.showAggregates,
11121
typeAggregates = $.type(showAggregates);
11122
this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
11123
this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
11124
11125
this.initializeViz();
11126
},
11127
11128
initializeViz: function() {
11129
var config = this.config, that = this;
11130
var nodeType = config.type.split(":")[0],
11131
horz = config.orientation == 'horizontal',
11132
nodeLabels = {};
11133
11134
var delegate = new $jit.ST({
11135
injectInto: config.injectInto,
11136
width: config.width,
11137
height: config.height,
11138
orientation: horz? 'left' : 'bottom',
11139
levelDistance: 0,
11140
siblingOffset: config.barsOffset,
11141
subtreeOffset: 0,
11142
withLabels: config.Label.type != 'Native',
11143
useCanvas: config.useCanvas,
11144
Label: {
11145
type: config.Label.type
11146
},
11147
Node: {
11148
overridable: true,
11149
type: 'barchart-' + nodeType,
11150
align: 'left',
11151
width: 1,
11152
height: 1
11153
},
11154
Edge: {
11155
type: 'none'
11156
},
11157
Tips: {
11158
enable: config.Tips.enable,
11159
type: 'Native',
11160
force: true,
11161
onShow: function(tip, node, contains) {
11162
var elem = contains;
11163
config.Tips.onShow(tip, elem, node);
11164
}
11165
},
11166
Events: {
11167
enable: true,
11168
type: 'Native',
11169
onClick: function(node, eventInfo, evt) {
11170
if(!config.Events.enable) return;
11171
var elem = eventInfo.getContains();
11172
config.Events.onClick(elem, eventInfo, evt);
11173
},
11174
onMouseMove: function(node, eventInfo, evt) {
11175
if(!config.hoveredColor) return;
11176
if(node) {
11177
var elem = eventInfo.getContains();
11178
that.select(node.id, elem.name, elem.index);
11179
} else {
11180
that.select(false, false, false);
11181
}
11182
}
11183
},
11184
onCreateLabel: function(domElement, node) {
11185
var labelConf = config.Label,
11186
valueArray = node.getData('valueArray'),
11187
acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0);
11188
var nlbs = {
11189
wrapper: document.createElement('div'),
11190
aggregate: document.createElement('div'),
11191
label: document.createElement('div')
11192
};
11193
var wrapper = nlbs.wrapper,
11194
label = nlbs.label,
11195
aggregate = nlbs.aggregate,
11196
wrapperStyle = wrapper.style,
11197
labelStyle = label.style,
11198
aggregateStyle = aggregate.style;
11199
//store node labels
11200
nodeLabels[node.id] = nlbs;
11201
//append labels
11202
wrapper.appendChild(label);
11203
wrapper.appendChild(aggregate);
11204
if(!config.showLabels(node.name, acum, node)) {
11205
labelStyle.display = 'none';
11206
}
11207
if(!config.showAggregates(node.name, acum, node)) {
11208
aggregateStyle.display = 'none';
11209
}
11210
wrapperStyle.position = 'relative';
11211
wrapperStyle.overflow = 'visible';
11212
wrapperStyle.fontSize = labelConf.size + 'px';
11213
wrapperStyle.fontFamily = labelConf.family;
11214
wrapperStyle.color = labelConf.color;
11215
wrapperStyle.textAlign = 'center';
11216
aggregateStyle.position = labelStyle.position = 'absolute';
11217
11218
domElement.style.width = node.getData('width') + 'px';
11219
domElement.style.height = node.getData('height') + 'px';
11220
aggregateStyle.left = labelStyle.left = '0px';
11221
11222
label.innerHTML = node.name;
11223
11224
domElement.appendChild(wrapper);
11225
},
11226
onPlaceLabel: function(domElement, node) {
11227
if(!nodeLabels[node.id]) return;
11228
var labels = nodeLabels[node.id],
11229
wrapperStyle = labels.wrapper.style,
11230
labelStyle = labels.label.style,
11231
aggregateStyle = labels.aggregate.style,
11232
grouped = config.type.split(':')[0] == 'grouped',
11233
horz = config.orientation == 'horizontal',
11234
dimArray = node.getData('dimArray'),
11235
valArray = node.getData('valueArray'),
11236
width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
11237
height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
11238
font = parseInt(wrapperStyle.fontSize, 10),
11239
domStyle = domElement.style;
11240
11241
11242
if(dimArray && valArray) {
11243
wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11244
for(var i=0, l=valArray.length, acum=0; i<l; i++) {
11245
if(dimArray[i] > 0) {
11246
acum+= valArray[i];
11247
}
11248
}
11249
if(config.showLabels(node.name, acum, node)) {
11250
labelStyle.display = '';
11251
} else {
11252
labelStyle.display = 'none';
11253
}
11254
var aggValue = config.showAggregates(node.name, acum, node);
11255
if(aggValue !== false) {
11256
aggregateStyle.display = '';
11257
} else {
11258
aggregateStyle.display = 'none';
11259
}
11260
if(config.orientation == 'horizontal') {
11261
aggregateStyle.textAlign = 'right';
11262
labelStyle.textAlign = 'left';
11263
labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
11264
aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
11265
domElement.style.height = wrapperStyle.height = height + 'px';
11266
} else {
11267
aggregateStyle.top = (-font - config.labelOffset) + 'px';
11268
labelStyle.top = (config.labelOffset + height) + 'px';
11269
domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
11270
domElement.style.height = wrapperStyle.height = height + 'px';
11271
}
11272
labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
11273
}
11274
}
11275
});
11276
11277
var size = delegate.canvas.getSize(),
11278
margin = config.Margin;
11279
if(horz) {
11280
delegate.config.offsetX = size.width/2 - margin.left
11281
- (config.showLabels && (config.labelOffset + config.Label.size));
11282
delegate.config.offsetY = (margin.bottom - margin.top)/2;
11283
} else {
11284
delegate.config.offsetY = -size.height/2 + margin.bottom
11285
+ (config.showLabels && (config.labelOffset + config.Label.size));
11286
delegate.config.offsetX = (margin.right - margin.left)/2;
11287
}
11288
this.delegate = delegate;
11289
this.canvas = this.delegate.canvas;
11290
},
11291
11292
/*
11293
Method: loadJSON
11294
11295
Loads JSON data into the visualization.
11296
11297
Parameters:
11298
11299
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>.
11300
11301
Example:
11302
(start code js)
11303
var barChart = new $jit.BarChart(options);
11304
barChart.loadJSON(json);
11305
(end code)
11306
*/
11307
loadJSON: function(json) {
11308
if(this.busy) return;
11309
this.busy = true;
11310
11311
var prefix = $.time(),
11312
ch = [],
11313
delegate = this.delegate,
11314
name = $.splat(json.label),
11315
color = $.splat(json.color || this.colors),
11316
config = this.config,
11317
gradient = !!config.type.split(":")[1],
11318
animate = config.animate,
11319
horz = config.orientation == 'horizontal',
11320
that = this;
11321
11322
for(var i=0, values=json.values, l=values.length; i<l; i++) {
11323
var val = values[i]
11324
var valArray = $.splat(values[i].values);
11325
var acum = 0;
11326
ch.push({
11327
'id': prefix + val.label,
11328
'name': val.label,
11329
'data': {
11330
'value': valArray,
11331
'$valueArray': valArray,
11332
'$colorArray': color,
11333
'$stringArray': name,
11334
'$gradient': gradient,
11335
'$config': config
11336
},
11337
'children': []
11338
});
11339
}
11340
var root = {
11341
'id': prefix + '$root',
11342
'name': '',
11343
'data': {
11344
'$type': 'none',
11345
'$width': 1,
11346
'$height': 1
11347
},
11348
'children': ch
11349
};
11350
delegate.loadJSON(root);
11351
11352
this.normalizeDims();
11353
delegate.compute();
11354
delegate.select(delegate.root);
11355
if(animate) {
11356
if(horz) {
11357
delegate.fx.animate({
11358
modes: ['node-property:width:dimArray'],
11359
duration:1500,
11360
onComplete: function() {
11361
that.busy = false;
11362
}
11363
});
11364
} else {
11365
delegate.fx.animate({
11366
modes: ['node-property:height:dimArray'],
11367
duration:1500,
11368
onComplete: function() {
11369
that.busy = false;
11370
}
11371
});
11372
}
11373
} else {
11374
this.busy = false;
11375
}
11376
},
11377
11378
/*
11379
Method: updateJSON
11380
11381
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.
11382
11383
Parameters:
11384
11385
json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
11386
onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11387
11388
Example:
11389
11390
(start code js)
11391
barChart.updateJSON(json, {
11392
onComplete: function() {
11393
alert('update complete!');
11394
}
11395
});
11396
(end code)
11397
*/
11398
updateJSON: function(json, onComplete) {
11399
if(this.busy) return;
11400
this.busy = true;
11401
this.select(false, false, false);
11402
var delegate = this.delegate;
11403
var graph = delegate.graph;
11404
var values = json.values;
11405
var animate = this.config.animate;
11406
var that = this;
11407
var horz = this.config.orientation == 'horizontal';
11408
$.each(values, function(v) {
11409
var n = graph.getByName(v.label);
11410
if(n) {
11411
n.setData('valueArray', $.splat(v.values));
11412
if(json.label) {
11413
n.setData('stringArray', $.splat(json.label));
11414
}
11415
}
11416
});
11417
this.normalizeDims();
11418
delegate.compute();
11419
delegate.select(delegate.root);
11420
if(animate) {
11421
if(horz) {
11422
delegate.fx.animate({
11423
modes: ['node-property:width:dimArray'],
11424
duration:1500,
11425
onComplete: function() {
11426
that.busy = false;
11427
onComplete && onComplete.onComplete();
11428
}
11429
});
11430
} else {
11431
delegate.fx.animate({
11432
modes: ['node-property:height:dimArray'],
11433
duration:1500,
11434
onComplete: function() {
11435
that.busy = false;
11436
onComplete && onComplete.onComplete();
11437
}
11438
});
11439
}
11440
}
11441
},
11442
11443
//adds the little brown bar when hovering the node
11444
select: function(id, name) {
11445
if(!this.config.hoveredColor) return;
11446
var s = this.selected;
11447
if(s.id != id || s.name != name) {
11448
s.id = id;
11449
s.name = name;
11450
s.color = this.config.hoveredColor;
11451
this.delegate.graph.eachNode(function(n) {
11452
if(id == n.id) {
11453
n.setData('border', s);
11454
} else {
11455
n.setData('border', false);
11456
}
11457
});
11458
this.delegate.plot();
11459
}
11460
},
11461
11462
/*
11463
Method: getLegend
11464
11465
Returns an object containing as keys the legend names and as values hex strings with color values.
11466
11467
Example:
11468
11469
(start code js)
11470
var legend = barChart.getLegend();
11471
(end code)
11472
*/
11473
getLegend: function() {
11474
var legend = {};
11475
var n;
11476
this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
11477
n = adj.nodeTo;
11478
});
11479
var colors = n.getData('colorArray'),
11480
len = colors.length;
11481
$.each(n.getData('stringArray'), function(s, i) {
11482
legend[s] = colors[i % len];
11483
});
11484
return legend;
11485
},
11486
11487
/*
11488
Method: getMaxValue
11489
11490
Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11491
11492
Example:
11493
11494
(start code js)
11495
var ans = barChart.getMaxValue();
11496
(end code)
11497
11498
In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
11499
11500
Example:
11501
11502
(start code js)
11503
//will return 100 for all BarChart instances,
11504
//displaying all of them with the same scale
11505
$jit.BarChart.implement({
11506
'getMaxValue': function() {
11507
return 100;
11508
}
11509
});
11510
(end code)
11511
11512
*/
11513
getMaxValue: function() {
11514
var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
11515
this.delegate.graph.eachNode(function(n) {
11516
var valArray = n.getData('valueArray'),
11517
acum = 0;
11518
if(!valArray) return;
11519
if(stacked) {
11520
$.each(valArray, function(v) {
11521
acum += +v;
11522
});
11523
} else {
11524
acum = Math.max.apply(null, valArray);
11525
}
11526
maxValue = maxValue>acum? maxValue:acum;
11527
});
11528
return maxValue;
11529
},
11530
11531
setBarType: function(type) {
11532
this.config.type = type;
11533
this.delegate.config.Node.type = 'barchart-' + type.split(':')[0];
11534
},
11535
11536
normalizeDims: function() {
11537
//number of elements
11538
var root = this.delegate.graph.getNode(this.delegate.root), l=0;
11539
root.eachAdjacency(function() {
11540
l++;
11541
});
11542
var maxValue = this.getMaxValue() || 1,
11543
size = this.delegate.canvas.getSize(),
11544
config = this.config,
11545
margin = config.Margin,
11546
marginWidth = margin.left + margin.right,
11547
marginHeight = margin.top + margin.bottom,
11548
horz = config.orientation == 'horizontal',
11549
fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (l -1) * config.barsOffset) / l,
11550
animate = config.animate,
11551
height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
11552
- (!horz && config.showAggregates && (config.Label.size + config.labelOffset))
11553
- (config.showLabels && (config.Label.size + config.labelOffset)),
11554
dim1 = horz? 'height':'width',
11555
dim2 = horz? 'width':'height';
11556
this.delegate.graph.eachNode(function(n) {
11557
var acum = 0, animateValue = [];
11558
$.each(n.getData('valueArray'), function(v) {
11559
acum += +v;
11560
animateValue.push(0);
11561
});
11562
n.setData(dim1, fixedDim);
11563
if(animate) {
11564
n.setData(dim2, acum * height / maxValue, 'end');
11565
n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11566
return n * height / maxValue;
11567
}), 'end');
11568
var dimArray = n.getData('dimArray');
11569
if(!dimArray) {
11570
n.setData('dimArray', animateValue);
11571
}
11572
} else {
11573
n.setData(dim2, acum * height / maxValue);
11574
n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11575
return n * height / maxValue;
11576
}));
11577
}
11578
});
11579
}
11580
});
11581
11582
11583
/*
11584
* File: Options.PieChart.js
11585
*
11586
*/
11587
/*
11588
Object: Options.PieChart
11589
11590
<PieChart> options.
11591
Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
11592
11593
Syntax:
11594
11595
(start code js)
11596
11597
Options.PieChart = {
11598
animate: true,
11599
offset: 25,
11600
sliceOffset:0,
11601
labelOffset: 3,
11602
type: 'stacked',
11603
hoveredColor: '#9fd4ff',
11604
showLabels: true,
11605
resizeLabels: false,
11606
updateHeights: false
11607
};
11608
11609
(end code)
11610
11611
Example:
11612
11613
(start code js)
11614
11615
var pie = new $jit.PieChart({
11616
animate: true,
11617
sliceOffset: 5,
11618
type: 'stacked:gradient'
11619
});
11620
11621
(end code)
11622
11623
Parameters:
11624
11625
animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
11626
offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11627
sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
11628
labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11629
type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
11630
hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
11631
showLabels - (boolean) Default's *true*. Display the name of the slots.
11632
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.
11633
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.
11634
11635
*/
11636
Options.PieChart = {
11637
$extend: true,
11638
11639
animate: true,
11640
offset: 25, // page offset
11641
sliceOffset:0,
11642
labelOffset: 3, // label offset
11643
type: 'stacked', // gradient
11644
hoveredColor: '#9fd4ff',
11645
Events: {
11646
enable: false,
11647
onClick: $.empty
11648
},
11649
Tips: {
11650
enable: false,
11651
onShow: $.empty,
11652
onHide: $.empty
11653
},
11654
showLabels: true,
11655
resizeLabels: false,
11656
11657
//only valid for mono-valued datasets
11658
updateHeights: false
11659
};
11660
11661
/*
11662
* Class: Layouts.Radial
11663
*
11664
* Implements a Radial Layout.
11665
*
11666
* Implemented By:
11667
*
11668
* <RGraph>, <Hypertree>
11669
*
11670
*/
11671
Layouts.Radial = new Class({
11672
11673
/*
11674
* Method: compute
11675
*
11676
* Computes nodes' positions.
11677
*
11678
* Parameters:
11679
*
11680
* property - _optional_ A <Graph.Node> position property to store the new
11681
* positions. Possible values are 'pos', 'end' or 'start'.
11682
*
11683
*/
11684
compute : function(property) {
11685
var prop = $.splat(property || [ 'current', 'start', 'end' ]);
11686
NodeDim.compute(this.graph, prop, this.config);
11687
this.graph.computeLevels(this.root, 0, "ignore");
11688
var lengthFunc = this.createLevelDistanceFunc();
11689
this.computeAngularWidths(prop);
11690
this.computePositions(prop, lengthFunc);
11691
},
11692
11693
/*
11694
* computePositions
11695
*
11696
* Performs the main algorithm for computing node positions.
11697
*/
11698
computePositions : function(property, getLength) {
11699
var propArray = property;
11700
var graph = this.graph;
11701
var root = graph.getNode(this.root);
11702
var parent = this.parent;
11703
var config = this.config;
11704
11705
for ( var i=0, l=propArray.length; i < l; i++) {
11706
var pi = propArray[i];
11707
root.setPos($P(0, 0), pi);
11708
root.setData('span', Math.PI * 2, pi);
11709
}
11710
11711
root.angleSpan = {
11712
begin : 0,
11713
end : 2 * Math.PI
11714
};
11715
11716
graph.eachBFS(this.root, function(elem) {
11717
var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
11718
var angleInit = elem.angleSpan.begin;
11719
var len = getLength(elem);
11720
//Calculate the sum of all angular widths
11721
var totalAngularWidths = 0, subnodes = [], maxDim = {};
11722
elem.eachSubnode(function(sib) {
11723
totalAngularWidths += sib._treeAngularWidth;
11724
//get max dim
11725
for ( var i=0, l=propArray.length; i < l; i++) {
11726
var pi = propArray[i], dim = sib.getData('dim', pi);
11727
maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
11728
}
11729
subnodes.push(sib);
11730
}, "ignore");
11731
//Maintain children order
11732
//Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
11733
if (parent && parent.id == elem.id && subnodes.length > 0
11734
&& subnodes[0].dist) {
11735
subnodes.sort(function(a, b) {
11736
return (a.dist >= b.dist) - (a.dist <= b.dist);
11737
});
11738
}
11739
//Calculate nodes positions.
11740
for (var k = 0, ls=subnodes.length; k < ls; k++) {
11741
var child = subnodes[k];
11742
if (!child._flag) {
11743
var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
11744
var theta = angleInit + angleProportion / 2;
11745
11746
for ( var i=0, l=propArray.length; i < l; i++) {
11747
var pi = propArray[i];
11748
child.setPos($P(theta, len), pi);
11749
child.setData('span', angleProportion, pi);
11750
child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
11751
}
11752
11753
child.angleSpan = {
11754
begin : angleInit,
11755
end : angleInit + angleProportion
11756
};
11757
angleInit += angleProportion;
11758
}
11759
}
11760
}, "ignore");
11761
},
11762
11763
/*
11764
* Method: setAngularWidthForNodes
11765
*
11766
* Sets nodes angular widths.
11767
*/
11768
setAngularWidthForNodes : function(prop) {
11769
this.graph.eachBFS(this.root, function(elem, i) {
11770
var diamValue = elem.getData('angularWidth', prop[0]) || 5;
11771
elem._angularWidth = diamValue / i;
11772
}, "ignore");
11773
},
11774
11775
/*
11776
* Method: setSubtreesAngularWidth
11777
*
11778
* Sets subtrees angular widths.
11779
*/
11780
setSubtreesAngularWidth : function() {
11781
var that = this;
11782
this.graph.eachNode(function(elem) {
11783
that.setSubtreeAngularWidth(elem);
11784
}, "ignore");
11785
},
11786
11787
/*
11788
* Method: setSubtreeAngularWidth
11789
*
11790
* Sets the angular width for a subtree.
11791
*/
11792
setSubtreeAngularWidth : function(elem) {
11793
var that = this, nodeAW = elem._angularWidth, sumAW = 0;
11794
elem.eachSubnode(function(child) {
11795
that.setSubtreeAngularWidth(child);
11796
sumAW += child._treeAngularWidth;
11797
}, "ignore");
11798
elem._treeAngularWidth = Math.max(nodeAW, sumAW);
11799
},
11800
11801
/*
11802
* Method: computeAngularWidths
11803
*
11804
* Computes nodes and subtrees angular widths.
11805
*/
11806
computeAngularWidths : function(prop) {
11807
this.setAngularWidthForNodes(prop);
11808
this.setSubtreesAngularWidth();
11809
}
11810
11811
});
11812
11813
11814
/*
11815
* File: Sunburst.js
11816
*/
11817
11818
/*
11819
Class: Sunburst
11820
11821
A radial space filling tree visualization.
11822
11823
Inspired by:
11824
11825
Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
11826
11827
Note:
11828
11829
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.
11830
11831
Implements:
11832
11833
All <Loader> methods
11834
11835
Constructor Options:
11836
11837
Inherits options from
11838
11839
- <Options.Canvas>
11840
- <Options.Controller>
11841
- <Options.Node>
11842
- <Options.Edge>
11843
- <Options.Label>
11844
- <Options.Events>
11845
- <Options.Tips>
11846
- <Options.NodeStyles>
11847
- <Options.Navigation>
11848
11849
Additionally, there are other parameters and some default values changed
11850
11851
interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
11852
levelDistance - (number) Default's *100*. The distance between levels of the tree.
11853
Node.type - Described in <Options.Node>. Default's to *multipie*.
11854
Node.height - Described in <Options.Node>. Default's *0*.
11855
Edge.type - Described in <Options.Edge>. Default's *none*.
11856
Label.textAlign - Described in <Options.Label>. Default's *start*.
11857
Label.textBaseline - Described in <Options.Label>. Default's *middle*.
11858
11859
Instance Properties:
11860
11861
canvas - Access a <Canvas> instance.
11862
graph - Access a <Graph> instance.
11863
op - Access a <Sunburst.Op> instance.
11864
fx - Access a <Sunburst.Plot> instance.
11865
labels - Access a <Sunburst.Label> interface implementation.
11866
11867
*/
11868
11869
$jit.Sunburst = new Class({
11870
11871
Implements: [ Loader, Extras, Layouts.Radial ],
11872
11873
initialize: function(controller) {
11874
var $Sunburst = $jit.Sunburst;
11875
11876
var config = {
11877
interpolation: 'linear',
11878
levelDistance: 100,
11879
Node: {
11880
'type': 'multipie',
11881
'height':0
11882
},
11883
Edge: {
11884
'type': 'none'
11885
},
11886
Label: {
11887
textAlign: 'start',
11888
textBaseline: 'middle'
11889
}
11890
};
11891
11892
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
11893
"Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
11894
11895
var canvasConfig = this.config;
11896
if(canvasConfig.useCanvas) {
11897
this.canvas = canvasConfig.useCanvas;
11898
this.config.labelContainer = this.canvas.id + '-label';
11899
} else {
11900
if(canvasConfig.background) {
11901
canvasConfig.background = $.merge({
11902
type: 'Circles'
11903
}, canvasConfig.background);
11904
}
11905
this.canvas = new Canvas(this, canvasConfig);
11906
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
11907
}
11908
11909
this.graphOptions = {
11910
'klass': Polar,
11911
'Node': {
11912
'selected': false,
11913
'exist': true,
11914
'drawn': true
11915
}
11916
};
11917
this.graph = new Graph(this.graphOptions, this.config.Node,
11918
this.config.Edge);
11919
this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
11920
this.fx = new $Sunburst.Plot(this, $Sunburst);
11921
this.op = new $Sunburst.Op(this);
11922
this.json = null;
11923
this.root = null;
11924
this.rotated = null;
11925
this.busy = false;
11926
// initialize extras
11927
this.initializeExtras();
11928
},
11929
11930
/*
11931
11932
createLevelDistanceFunc
11933
11934
Returns the levelDistance function used for calculating a node distance
11935
to its origin. This function returns a function that is computed
11936
per level and not per node, such that all nodes with the same depth will have the
11937
same distance to the origin. The resulting function gets the
11938
parent node as parameter and returns a float.
11939
11940
*/
11941
createLevelDistanceFunc: function() {
11942
var ld = this.config.levelDistance;
11943
return function(elem) {
11944
return (elem._depth + 1) * ld;
11945
};
11946
},
11947
11948
/*
11949
Method: refresh
11950
11951
Computes positions and plots the tree.
11952
11953
*/
11954
refresh: function() {
11955
this.compute();
11956
this.plot();
11957
},
11958
11959
/*
11960
reposition
11961
11962
An alias for computing new positions to _endPos_
11963
11964
See also:
11965
11966
<Sunburst.compute>
11967
11968
*/
11969
reposition: function() {
11970
this.compute('end');
11971
},
11972
11973
/*
11974
Method: rotate
11975
11976
Rotates the graph so that the selected node is horizontal on the right.
11977
11978
Parameters:
11979
11980
node - (object) A <Graph.Node>.
11981
method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
11982
opt - (object) Configuration options merged with this visualization configuration options.
11983
11984
See also:
11985
11986
<Sunburst.rotateAngle>
11987
11988
*/
11989
rotate: function(node, method, opt) {
11990
var theta = node.getPos(opt.property || 'current').getp(true).theta;
11991
this.rotated = node;
11992
this.rotateAngle(-theta, method, opt);
11993
},
11994
11995
/*
11996
Method: rotateAngle
11997
11998
Rotates the graph of an angle theta.
11999
12000
Parameters:
12001
12002
node - (object) A <Graph.Node>.
12003
method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
12004
opt - (object) Configuration options merged with this visualization configuration options.
12005
12006
See also:
12007
12008
<Sunburst.rotate>
12009
12010
*/
12011
rotateAngle: function(theta, method, opt) {
12012
var that = this;
12013
var options = $.merge(this.config, opt || {}, {
12014
modes: [ 'polar' ]
12015
});
12016
var prop = opt.property || (method === "animate" ? 'end' : 'current');
12017
if(method === 'animate') {
12018
this.fx.animation.pause();
12019
}
12020
this.graph.eachNode(function(n) {
12021
var p = n.getPos(prop);
12022
p.theta += theta;
12023
if (p.theta < 0) {
12024
p.theta += Math.PI * 2;
12025
}
12026
});
12027
if (method == 'animate') {
12028
this.fx.animate(options);
12029
} else if (method == 'replot') {
12030
this.fx.plot();
12031
this.busy = false;
12032
}
12033
},
12034
12035
/*
12036
Method: plot
12037
12038
Plots the Sunburst. This is a shortcut to *fx.plot*.
12039
*/
12040
plot: function() {
12041
this.fx.plot();
12042
}
12043
});
12044
12045
$jit.Sunburst.$extend = true;
12046
12047
(function(Sunburst) {
12048
12049
/*
12050
Class: Sunburst.Op
12051
12052
Custom extension of <Graph.Op>.
12053
12054
Extends:
12055
12056
All <Graph.Op> methods
12057
12058
See also:
12059
12060
<Graph.Op>
12061
12062
*/
12063
Sunburst.Op = new Class( {
12064
12065
Implements: Graph.Op
12066
12067
});
12068
12069
/*
12070
Class: Sunburst.Plot
12071
12072
Custom extension of <Graph.Plot>.
12073
12074
Extends:
12075
12076
All <Graph.Plot> methods
12077
12078
See also:
12079
12080
<Graph.Plot>
12081
12082
*/
12083
Sunburst.Plot = new Class( {
12084
12085
Implements: Graph.Plot
12086
12087
});
12088
12089
/*
12090
Class: Sunburst.Label
12091
12092
Custom extension of <Graph.Label>.
12093
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
12094
12095
Extends:
12096
12097
All <Graph.Label> methods and subclasses.
12098
12099
See also:
12100
12101
<Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
12102
12103
*/
12104
Sunburst.Label = {};
12105
12106
/*
12107
Sunburst.Label.Native
12108
12109
Custom extension of <Graph.Label.Native>.
12110
12111
Extends:
12112
12113
All <Graph.Label.Native> methods
12114
12115
See also:
12116
12117
<Graph.Label.Native>
12118
*/
12119
Sunburst.Label.Native = new Class( {
12120
Implements: Graph.Label.Native,
12121
12122
initialize: function(viz) {
12123
this.viz = viz;
12124
this.label = viz.config.Label;
12125
this.config = viz.config;
12126
},
12127
12128
renderLabel: function(canvas, node, controller) {
12129
var span = node.getData('span');
12130
if(span < Math.PI /2 && Math.tan(span) *
12131
this.config.levelDistance * node._depth < 10) {
12132
return;
12133
}
12134
var ctx = canvas.getCtx();
12135
var measure = ctx.measureText(node.name);
12136
if (node.id == this.viz.root) {
12137
var x = -measure.width / 2, y = 0, thetap = 0;
12138
var ld = 0;
12139
} else {
12140
var indent = 5;
12141
var ld = controller.levelDistance - indent;
12142
var clone = node.pos.clone();
12143
clone.rho += indent;
12144
var p = clone.getp(true);
12145
var ct = clone.getc(true);
12146
var x = ct.x, y = ct.y;
12147
// get angle in degrees
12148
var pi = Math.PI;
12149
var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12150
var thetap = cond ? p.theta + pi : p.theta;
12151
if (cond) {
12152
x -= Math.abs(Math.cos(p.theta) * measure.width);
12153
y += Math.sin(p.theta) * measure.width;
12154
} else if (node.id == this.viz.root) {
12155
x -= measure.width / 2;
12156
}
12157
}
12158
ctx.save();
12159
ctx.translate(x, y);
12160
ctx.rotate(thetap);
12161
ctx.fillText(node.name, 0, 0);
12162
ctx.restore();
12163
}
12164
});
12165
12166
/*
12167
Sunburst.Label.SVG
12168
12169
Custom extension of <Graph.Label.SVG>.
12170
12171
Extends:
12172
12173
All <Graph.Label.SVG> methods
12174
12175
See also:
12176
12177
<Graph.Label.SVG>
12178
12179
*/
12180
Sunburst.Label.SVG = new Class( {
12181
Implements: Graph.Label.SVG,
12182
12183
initialize: function(viz) {
12184
this.viz = viz;
12185
},
12186
12187
/*
12188
placeLabel
12189
12190
Overrides abstract method placeLabel in <Graph.Plot>.
12191
12192
Parameters:
12193
12194
tag - A DOM label element.
12195
node - A <Graph.Node>.
12196
controller - A configuration/controller object passed to the visualization.
12197
12198
*/
12199
placeLabel: function(tag, node, controller) {
12200
var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
12201
var radius = canvas.getSize();
12202
var labelPos = {
12203
x: Math.round(pos.x + radius.width / 2),
12204
y: Math.round(pos.y + radius.height / 2)
12205
};
12206
tag.setAttribute('x', labelPos.x);
12207
tag.setAttribute('y', labelPos.y);
12208
12209
var bb = tag.getBBox();
12210
if (bb) {
12211
// center the label
12212
var x = tag.getAttribute('x');
12213
var y = tag.getAttribute('y');
12214
// get polar coordinates
12215
var p = node.pos.getp(true);
12216
// get angle in degrees
12217
var pi = Math.PI;
12218
var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12219
if (cond) {
12220
tag.setAttribute('x', x - bb.width);
12221
tag.setAttribute('y', y - bb.height);
12222
} else if (node.id == viz.root) {
12223
tag.setAttribute('x', x - bb.width / 2);
12224
}
12225
12226
var thetap = cond ? p.theta + pi : p.theta;
12227
if(node._depth)
12228
tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
12229
+ ' ' + y + ')');
12230
}
12231
12232
controller.onPlaceLabel(tag, node);
12233
}
12234
});
12235
12236
/*
12237
Sunburst.Label.HTML
12238
12239
Custom extension of <Graph.Label.HTML>.
12240
12241
Extends:
12242
12243
All <Graph.Label.HTML> methods.
12244
12245
See also:
12246
12247
<Graph.Label.HTML>
12248
12249
*/
12250
Sunburst.Label.HTML = new Class( {
12251
Implements: Graph.Label.HTML,
12252
12253
initialize: function(viz) {
12254
this.viz = viz;
12255
},
12256
/*
12257
placeLabel
12258
12259
Overrides abstract method placeLabel in <Graph.Plot>.
12260
12261
Parameters:
12262
12263
tag - A DOM label element.
12264
node - A <Graph.Node>.
12265
controller - A configuration/controller object passed to the visualization.
12266
12267
*/
12268
placeLabel: function(tag, node, controller) {
12269
var pos = node.pos.clone(),
12270
canvas = this.viz.canvas,
12271
height = node.getData('height'),
12272
ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
12273
radius = canvas.getSize();
12274
pos.rho += ldist;
12275
pos = pos.getc(true);
12276
12277
var labelPos = {
12278
x: Math.round(pos.x + radius.width / 2),
12279
y: Math.round(pos.y + radius.height / 2)
12280
};
12281
12282
var style = tag.style;
12283
style.left = labelPos.x + 'px';
12284
style.top = labelPos.y + 'px';
12285
style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
12286
12287
controller.onPlaceLabel(tag, node);
12288
}
12289
});
12290
12291
/*
12292
Class: Sunburst.Plot.NodeTypes
12293
12294
This class contains a list of <Graph.Node> built-in types.
12295
Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
12296
12297
You can add your custom node types, customizing your visualization to the extreme.
12298
12299
Example:
12300
12301
(start code js)
12302
Sunburst.Plot.NodeTypes.implement({
12303
'mySpecialType': {
12304
'render': function(node, canvas) {
12305
//print your custom node to canvas
12306
},
12307
//optional
12308
'contains': function(node, pos) {
12309
//return true if pos is inside the node or false otherwise
12310
}
12311
}
12312
});
12313
(end code)
12314
12315
*/
12316
Sunburst.Plot.NodeTypes = new Class( {
12317
'none': {
12318
'render': $.empty,
12319
'contains': $.lambda(false),
12320
'anglecontains': function(node, pos) {
12321
var span = node.getData('span') / 2, theta = node.pos.theta;
12322
var begin = theta - span, end = theta + span;
12323
if (begin < 0)
12324
begin += Math.PI * 2;
12325
var atan = Math.atan2(pos.y, pos.x);
12326
if (atan < 0)
12327
atan += Math.PI * 2;
12328
if (begin > end) {
12329
return (atan > begin && atan <= Math.PI * 2) || atan < end;
12330
} else {
12331
return atan > begin && atan < end;
12332
}
12333
}
12334
},
12335
12336
'pie': {
12337
'render': function(node, canvas) {
12338
var span = node.getData('span') / 2, theta = node.pos.theta;
12339
var begin = theta - span, end = theta + span;
12340
var polarNode = node.pos.getp(true);
12341
var polar = new Polar(polarNode.rho, begin);
12342
var p1coord = polar.getc(true);
12343
polar.theta = end;
12344
var p2coord = polar.getc(true);
12345
12346
var ctx = canvas.getCtx();
12347
ctx.beginPath();
12348
ctx.moveTo(0, 0);
12349
ctx.lineTo(p1coord.x, p1coord.y);
12350
ctx.moveTo(0, 0);
12351
ctx.lineTo(p2coord.x, p2coord.y);
12352
ctx.moveTo(0, 0);
12353
ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
12354
false);
12355
ctx.fill();
12356
},
12357
'contains': function(node, pos) {
12358
if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12359
var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12360
var ld = this.config.levelDistance, d = node._depth;
12361
return (rho <= ld * d);
12362
}
12363
return false;
12364
}
12365
},
12366
'multipie': {
12367
'render': function(node, canvas) {
12368
var height = node.getData('height');
12369
var ldist = height? height : this.config.levelDistance;
12370
var span = node.getData('span') / 2, theta = node.pos.theta;
12371
var begin = theta - span, end = theta + span;
12372
var polarNode = node.pos.getp(true);
12373
12374
var polar = new Polar(polarNode.rho, begin);
12375
var p1coord = polar.getc(true);
12376
12377
polar.theta = end;
12378
var p2coord = polar.getc(true);
12379
12380
polar.rho += ldist;
12381
var p3coord = polar.getc(true);
12382
12383
polar.theta = begin;
12384
var p4coord = polar.getc(true);
12385
12386
var ctx = canvas.getCtx();
12387
ctx.moveTo(0, 0);
12388
ctx.beginPath();
12389
ctx.arc(0, 0, polarNode.rho, begin, end, false);
12390
ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
12391
ctx.moveTo(p1coord.x, p1coord.y);
12392
ctx.lineTo(p4coord.x, p4coord.y);
12393
ctx.moveTo(p2coord.x, p2coord.y);
12394
ctx.lineTo(p3coord.x, p3coord.y);
12395
ctx.fill();
12396
12397
if (node.collapsed) {
12398
ctx.save();
12399
ctx.lineWidth = 2;
12400
ctx.moveTo(0, 0);
12401
ctx.beginPath();
12402
ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
12403
true);
12404
ctx.stroke();
12405
ctx.restore();
12406
}
12407
},
12408
'contains': function(node, pos) {
12409
if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12410
var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12411
var height = node.getData('height');
12412
var ldist = height? height : this.config.levelDistance;
12413
var ld = this.config.levelDistance, d = node._depth;
12414
return (rho >= ld * d) && (rho <= (ld * d + ldist));
12415
}
12416
return false;
12417
}
12418
},
12419
12420
'gradient-multipie': {
12421
'render': function(node, canvas) {
12422
var ctx = canvas.getCtx();
12423
var height = node.getData('height');
12424
var ldist = height? height : this.config.levelDistance;
12425
var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
12426
0, 0, node.getPos().rho + ldist);
12427
12428
var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12429
$.each(colorArray, function(i) {
12430
ans.push(parseInt(i * 0.5, 10));
12431
});
12432
var endColor = $.rgbToHex(ans);
12433
radialGradient.addColorStop(0, endColor);
12434
radialGradient.addColorStop(1, node.getData('color'));
12435
ctx.fillStyle = radialGradient;
12436
this.nodeTypes['multipie'].render.call(this, node, canvas);
12437
},
12438
'contains': function(node, pos) {
12439
return this.nodeTypes['multipie'].contains.call(this, node, pos);
12440
}
12441
},
12442
12443
'gradient-pie': {
12444
'render': function(node, canvas) {
12445
var ctx = canvas.getCtx();
12446
var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
12447
.getPos().rho);
12448
12449
var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12450
$.each(colorArray, function(i) {
12451
ans.push(parseInt(i * 0.5, 10));
12452
});
12453
var endColor = $.rgbToHex(ans);
12454
radialGradient.addColorStop(1, endColor);
12455
radialGradient.addColorStop(0, node.getData('color'));
12456
ctx.fillStyle = radialGradient;
12457
this.nodeTypes['pie'].render.call(this, node, canvas);
12458
},
12459
'contains': function(node, pos) {
12460
return this.nodeTypes['pie'].contains.call(this, node, pos);
12461
}
12462
}
12463
});
12464
12465
/*
12466
Class: Sunburst.Plot.EdgeTypes
12467
12468
This class contains a list of <Graph.Adjacence> built-in types.
12469
Edge types implemented are 'none', 'line' and 'arrow'.
12470
12471
You can add your custom edge types, customizing your visualization to the extreme.
12472
12473
Example:
12474
12475
(start code js)
12476
Sunburst.Plot.EdgeTypes.implement({
12477
'mySpecialType': {
12478
'render': function(adj, canvas) {
12479
//print your custom edge to canvas
12480
},
12481
//optional
12482
'contains': function(adj, pos) {
12483
//return true if pos is inside the arc or false otherwise
12484
}
12485
}
12486
});
12487
(end code)
12488
12489
*/
12490
Sunburst.Plot.EdgeTypes = new Class({
12491
'none': $.empty,
12492
'line': {
12493
'render': function(adj, canvas) {
12494
var from = adj.nodeFrom.pos.getc(true),
12495
to = adj.nodeTo.pos.getc(true);
12496
this.edgeHelper.line.render(from, to, canvas);
12497
},
12498
'contains': function(adj, pos) {
12499
var from = adj.nodeFrom.pos.getc(true),
12500
to = adj.nodeTo.pos.getc(true);
12501
return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
12502
}
12503
},
12504
'arrow': {
12505
'render': function(adj, canvas) {
12506
var from = adj.nodeFrom.pos.getc(true),
12507
to = adj.nodeTo.pos.getc(true),
12508
dim = adj.getData('dim'),
12509
direction = adj.data.$direction,
12510
inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
12511
this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
12512
},
12513
'contains': function(adj, pos) {
12514
var from = adj.nodeFrom.pos.getc(true),
12515
to = adj.nodeTo.pos.getc(true);
12516
return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
12517
}
12518
},
12519
'hyperline': {
12520
'render': function(adj, canvas) {
12521
var from = adj.nodeFrom.pos.getc(),
12522
to = adj.nodeTo.pos.getc(),
12523
dim = Math.max(from.norm(), to.norm());
12524
this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
12525
},
12526
'contains': $.lambda(false) //Implement this!
12527
}
12528
});
12529
12530
})($jit.Sunburst);
12531
12532
12533
/*
12534
* File: PieChart.js
12535
*
12536
*/
12537
12538
$jit.Sunburst.Plot.NodeTypes.implement({
12539
'piechart-stacked' : {
12540
'render' : function(node, canvas) {
12541
var pos = node.pos.getp(true),
12542
dimArray = node.getData('dimArray'),
12543
valueArray = node.getData('valueArray'),
12544
colorArray = node.getData('colorArray'),
12545
colorLength = colorArray.length,
12546
stringArray = node.getData('stringArray'),
12547
span = node.getData('span') / 2,
12548
theta = node.pos.theta,
12549
begin = theta - span,
12550
end = theta + span,
12551
polar = new Polar;
12552
12553
var ctx = canvas.getCtx(),
12554
opt = {},
12555
gradient = node.getData('gradient'),
12556
border = node.getData('border'),
12557
config = node.getData('config'),
12558
showLabels = config.showLabels,
12559
resizeLabels = config.resizeLabels,
12560
label = config.Label;
12561
12562
var xpos = config.sliceOffset * Math.cos((begin + end) /2);
12563
var ypos = config.sliceOffset * Math.sin((begin + end) /2);
12564
12565
if (colorArray && dimArray && stringArray) {
12566
for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
12567
var dimi = dimArray[i], colori = colorArray[i % colorLength];
12568
if(dimi <= 0) continue;
12569
ctx.fillStyle = ctx.strokeStyle = colori;
12570
if(gradient && dimi) {
12571
var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
12572
xpos, ypos, acum + dimi + config.sliceOffset);
12573
var colorRgb = $.hexToRgb(colori),
12574
ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
12575
endColor = $.rgbToHex(ans);
12576
12577
radialGradient.addColorStop(0, colori);
12578
radialGradient.addColorStop(0.5, colori);
12579
radialGradient.addColorStop(1, endColor);
12580
ctx.fillStyle = radialGradient;
12581
}
12582
12583
polar.rho = acum + config.sliceOffset;
12584
polar.theta = begin;
12585
var p1coord = polar.getc(true);
12586
polar.theta = end;
12587
var p2coord = polar.getc(true);
12588
polar.rho += dimi;
12589
var p3coord = polar.getc(true);
12590
polar.theta = begin;
12591
var p4coord = polar.getc(true);
12592
12593
ctx.beginPath();
12594
//fixing FF arc method + fill
12595
ctx.arc(xpos, ypos, acum + .01, begin, end, false);
12596
ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
12597
ctx.fill();
12598
if(border && border.name == stringArray[i]) {
12599
opt.acum = acum;
12600
opt.dimValue = dimArray[i];
12601
opt.begin = begin;
12602
opt.end = end;
12603
}
12604
acum += (dimi || 0);
12605
valAcum += (valueArray[i] || 0);
12606
}
12607
if(border) {
12608
ctx.save();
12609
ctx.globalCompositeOperation = "source-over";
12610
ctx.lineWidth = 2;
12611
ctx.strokeStyle = border.color;
12612
var s = begin < end? 1 : -1;
12613
ctx.beginPath();
12614
//fixing FF arc method + fill
12615
ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
12616
ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
12617
ctx.closePath();
12618
ctx.stroke();
12619
ctx.restore();
12620
}
12621
if(showLabels && label.type == 'Native') {
12622
ctx.save();
12623
ctx.fillStyle = ctx.strokeStyle = label.color;
12624
var scale = resizeLabels? node.getData('normalizedDim') : 1,
12625
fontSize = (label.size * scale) >> 0;
12626
fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12627
12628
ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
12629
ctx.textBaseline = 'middle';
12630
ctx.textAlign = 'center';
12631
12632
polar.rho = acum + config.labelOffset + config.sliceOffset;
12633
polar.theta = node.pos.theta;
12634
var cart = polar.getc(true);
12635
12636
ctx.fillText(node.name, cart.x, cart.y);
12637
ctx.restore();
12638
}
12639
}
12640
},
12641
'contains': function(node, pos) {
12642
if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12643
var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12644
var ld = this.config.levelDistance, d = node._depth;
12645
var config = node.getData('config');
12646
if(rho <=ld * d + config.sliceOffset) {
12647
var dimArray = node.getData('dimArray');
12648
for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
12649
var dimi = dimArray[i];
12650
if(rho >= acum && rho <= acum + dimi) {
12651
return {
12652
name: node.getData('stringArray')[i],
12653
color: node.getData('colorArray')[i],
12654
value: node.getData('valueArray')[i],
12655
label: node.name
12656
};
12657
}
12658
acum += dimi;
12659
}
12660
}
12661
return false;
12662
12663
}
12664
return false;
12665
}
12666
}
12667
});
12668
12669
/*
12670
Class: PieChart
12671
12672
A visualization that displays stacked bar charts.
12673
12674
Constructor Options:
12675
12676
See <Options.PieChart>.
12677
12678
*/
12679
$jit.PieChart = new Class({
12680
sb: null,
12681
colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
12682
selected: {},
12683
busy: false,
12684
12685
initialize: function(opt) {
12686
this.controller = this.config =
12687
$.merge(Options("Canvas", "PieChart", "Label"), {
12688
Label: { type: 'Native' }
12689
}, opt);
12690
this.initializeViz();
12691
},
12692
12693
initializeViz: function() {
12694
var config = this.config, that = this;
12695
var nodeType = config.type.split(":")[0];
12696
var delegate = new $jit.Sunburst({
12697
injectInto: config.injectInto,
12698
width: config.width,
12699
height: config.height,
12700
useCanvas: config.useCanvas,
12701
withLabels: config.Label.type != 'Native',
12702
Label: {
12703
type: config.Label.type
12704
},
12705
Node: {
12706
overridable: true,
12707
type: 'piechart-' + nodeType,
12708
width: 1,
12709
height: 1
12710
},
12711
Edge: {
12712
type: 'none'
12713
},
12714
Tips: {
12715
enable: config.Tips.enable,
12716
type: 'Native',
12717
force: true,
12718
onShow: function(tip, node, contains) {
12719
var elem = contains;
12720
config.Tips.onShow(tip, elem, node);
12721
}
12722
},
12723
Events: {
12724
enable: true,
12725
type: 'Native',
12726
onClick: function(node, eventInfo, evt) {
12727
if(!config.Events.enable) return;
12728
var elem = eventInfo.getContains();
12729
config.Events.onClick(elem, eventInfo, evt);
12730
},
12731
onMouseMove: function(node, eventInfo, evt) {
12732
if(!config.hoveredColor) return;
12733
if(node) {
12734
var elem = eventInfo.getContains();
12735
that.select(node.id, elem.name, elem.index);
12736
} else {
12737
that.select(false, false, false);
12738
}
12739
}
12740
},
12741
onCreateLabel: function(domElement, node) {
12742
var labelConf = config.Label;
12743
if(config.showLabels) {
12744
var style = domElement.style;
12745
style.fontSize = labelConf.size + 'px';
12746
style.fontFamily = labelConf.family;
12747
style.color = labelConf.color;
12748
style.textAlign = 'center';
12749
domElement.innerHTML = node.name;
12750
}
12751
},
12752
onPlaceLabel: function(domElement, node) {
12753
if(!config.showLabels) return;
12754
var pos = node.pos.getp(true),
12755
dimArray = node.getData('dimArray'),
12756
span = node.getData('span') / 2,
12757
theta = node.pos.theta,
12758
begin = theta - span,
12759
end = theta + span,
12760
polar = new Polar;
12761
12762
var showLabels = config.showLabels,
12763
resizeLabels = config.resizeLabels,
12764
label = config.Label;
12765
12766
if (dimArray) {
12767
for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
12768
acum += dimArray[i];
12769
}
12770
var scale = resizeLabels? node.getData('normalizedDim') : 1,
12771
fontSize = (label.size * scale) >> 0;
12772
fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12773
domElement.style.fontSize = fontSize + 'px';
12774
polar.rho = acum + config.labelOffset + config.sliceOffset;
12775
polar.theta = (begin + end) / 2;
12776
var pos = polar.getc(true);
12777
var radius = that.canvas.getSize();
12778
var labelPos = {
12779
x: Math.round(pos.x + radius.width / 2),
12780
y: Math.round(pos.y + radius.height / 2)
12781
};
12782
domElement.style.left = labelPos.x + 'px';
12783
domElement.style.top = labelPos.y + 'px';
12784
}
12785
}
12786
});
12787
12788
var size = delegate.canvas.getSize(),
12789
min = Math.min;
12790
delegate.config.levelDistance = min(size.width, size.height)/2
12791
- config.offset - config.sliceOffset;
12792
this.delegate = delegate;
12793
this.canvas = this.delegate.canvas;
12794
this.canvas.getCtx().globalCompositeOperation = 'lighter';
12795
},
12796
12797
/*
12798
Method: loadJSON
12799
12800
Loads JSON data into the visualization.
12801
12802
Parameters:
12803
12804
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>.
12805
12806
Example:
12807
(start code js)
12808
var pieChart = new $jit.PieChart(options);
12809
pieChart.loadJSON(json);
12810
(end code)
12811
*/
12812
loadJSON: function(json) {
12813
var prefix = $.time(),
12814
ch = [],
12815
delegate = this.delegate,
12816
name = $.splat(json.label),
12817
nameLength = name.length,
12818
color = $.splat(json.color || this.colors),
12819
colorLength = color.length,
12820
config = this.config,
12821
gradient = !!config.type.split(":")[1],
12822
animate = config.animate,
12823
mono = nameLength == 1;
12824
12825
for(var i=0, values=json.values, l=values.length; i<l; i++) {
12826
var val = values[i];
12827
var valArray = $.splat(val.values);
12828
ch.push({
12829
'id': prefix + val.label,
12830
'name': val.label,
12831
'data': {
12832
'value': valArray,
12833
'$valueArray': valArray,
12834
'$colorArray': mono? $.splat(color[i % colorLength]) : color,
12835
'$stringArray': name,
12836
'$gradient': gradient,
12837
'$config': config,
12838
'$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
12839
},
12840
'children': []
12841
});
12842
}
12843
var root = {
12844
'id': prefix + '$root',
12845
'name': '',
12846
'data': {
12847
'$type': 'none',
12848
'$width': 1,
12849
'$height': 1
12850
},
12851
'children': ch
12852
};
12853
delegate.loadJSON(root);
12854
12855
this.normalizeDims();
12856
delegate.refresh();
12857
if(animate) {
12858
delegate.fx.animate({
12859
modes: ['node-property:dimArray'],
12860
duration:1500
12861
});
12862
}
12863
},
12864
12865
/*
12866
Method: updateJSON
12867
12868
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.
12869
12870
Parameters:
12871
12872
json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
12873
onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12874
12875
Example:
12876
12877
(start code js)
12878
pieChart.updateJSON(json, {
12879
onComplete: function() {
12880
alert('update complete!');
12881
}
12882
});
12883
(end code)
12884
*/
12885
updateJSON: function(json, onComplete) {
12886
if(this.busy) return;
12887
this.busy = true;
12888
12889
var delegate = this.delegate;
12890
var graph = delegate.graph;
12891
var values = json.values;
12892
var animate = this.config.animate;
12893
var that = this;
12894
$.each(values, function(v) {
12895
var n = graph.getByName(v.label),
12896
vals = $.splat(v.values);
12897
if(n) {
12898
n.setData('valueArray', vals);
12899
n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
12900
if(json.label) {
12901
n.setData('stringArray', $.splat(json.label));
12902
}
12903
}
12904
});
12905
this.normalizeDims();
12906
if(animate) {
12907
delegate.compute('end');
12908
delegate.fx.animate({
12909
modes: ['node-property:dimArray:span', 'linear'],
12910
duration:1500,
12911
onComplete: function() {
12912
that.busy = false;
12913
onComplete && onComplete.onComplete();
12914
}
12915
});
12916
} else {
12917
delegate.refresh();
12918
}
12919
},
12920
12921
//adds the little brown bar when hovering the node
12922
select: function(id, name) {
12923
if(!this.config.hoveredColor) return;
12924
var s = this.selected;
12925
if(s.id != id || s.name != name) {
12926
s.id = id;
12927
s.name = name;
12928
s.color = this.config.hoveredColor;
12929
this.delegate.graph.eachNode(function(n) {
12930
if(id == n.id) {
12931
n.setData('border', s);
12932
} else {
12933
n.setData('border', false);
12934
}
12935
});
12936
this.delegate.plot();
12937
}
12938
},
12939
12940
/*
12941
Method: getLegend
12942
12943
Returns an object containing as keys the legend names and as values hex strings with color values.
12944
12945
Example:
12946
12947
(start code js)
12948
var legend = pieChart.getLegend();
12949
(end code)
12950
*/
12951
getLegend: function() {
12952
var legend = {};
12953
var n;
12954
this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
12955
n = adj.nodeTo;
12956
});
12957
var colors = n.getData('colorArray'),
12958
len = colors.length;
12959
$.each(n.getData('stringArray'), function(s, i) {
12960
legend[s] = colors[i % len];
12961
});
12962
return legend;
12963
},
12964
12965
/*
12966
Method: getMaxValue
12967
12968
Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
12969
12970
Example:
12971
12972
(start code js)
12973
var ans = pieChart.getMaxValue();
12974
(end code)
12975
12976
In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
12977
12978
Example:
12979
12980
(start code js)
12981
//will return 100 for all PieChart instances,
12982
//displaying all of them with the same scale
12983
$jit.PieChart.implement({
12984
'getMaxValue': function() {
12985
return 100;
12986
}
12987
});
12988
(end code)
12989
12990
*/
12991
getMaxValue: function() {
12992
var maxValue = 0;
12993
this.delegate.graph.eachNode(function(n) {
12994
var valArray = n.getData('valueArray'),
12995
acum = 0;
12996
$.each(valArray, function(v) {
12997
acum += +v;
12998
});
12999
maxValue = maxValue>acum? maxValue:acum;
13000
});
13001
return maxValue;
13002
},
13003
13004
normalizeDims: function() {
13005
//number of elements
13006
var root = this.delegate.graph.getNode(this.delegate.root), l=0;
13007
root.eachAdjacency(function() {
13008
l++;
13009
});
13010
var maxValue = this.getMaxValue() || 1,
13011
config = this.config,
13012
animate = config.animate,
13013
rho = this.delegate.config.levelDistance;
13014
this.delegate.graph.eachNode(function(n) {
13015
var acum = 0, animateValue = [];
13016
$.each(n.getData('valueArray'), function(v) {
13017
acum += +v;
13018
animateValue.push(1);
13019
});
13020
var stat = (animateValue.length == 1) && !config.updateHeights;
13021
if(animate) {
13022
n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13023
return stat? rho: (n * rho / maxValue);
13024
}), 'end');
13025
var dimArray = n.getData('dimArray');
13026
if(!dimArray) {
13027
n.setData('dimArray', animateValue);
13028
}
13029
} else {
13030
n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13031
return stat? rho : (n * rho / maxValue);
13032
}));
13033
}
13034
n.setData('normalizedDim', acum / maxValue);
13035
});
13036
}
13037
});
13038
13039
13040
/*
13041
* Class: Layouts.TM
13042
*
13043
* Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
13044
*
13045
* Implemented By:
13046
*
13047
* <TM>
13048
*
13049
*/
13050
Layouts.TM = {};
13051
13052
Layouts.TM.SliceAndDice = new Class({
13053
compute: function(prop) {
13054
var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13055
this.controller.onBeforeCompute(root);
13056
var size = this.canvas.getSize(),
13057
config = this.config,
13058
width = size.width,
13059
height = size.height;
13060
this.graph.computeLevels(this.root, 0, "ignore");
13061
//set root position and dimensions
13062
root.getPos(prop).setc(-width/2, -height/2);
13063
root.setData('width', width, prop);
13064
root.setData('height', height + config.titleHeight, prop);
13065
this.computePositions(root, root, this.layout.orientation, prop);
13066
this.controller.onAfterCompute(root);
13067
},
13068
13069
computePositions: function(par, ch, orn, prop) {
13070
//compute children areas
13071
var totalArea = 0;
13072
par.eachSubnode(function(n) {
13073
totalArea += n.getData('area', prop);
13074
});
13075
13076
var config = this.config,
13077
offst = config.offset,
13078
width = par.getData('width', prop),
13079
height = Math.max(par.getData('height', prop) - config.titleHeight, 0),
13080
fact = par == ch? 1 : (ch.getData('area', prop) / totalArea);
13081
13082
var otherSize, size, dim, pos, pos2, posth, pos2th;
13083
var horizontal = (orn == "h");
13084
if(horizontal) {
13085
orn = 'v';
13086
otherSize = height;
13087
size = width * fact;
13088
dim = 'height';
13089
pos = 'y';
13090
pos2 = 'x';
13091
posth = config.titleHeight;
13092
pos2th = 0;
13093
} else {
13094
orn = 'h';
13095
otherSize = height * fact;
13096
size = width;
13097
dim = 'width';
13098
pos = 'x';
13099
pos2 = 'y';
13100
posth = 0;
13101
pos2th = config.titleHeight;
13102
}
13103
var cpos = ch.getPos(prop);
13104
ch.setData('width', size, prop);
13105
ch.setData('height', otherSize, prop);
13106
var offsetSize = 0, tm = this;
13107
ch.eachSubnode(function(n) {
13108
var p = n.getPos(prop);
13109
p[pos] = offsetSize + cpos[pos] + posth;
13110
p[pos2] = cpos[pos2] + pos2th;
13111
tm.computePositions(ch, n, orn, prop);
13112
offsetSize += n.getData(dim, prop);
13113
});
13114
}
13115
13116
});
13117
13118
Layouts.TM.Area = {
13119
/*
13120
Method: compute
13121
13122
Called by loadJSON to calculate recursively all node positions and lay out the tree.
13123
13124
Parameters:
13125
13126
json - A JSON tree. See also <Loader.loadJSON>.
13127
coord - A coordinates object specifying width, height, left and top style properties.
13128
*/
13129
compute: function(prop) {
13130
prop = prop || "current";
13131
var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13132
this.controller.onBeforeCompute(root);
13133
var config = this.config,
13134
size = this.canvas.getSize(),
13135
width = size.width,
13136
height = size.height,
13137
offst = config.offset,
13138
offwdth = width - offst,
13139
offhght = height - offst;
13140
this.graph.computeLevels(this.root, 0, "ignore");
13141
//set root position and dimensions
13142
root.getPos(prop).setc(-width/2, -height/2);
13143
root.setData('width', width, prop);
13144
root.setData('height', height, prop);
13145
//create a coordinates object
13146
var coord = {
13147
'top': -height/2 + config.titleHeight,
13148
'left': -width/2,
13149
'width': offwdth,
13150
'height': offhght - config.titleHeight
13151
};
13152
this.computePositions(root, coord, prop);
13153
this.controller.onAfterCompute(root);
13154
},
13155
13156
/*
13157
Method: computeDim
13158
13159
Computes dimensions and positions of a group of nodes
13160
according to a custom layout row condition.
13161
13162
Parameters:
13163
13164
tail - An array of nodes.
13165
initElem - An array of nodes (containing the initial node to be laid).
13166
w - A fixed dimension where nodes will be layed out.
13167
coord - A coordinates object specifying width, height, left and top style properties.
13168
comp - A custom comparison function
13169
*/
13170
computeDim: function(tail, initElem, w, coord, comp, prop) {
13171
if(tail.length + initElem.length == 1) {
13172
var l = (tail.length == 1)? tail : initElem;
13173
this.layoutLast(l, w, coord, prop);
13174
return;
13175
}
13176
if(tail.length >= 2 && initElem.length == 0) {
13177
initElem = [tail.shift()];
13178
}
13179
if(tail.length == 0) {
13180
if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
13181
return;
13182
}
13183
var c = tail[0];
13184
if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
13185
this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
13186
} else {
13187
var newCoords = this.layoutRow(initElem, w, coord, prop);
13188
this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
13189
}
13190
},
13191
13192
13193
/*
13194
Method: worstAspectRatio
13195
13196
Calculates the worst aspect ratio of a group of rectangles.
13197
13198
See also:
13199
13200
<http://en.wikipedia.org/wiki/Aspect_ratio>
13201
13202
Parameters:
13203
13204
ch - An array of nodes.
13205
w - The fixed dimension where rectangles are being laid out.
13206
13207
Returns:
13208
13209
The worst aspect ratio.
13210
13211
13212
*/
13213
worstAspectRatio: function(ch, w) {
13214
if(!ch || ch.length == 0) return Number.MAX_VALUE;
13215
var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
13216
for(var i=0, l=ch.length; i<l; i++) {
13217
var area = ch[i]._area;
13218
areaSum += area;
13219
minArea = minArea < area? minArea : area;
13220
maxArea = maxArea > area? maxArea : area;
13221
}
13222
var sqw = w * w, sqAreaSum = areaSum * areaSum;
13223
return Math.max(sqw * maxArea / sqAreaSum,
13224
sqAreaSum / (sqw * minArea));
13225
},
13226
13227
/*
13228
Method: avgAspectRatio
13229
13230
Calculates the average aspect ratio of a group of rectangles.
13231
13232
See also:
13233
13234
<http://en.wikipedia.org/wiki/Aspect_ratio>
13235
13236
Parameters:
13237
13238
ch - An array of nodes.
13239
w - The fixed dimension where rectangles are being laid out.
13240
13241
Returns:
13242
13243
The average aspect ratio.
13244
13245
13246
*/
13247
avgAspectRatio: function(ch, w) {
13248
if(!ch || ch.length == 0) return Number.MAX_VALUE;
13249
var arSum = 0;
13250
for(var i=0, l=ch.length; i<l; i++) {
13251
var area = ch[i]._area;
13252
var h = area / w;
13253
arSum += w > h? w / h : h / w;
13254
}
13255
return arSum / l;
13256
},
13257
13258
/*
13259
layoutLast
13260
13261
Performs the layout of the last computed sibling.
13262
13263
Parameters:
13264
13265
ch - An array of nodes.
13266
w - A fixed dimension where nodes will be layed out.
13267
coord - A coordinates object specifying width, height, left and top style properties.
13268
*/
13269
layoutLast: function(ch, w, coord, prop) {
13270
var child = ch[0];
13271
child.getPos(prop).setc(coord.left, coord.top);
13272
child.setData('width', coord.width, prop);
13273
child.setData('height', coord.height, prop);
13274
}
13275
};
13276
13277
13278
Layouts.TM.Squarified = new Class({
13279
Implements: Layouts.TM.Area,
13280
13281
computePositions: function(node, coord, prop) {
13282
var config = this.config,
13283
max = Math.max;
13284
13285
if (coord.width >= coord.height)
13286
this.layout.orientation = 'h';
13287
else
13288
this.layout.orientation = 'v';
13289
13290
var ch = node.getSubnodes([1, 1], "ignore");
13291
if(ch.length > 0) {
13292
this.processChildrenLayout(node, ch, coord, prop);
13293
for(var i=0, l=ch.length; i<l; i++) {
13294
var chi = ch[i],
13295
offst = config.offset,
13296
height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13297
width = max(chi.getData('width', prop) - offst, 0),
13298
chipos = chi.getPos(prop);
13299
13300
coord = {
13301
'width': width,
13302
'height': height,
13303
'top': chipos.y + config.titleHeight,
13304
'left': chipos.x
13305
};
13306
this.computePositions(chi, coord, prop);
13307
}
13308
}
13309
},
13310
13311
/*
13312
Method: processChildrenLayout
13313
13314
Computes children real areas and other useful parameters for performing the Squarified algorithm.
13315
13316
Parameters:
13317
13318
par - The parent node of the json subtree.
13319
ch - An Array of nodes
13320
coord - A coordinates object specifying width, height, left and top style properties.
13321
*/
13322
processChildrenLayout: function(par, ch, coord, prop) {
13323
//compute children real areas
13324
var parentArea = coord.width * coord.height;
13325
var i, l=ch.length, totalChArea=0, chArea = [];
13326
for(i=0; i<l; i++) {
13327
chArea[i] = parseFloat(ch[i].getData('area', prop));
13328
totalChArea += chArea[i];
13329
}
13330
for(i=0; i<l; i++) {
13331
ch[i]._area = parentArea * chArea[i] / totalChArea;
13332
}
13333
var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
13334
ch.sort(function(a, b) {
13335
var diff = b._area - a._area;
13336
return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
13337
});
13338
var initElem = [ch[0]];
13339
var tail = ch.slice(1);
13340
this.squarify(tail, initElem, minimumSideValue, coord, prop);
13341
},
13342
13343
/*
13344
Method: squarify
13345
13346
Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
13347
13348
Parameters:
13349
13350
tail - An array of nodes.
13351
initElem - An array of nodes, containing the initial node to be laid out.
13352
w - A fixed dimension where nodes will be laid out.
13353
coord - A coordinates object specifying width, height, left and top style properties.
13354
*/
13355
squarify: function(tail, initElem, w, coord, prop) {
13356
this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
13357
},
13358
13359
/*
13360
Method: layoutRow
13361
13362
Performs the layout of an array of nodes.
13363
13364
Parameters:
13365
13366
ch - An array of nodes.
13367
w - A fixed dimension where nodes will be laid out.
13368
coord - A coordinates object specifying width, height, left and top style properties.
13369
*/
13370
layoutRow: function(ch, w, coord, prop) {
13371
if(this.layout.horizontal()) {
13372
return this.layoutV(ch, w, coord, prop);
13373
} else {
13374
return this.layoutH(ch, w, coord, prop);
13375
}
13376
},
13377
13378
layoutV: function(ch, w, coord, prop) {
13379
var totalArea = 0, rnd = function(x) { return x; };
13380
$.each(ch, function(elem) { totalArea += elem._area; });
13381
var width = rnd(totalArea / w), top = 0;
13382
for(var i=0, l=ch.length; i<l; i++) {
13383
var h = rnd(ch[i]._area / width);
13384
var chi = ch[i];
13385
chi.getPos(prop).setc(coord.left, coord.top + top);
13386
chi.setData('width', width, prop);
13387
chi.setData('height', h, prop);
13388
top += h;
13389
}
13390
var ans = {
13391
'height': coord.height,
13392
'width': coord.width - width,
13393
'top': coord.top,
13394
'left': coord.left + width
13395
};
13396
//take minimum side value.
13397
ans.dim = Math.min(ans.width, ans.height);
13398
if(ans.dim != ans.height) this.layout.change();
13399
return ans;
13400
},
13401
13402
layoutH: function(ch, w, coord, prop) {
13403
var totalArea = 0;
13404
$.each(ch, function(elem) { totalArea += elem._area; });
13405
var height = totalArea / w,
13406
top = coord.top,
13407
left = 0;
13408
13409
for(var i=0, l=ch.length; i<l; i++) {
13410
var chi = ch[i];
13411
var w = chi._area / height;
13412
chi.getPos(prop).setc(coord.left + left, top);
13413
chi.setData('width', w, prop);
13414
chi.setData('height', height, prop);
13415
left += w;
13416
}
13417
var ans = {
13418
'height': coord.height - height,
13419
'width': coord.width,
13420
'top': coord.top + height,
13421
'left': coord.left
13422
};
13423
ans.dim = Math.min(ans.width, ans.height);
13424
if(ans.dim != ans.width) this.layout.change();
13425
return ans;
13426
}
13427
});
13428
13429
Layouts.TM.Strip = new Class({
13430
Implements: Layouts.TM.Area,
13431
13432
/*
13433
Method: compute
13434
13435
Called by loadJSON to calculate recursively all node positions and lay out the tree.
13436
13437
Parameters:
13438
13439
json - A JSON subtree. See also <Loader.loadJSON>.
13440
coord - A coordinates object specifying width, height, left and top style properties.
13441
*/
13442
computePositions: function(node, coord, prop) {
13443
var ch = node.getSubnodes([1, 1], "ignore"),
13444
config = this.config,
13445
max = Math.max;
13446
if(ch.length > 0) {
13447
this.processChildrenLayout(node, ch, coord, prop);
13448
for(var i=0, l=ch.length; i<l; i++) {
13449
var chi = ch[i];
13450
var offst = config.offset,
13451
height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13452
width = max(chi.getData('width', prop) - offst, 0);
13453
var chipos = chi.getPos(prop);
13454
coord = {
13455
'width': width,
13456
'height': height,
13457
'top': chipos.y + config.titleHeight,
13458
'left': chipos.x
13459
};
13460
this.computePositions(chi, coord, prop);
13461
}
13462
}
13463
},
13464
13465
/*
13466
Method: processChildrenLayout
13467
13468
Computes children real areas and other useful parameters for performing the Strip algorithm.
13469
13470
Parameters:
13471
13472
par - The parent node of the json subtree.
13473
ch - An Array of nodes
13474
coord - A coordinates object specifying width, height, left and top style properties.
13475
*/
13476
processChildrenLayout: function(par, ch, coord, prop) {
13477
//compute children real areas
13478
var parentArea = coord.width * coord.height;
13479
var i, l=ch.length, totalChArea=0, chArea = [];
13480
for(i=0; i<l; i++) {
13481
chArea[i] = +ch[i].getData('area', prop);
13482
totalChArea += chArea[i];
13483
}
13484
for(i=0; i<l; i++) {
13485
ch[i]._area = parentArea * chArea[i] / totalChArea;
13486
}
13487
var side = this.layout.horizontal()? coord.width : coord.height;
13488
var initElem = [ch[0]];
13489
var tail = ch.slice(1);
13490
this.stripify(tail, initElem, side, coord, prop);
13491
},
13492
13493
/*
13494
Method: stripify
13495
13496
Performs an heuristic method to calculate div elements sizes in order to have
13497
a good compromise between aspect ratio and order.
13498
13499
Parameters:
13500
13501
tail - An array of nodes.
13502
initElem - An array of nodes.
13503
w - A fixed dimension where nodes will be layed out.
13504
coord - A coordinates object specifying width, height, left and top style properties.
13505
*/
13506
stripify: function(tail, initElem, w, coord, prop) {
13507
this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
13508
},
13509
13510
/*
13511
Method: layoutRow
13512
13513
Performs the layout of an array of nodes.
13514
13515
Parameters:
13516
13517
ch - An array of nodes.
13518
w - A fixed dimension where nodes will be laid out.
13519
coord - A coordinates object specifying width, height, left and top style properties.
13520
*/
13521
layoutRow: function(ch, w, coord, prop) {
13522
if(this.layout.horizontal()) {
13523
return this.layoutH(ch, w, coord, prop);
13524
} else {
13525
return this.layoutV(ch, w, coord, prop);
13526
}
13527
},
13528
13529
layoutV: function(ch, w, coord, prop) {
13530
var totalArea = 0;
13531
$.each(ch, function(elem) { totalArea += elem._area; });
13532
var width = totalArea / w, top = 0;
13533
for(var i=0, l=ch.length; i<l; i++) {
13534
var chi = ch[i];
13535
var h = chi._area / width;
13536
chi.getPos(prop).setc(coord.left,
13537
coord.top + (w - h - top));
13538
chi.setData('width', width, prop);
13539
chi.setData('height', h, prop);
13540
top += h;
13541
}
13542
13543
return {
13544
'height': coord.height,
13545
'width': coord.width - width,
13546
'top': coord.top,
13547
'left': coord.left + width,
13548
'dim': w
13549
};
13550
},
13551
13552
layoutH: function(ch, w, coord, prop) {
13553
var totalArea = 0;
13554
$.each(ch, function(elem) { totalArea += elem._area; });
13555
var height = totalArea / w,
13556
top = coord.height - height,
13557
left = 0;
13558
13559
for(var i=0, l=ch.length; i<l; i++) {
13560
var chi = ch[i];
13561
var s = chi._area / height;
13562
chi.getPos(prop).setc(coord.left + left, coord.top + top);
13563
chi.setData('width', s, prop);
13564
chi.setData('height', height, prop);
13565
left += s;
13566
}
13567
return {
13568
'height': coord.height - height,
13569
'width': coord.width,
13570
'top': coord.top,
13571
'left': coord.left,
13572
'dim': w
13573
};
13574
}
13575
});
13576
13577
13578
/*
13579
* Class: Layouts.Icicle
13580
*
13581
* Implements the icicle tree layout.
13582
*
13583
* Implemented By:
13584
*
13585
* <Icicle>
13586
*
13587
*/
13588
13589
Layouts.Icicle = new Class({
13590
/*
13591
* Method: compute
13592
*
13593
* Called by loadJSON to calculate all node positions.
13594
*
13595
* Parameters:
13596
*
13597
* posType - The nodes' position to compute. Either "start", "end" or
13598
* "current". Defaults to "current".
13599
*/
13600
compute: function(posType) {
13601
posType = posType || "current";
13602
13603
var root = this.graph.getNode(this.root),
13604
config = this.config,
13605
size = this.canvas.getSize(),
13606
width = size.width,
13607
height = size.height,
13608
offset = config.offset,
13609
levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
13610
13611
this.controller.onBeforeCompute(root);
13612
13613
Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
13614
13615
var treeDepth = 0;
13616
13617
Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
13618
13619
var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
13620
var maxDepth = Math.min(treeDepth, levelsToShow-1);
13621
var initialDepth = startNode._depth;
13622
if(this.layout.horizontal()) {
13623
this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
13624
} else {
13625
this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
13626
}
13627
},
13628
13629
computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
13630
root.getPos(posType).setc(x, y);
13631
root.setData('width', width, posType);
13632
root.setData('height', height, posType);
13633
13634
var nodeLength, prevNodeLength = 0, totalDim = 0;
13635
var children = Graph.Util.getSubnodes(root, [1, 1], 'ignore'); // next level from this node
13636
13637
if(!children.length)
13638
return;
13639
13640
$.each(children, function(e) { totalDim += e.getData('dim'); });
13641
13642
for(var i=0, l=children.length; i < l; i++) {
13643
if(this.layout.horizontal()) {
13644
nodeLength = height * children[i].getData('dim') / totalDim;
13645
this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
13646
y += nodeLength;
13647
} else {
13648
nodeLength = width * children[i].getData('dim') / totalDim;
13649
this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
13650
x += nodeLength;
13651
}
13652
}
13653
}
13654
});
13655
13656
13657
13658
/*
13659
* File: Icicle.js
13660
*
13661
*/
13662
13663
/*
13664
Class: Icicle
13665
13666
Icicle space filling visualization.
13667
13668
Implements:
13669
13670
All <Loader> methods
13671
13672
Constructor Options:
13673
13674
Inherits options from
13675
13676
- <Options.Canvas>
13677
- <Options.Controller>
13678
- <Options.Node>
13679
- <Options.Edge>
13680
- <Options.Label>
13681
- <Options.Events>
13682
- <Options.Tips>
13683
- <Options.NodeStyles>
13684
- <Options.Navigation>
13685
13686
Additionally, there are other parameters and some default values changed
13687
13688
orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
13689
offset - (number) Default's *2*. Boxes offset.
13690
constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
13691
levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
13692
animate - (boolean) Default's *false*. Whether to animate transitions.
13693
Node.type - Described in <Options.Node>. Default's *rectangle*.
13694
Label.type - Described in <Options.Label>. Default's *Native*.
13695
duration - Described in <Options.Fx>. Default's *700*.
13696
fps - Described in <Options.Fx>. Default's *45*.
13697
13698
Instance Properties:
13699
13700
canvas - Access a <Canvas> instance.
13701
graph - Access a <Graph> instance.
13702
op - Access a <Icicle.Op> instance.
13703
fx - Access a <Icicle.Plot> instance.
13704
labels - Access a <Icicle.Label> interface implementation.
13705
13706
*/
13707
13708
$jit.Icicle = new Class({
13709
Implements: [ Loader, Extras, Layouts.Icicle ],
13710
13711
layout: {
13712
orientation: "h",
13713
vertical: function(){
13714
return this.orientation == "v";
13715
},
13716
horizontal: function(){
13717
return this.orientation == "h";
13718
},
13719
change: function(){
13720
this.orientation = this.vertical()? "h" : "v";
13721
}
13722
},
13723
13724
initialize: function(controller) {
13725
var config = {
13726
animate: false,
13727
orientation: "h",
13728
offset: 2,
13729
levelsToShow: Number.MAX_VALUE,
13730
constrained: false,
13731
Node: {
13732
type: 'rectangle',
13733
overridable: true
13734
},
13735
Edge: {
13736
type: 'none'
13737
},
13738
Label: {
13739
type: 'Native'
13740
},
13741
duration: 700,
13742
fps: 45
13743
};
13744
13745
var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
13746
"Events", "Navigation", "Controller", "Label");
13747
this.controller = this.config = $.merge(opts, config, controller);
13748
this.layout.orientation = this.config.orientation;
13749
13750
var canvasConfig = this.config;
13751
if (canvasConfig.useCanvas) {
13752
this.canvas = canvasConfig.useCanvas;
13753
this.config.labelContainer = this.canvas.id + '-label';
13754
} else {
13755
this.canvas = new Canvas(this, canvasConfig);
13756
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
13757
}
13758
13759
this.graphOptions = {
13760
'klass': Complex,
13761
'Node': {
13762
'selected': false,
13763
'exist': true,
13764
'drawn': true
13765
}
13766
};
13767
13768
this.graph = new Graph(
13769
this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
13770
13771
this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
13772
this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
13773
this.op = new $jit.Icicle.Op(this);
13774
this.group = new $jit.Icicle.Group(this);
13775
this.clickedNode = null;
13776
13777
this.initializeExtras();
13778
},
13779
13780
/*
13781
Method: refresh
13782
13783
Computes positions and plots the tree.
13784
*/
13785
refresh: function(){
13786
var labelType = this.config.Label.type;
13787
if(labelType != 'Native') {
13788
var that = this;
13789
this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
13790
}
13791
this.compute();
13792
this.plot();
13793
},
13794
13795
/*
13796
Method: plot
13797
13798
Plots the Icicle visualization. This is a shortcut to *fx.plot*.
13799
13800
*/
13801
plot: function(){
13802
this.fx.plot(this.config);
13803
},
13804
13805
/*
13806
Method: enter
13807
13808
Sets the node as root.
13809
13810
Parameters:
13811
13812
node - (object) A <Graph.Node>.
13813
13814
*/
13815
enter: function (node) {
13816
if (this.busy)
13817
return;
13818
this.busy = true;
13819
13820
var that = this,
13821
config = this.config;
13822
13823
var callback = {
13824
onComplete: function() {
13825
//compute positions of newly inserted nodes
13826
if(config.request)
13827
that.compute();
13828
13829
if(config.animate) {
13830
that.graph.nodeList.setDataset(['current', 'end'], {
13831
'alpha': [1, 0] //fade nodes
13832
});
13833
13834
Graph.Util.eachSubgraph(node, function(n) {
13835
n.setData('alpha', 1, 'end');
13836
}, "ignore");
13837
13838
that.fx.animate({
13839
duration: 500,
13840
modes:['node-property:alpha'],
13841
onComplete: function() {
13842
that.clickedNode = node;
13843
that.compute('end');
13844
13845
that.fx.animate({
13846
modes:['linear', 'node-property:width:height'],
13847
duration: 1000,
13848
onComplete: function() {
13849
that.busy = false;
13850
that.clickedNode = node;
13851
}
13852
});
13853
}
13854
});
13855
} else {
13856
that.clickedNode = node;
13857
that.busy = false;
13858
that.refresh();
13859
}
13860
}
13861
};
13862
13863
if(config.request) {
13864
this.requestNodes(clickedNode, callback);
13865
} else {
13866
callback.onComplete();
13867
}
13868
},
13869
13870
/*
13871
Method: out
13872
13873
Sets the parent node of the current selected node as root.
13874
13875
*/
13876
out: function(){
13877
if(this.busy)
13878
return;
13879
13880
var that = this,
13881
GUtil = Graph.Util,
13882
config = this.config,
13883
graph = this.graph,
13884
parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
13885
parent = parents[0],
13886
clickedNode = parent,
13887
previousClickedNode = this.clickedNode;
13888
13889
this.busy = true;
13890
this.events.hoveredNode = false;
13891
13892
if(!parent) {
13893
this.busy = false;
13894
return;
13895
}
13896
13897
//final plot callback
13898
callback = {
13899
onComplete: function() {
13900
that.clickedNode = parent;
13901
if(config.request) {
13902
that.requestNodes(parent, {
13903
onComplete: function() {
13904
that.compute();
13905
that.plot();
13906
that.busy = false;
13907
}
13908
});
13909
} else {
13910
that.compute();
13911
that.plot();
13912
that.busy = false;
13913
}
13914
}
13915
};
13916
13917
//animate node positions
13918
if(config.animate) {
13919
this.clickedNode = clickedNode;
13920
this.compute('end');
13921
//animate the visible subtree only
13922
this.clickedNode = previousClickedNode;
13923
this.fx.animate({
13924
modes:['linear', 'node-property:width:height'],
13925
duration: 1000,
13926
onComplete: function() {
13927
//animate the parent subtree
13928
that.clickedNode = clickedNode;
13929
//change nodes alpha
13930
graph.nodeList.setDataset(['current', 'end'], {
13931
'alpha': [0, 1]
13932
});
13933
GUtil.eachSubgraph(previousClickedNode, function(node) {
13934
node.setData('alpha', 1);
13935
}, "ignore");
13936
that.fx.animate({
13937
duration: 500,
13938
modes:['node-property:alpha'],
13939
onComplete: function() {
13940
callback.onComplete();
13941
}
13942
});
13943
}
13944
});
13945
} else {
13946
callback.onComplete();
13947
}
13948
},
13949
requestNodes: function(node, onComplete){
13950
var handler = $.merge(this.controller, onComplete),
13951
levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
13952
13953
if (handler.request) {
13954
var leaves = [], d = node._depth;
13955
Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
13956
if (n.drawn && !Graph.Util.anySubnode(n)) {
13957
leaves.push(n);
13958
n._level = n._depth - d;
13959
if (this.config.constrained)
13960
n._level = levelsToShow - n._level;
13961
13962
}
13963
});
13964
this.group.requestNodes(leaves, handler);
13965
} else {
13966
handler.onComplete();
13967
}
13968
}
13969
});
13970
13971
/*
13972
Class: Icicle.Op
13973
13974
Custom extension of <Graph.Op>.
13975
13976
Extends:
13977
13978
All <Graph.Op> methods
13979
13980
See also:
13981
13982
<Graph.Op>
13983
13984
*/
13985
$jit.Icicle.Op = new Class({
13986
13987
Implements: Graph.Op
13988
13989
});
13990
13991
/*
13992
* Performs operations on group of nodes.
13993
*/
13994
$jit.Icicle.Group = new Class({
13995
13996
initialize: function(viz){
13997
this.viz = viz;
13998
this.canvas = viz.canvas;
13999
this.config = viz.config;
14000
},
14001
14002
/*
14003
* Calls the request method on the controller to request a subtree for each node.
14004
*/
14005
requestNodes: function(nodes, controller){
14006
var counter = 0, len = nodes.length, nodeSelected = {};
14007
var complete = function(){
14008
controller.onComplete();
14009
};
14010
var viz = this.viz;
14011
if (len == 0)
14012
complete();
14013
for(var i = 0; i < len; i++) {
14014
nodeSelected[nodes[i].id] = nodes[i];
14015
controller.request(nodes[i].id, nodes[i]._level, {
14016
onComplete: function(nodeId, data){
14017
if (data && data.children) {
14018
data.id = nodeId;
14019
viz.op.sum(data, {
14020
type: 'nothing'
14021
});
14022
}
14023
if (++counter == len) {
14024
Graph.Util.computeLevels(viz.graph, viz.root, 0);
14025
complete();
14026
}
14027
}
14028
});
14029
}
14030
}
14031
});
14032
14033
/*
14034
Class: Icicle.Plot
14035
14036
Custom extension of <Graph.Plot>.
14037
14038
Extends:
14039
14040
All <Graph.Plot> methods
14041
14042
See also:
14043
14044
<Graph.Plot>
14045
14046
*/
14047
$jit.Icicle.Plot = new Class({
14048
Implements: Graph.Plot,
14049
14050
plot: function(opt, animating){
14051
opt = opt || this.viz.controller;
14052
var viz = this.viz,
14053
graph = viz.graph,
14054
root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
14055
initialDepth = root._depth;
14056
14057
viz.canvas.clear();
14058
this.plotTree(root, $.merge(opt, {
14059
'withLabels': true,
14060
'hideLabels': false,
14061
'plotSubtree': function(root, node) {
14062
return !viz.config.constrained ||
14063
(node._depth - initialDepth < viz.config.levelsToShow);
14064
}
14065
}), animating);
14066
}
14067
});
14068
14069
/*
14070
Class: Icicle.Label
14071
14072
Custom extension of <Graph.Label>.
14073
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14074
14075
Extends:
14076
14077
All <Graph.Label> methods and subclasses.
14078
14079
See also:
14080
14081
<Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14082
14083
*/
14084
$jit.Icicle.Label = {};
14085
14086
/*
14087
Icicle.Label.Native
14088
14089
Custom extension of <Graph.Label.Native>.
14090
14091
Extends:
14092
14093
All <Graph.Label.Native> methods
14094
14095
See also:
14096
14097
<Graph.Label.Native>
14098
14099
*/
14100
$jit.Icicle.Label.Native = new Class({
14101
Implements: Graph.Label.Native,
14102
14103
renderLabel: function(canvas, node, controller) {
14104
var ctx = canvas.getCtx(),
14105
width = node.getData('width'),
14106
height = node.getData('height'),
14107
size = node.getLabelData('size'),
14108
m = ctx.measureText(node.name);
14109
14110
// Guess as much as possible if the label will fit in the node
14111
if(height < (size * 1.5) || width < m.width)
14112
return;
14113
14114
var pos = node.pos.getc(true);
14115
ctx.fillText(node.name,
14116
pos.x + width / 2,
14117
pos.y + height / 2);
14118
}
14119
});
14120
14121
/*
14122
Icicle.Label.SVG
14123
14124
Custom extension of <Graph.Label.SVG>.
14125
14126
Extends:
14127
14128
All <Graph.Label.SVG> methods
14129
14130
See also:
14131
14132
<Graph.Label.SVG>
14133
*/
14134
$jit.Icicle.Label.SVG = new Class( {
14135
Implements: Graph.Label.SVG,
14136
14137
initialize: function(viz){
14138
this.viz = viz;
14139
},
14140
14141
/*
14142
placeLabel
14143
14144
Overrides abstract method placeLabel in <Graph.Plot>.
14145
14146
Parameters:
14147
14148
tag - A DOM label element.
14149
node - A <Graph.Node>.
14150
controller - A configuration/controller object passed to the visualization.
14151
*/
14152
placeLabel: function(tag, node, controller){
14153
var pos = node.pos.getc(true), canvas = this.viz.canvas;
14154
var radius = canvas.getSize();
14155
var labelPos = {
14156
x: Math.round(pos.x + radius.width / 2),
14157
y: Math.round(pos.y + radius.height / 2)
14158
};
14159
tag.setAttribute('x', labelPos.x);
14160
tag.setAttribute('y', labelPos.y);
14161
14162
controller.onPlaceLabel(tag, node);
14163
}
14164
});
14165
14166
/*
14167
Icicle.Label.HTML
14168
14169
Custom extension of <Graph.Label.HTML>.
14170
14171
Extends:
14172
14173
All <Graph.Label.HTML> methods.
14174
14175
See also:
14176
14177
<Graph.Label.HTML>
14178
14179
*/
14180
$jit.Icicle.Label.HTML = new Class( {
14181
Implements: Graph.Label.HTML,
14182
14183
initialize: function(viz){
14184
this.viz = viz;
14185
},
14186
14187
/*
14188
placeLabel
14189
14190
Overrides abstract method placeLabel in <Graph.Plot>.
14191
14192
Parameters:
14193
14194
tag - A DOM label element.
14195
node - A <Graph.Node>.
14196
controller - A configuration/controller object passed to the visualization.
14197
*/
14198
placeLabel: function(tag, node, controller){
14199
var pos = node.pos.getc(true), canvas = this.viz.canvas;
14200
var radius = canvas.getSize();
14201
var labelPos = {
14202
x: Math.round(pos.x + radius.width / 2),
14203
y: Math.round(pos.y + radius.height / 2)
14204
};
14205
14206
var style = tag.style;
14207
style.left = labelPos.x + 'px';
14208
style.top = labelPos.y + 'px';
14209
style.display = '';
14210
14211
controller.onPlaceLabel(tag, node);
14212
}
14213
});
14214
14215
/*
14216
Class: Icicle.Plot.NodeTypes
14217
14218
This class contains a list of <Graph.Node> built-in types.
14219
Node types implemented are 'none', 'rectangle'.
14220
14221
You can add your custom node types, customizing your visualization to the extreme.
14222
14223
Example:
14224
14225
(start code js)
14226
Icicle.Plot.NodeTypes.implement({
14227
'mySpecialType': {
14228
'render': function(node, canvas) {
14229
//print your custom node to canvas
14230
},
14231
//optional
14232
'contains': function(node, pos) {
14233
//return true if pos is inside the node or false otherwise
14234
}
14235
}
14236
});
14237
(end code)
14238
14239
*/
14240
$jit.Icicle.Plot.NodeTypes = new Class( {
14241
'none': {
14242
'render': $.empty
14243
},
14244
14245
'rectangle': {
14246
'render': function(node, canvas, animating) {
14247
var config = this.viz.config;
14248
var offset = config.offset;
14249
var width = node.getData('width');
14250
var height = node.getData('height');
14251
var border = node.getData('border');
14252
var pos = node.pos.getc(true);
14253
var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
14254
var ctx = canvas.getCtx();
14255
14256
if(width - offset < 2 || height - offset < 2) return;
14257
14258
if(config.cushion) {
14259
var color = node.getData('color');
14260
var lg = ctx.createRadialGradient(posx + (width - offset)/2,
14261
posy + (height - offset)/2, 1,
14262
posx + (width-offset)/2, posy + (height-offset)/2,
14263
width < height? height : width);
14264
var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
14265
function(r) { return r * 0.3 >> 0; }));
14266
lg.addColorStop(0, color);
14267
lg.addColorStop(1, colorGrad);
14268
ctx.fillStyle = lg;
14269
}
14270
14271
if (border) {
14272
ctx.strokeStyle = border;
14273
ctx.lineWidth = 3;
14274
}
14275
14276
ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
14277
border && ctx.strokeRect(pos.x, pos.y, width, height);
14278
},
14279
14280
'contains': function(node, pos) {
14281
if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
14282
var npos = node.pos.getc(true),
14283
width = node.getData('width'),
14284
height = node.getData('height');
14285
return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
14286
}
14287
}
14288
});
14289
14290
$jit.Icicle.Plot.EdgeTypes = new Class( {
14291
'none': $.empty
14292
});
14293
14294
14295
14296
/*
14297
* File: Layouts.ForceDirected.js
14298
*
14299
*/
14300
14301
/*
14302
* Class: Layouts.ForceDirected
14303
*
14304
* Implements a Force Directed Layout.
14305
*
14306
* Implemented By:
14307
*
14308
* <ForceDirected>
14309
*
14310
* Credits:
14311
*
14312
* Marcus Cobden <http://marcuscobden.co.uk>
14313
*
14314
*/
14315
Layouts.ForceDirected = new Class({
14316
14317
getOptions: function(random) {
14318
var s = this.canvas.getSize();
14319
var w = s.width, h = s.height;
14320
//count nodes
14321
var count = 0;
14322
this.graph.eachNode(function(n) {
14323
count++;
14324
});
14325
var k2 = w * h / count, k = Math.sqrt(k2);
14326
var l = this.config.levelDistance;
14327
14328
return {
14329
width: w,
14330
height: h,
14331
tstart: w * 0.1,
14332
nodef: function(x) { return k2 / (x || 1); },
14333
edgef: function(x) { return /* x * x / k; */ k * (x - l); }
14334
};
14335
},
14336
14337
compute: function(property, incremental) {
14338
var prop = $.splat(property || ['current', 'start', 'end']);
14339
var opt = this.getOptions();
14340
NodeDim.compute(this.graph, prop, this.config);
14341
this.graph.computeLevels(this.root, 0, "ignore");
14342
this.graph.eachNode(function(n) {
14343
$.each(prop, function(p) {
14344
var pos = n.getPos(p);
14345
if(pos.equals(Complex.KER)) {
14346
pos.x = opt.width/5 * (Math.random() - 0.5);
14347
pos.y = opt.height/5 * (Math.random() - 0.5);
14348
}
14349
//initialize disp vector
14350
n.disp = {};
14351
$.each(prop, function(p) {
14352
n.disp[p] = $C(0, 0);
14353
});
14354
});
14355
});
14356
this.computePositions(prop, opt, incremental);
14357
},
14358
14359
computePositions: function(property, opt, incremental) {
14360
var times = this.config.iterations, i = 0, that = this;
14361
if(incremental) {
14362
(function iter() {
14363
for(var total=incremental.iter, j=0; j<total; j++) {
14364
opt.t = opt.tstart;
14365
if(times) opt.t *= (1 - i++/(times -1));
14366
that.computePositionStep(property, opt);
14367
if(times && i >= times) {
14368
incremental.onComplete();
14369
return;
14370
}
14371
}
14372
incremental.onStep(Math.round(i / (times -1) * 100));
14373
setTimeout(iter, 1);
14374
})();
14375
} else {
14376
for(; i < times; i++) {
14377
opt.t = opt.tstart * (1 - i/(times -1));
14378
this.computePositionStep(property, opt);
14379
}
14380
}
14381
},
14382
14383
computePositionStep: function(property, opt) {
14384
var graph = this.graph;
14385
var min = Math.min, max = Math.max;
14386
var dpos = $C(0, 0);
14387
//calculate repulsive forces
14388
graph.eachNode(function(v) {
14389
//initialize disp
14390
$.each(property, function(p) {
14391
v.disp[p].x = 0; v.disp[p].y = 0;
14392
});
14393
graph.eachNode(function(u) {
14394
if(u.id != v.id) {
14395
$.each(property, function(p) {
14396
var vp = v.getPos(p), up = u.getPos(p);
14397
dpos.x = vp.x - up.x;
14398
dpos.y = vp.y - up.y;
14399
var norm = dpos.norm() || 1;
14400
v.disp[p].$add(dpos
14401
.$scale(opt.nodef(norm) / norm));
14402
});
14403
}
14404
});
14405
});
14406
//calculate attractive forces
14407
var T = !!graph.getNode(this.root).visited;
14408
graph.eachNode(function(node) {
14409
node.eachAdjacency(function(adj) {
14410
var nodeTo = adj.nodeTo;
14411
if(!!nodeTo.visited === T) {
14412
$.each(property, function(p) {
14413
var vp = node.getPos(p), up = nodeTo.getPos(p);
14414
dpos.x = vp.x - up.x;
14415
dpos.y = vp.y - up.y;
14416
var norm = dpos.norm() || 1;
14417
node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
14418
nodeTo.disp[p].$add(dpos.$scale(-1));
14419
});
14420
}
14421
});
14422
node.visited = !T;
14423
});
14424
//arrange positions to fit the canvas
14425
var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
14426
graph.eachNode(function(u) {
14427
$.each(property, function(p) {
14428
var disp = u.disp[p];
14429
var norm = disp.norm() || 1;
14430
var p = u.getPos(p);
14431
p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
14432
disp.y * min(Math.abs(disp.y), t) / norm));
14433
p.x = min(w2, max(-w2, p.x));
14434
p.y = min(h2, max(-h2, p.y));
14435
});
14436
});
14437
}
14438
});
14439
14440
/*
14441
* File: ForceDirected.js
14442
*/
14443
14444
/*
14445
Class: ForceDirected
14446
14447
A visualization that lays graphs using a Force-Directed layout algorithm.
14448
14449
Inspired by:
14450
14451
Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
14452
14453
Implements:
14454
14455
All <Loader> methods
14456
14457
Constructor Options:
14458
14459
Inherits options from
14460
14461
- <Options.Canvas>
14462
- <Options.Controller>
14463
- <Options.Node>
14464
- <Options.Edge>
14465
- <Options.Label>
14466
- <Options.Events>
14467
- <Options.Tips>
14468
- <Options.NodeStyles>
14469
- <Options.Navigation>
14470
14471
Additionally, there are two parameters
14472
14473
levelDistance - (number) Default's *50*. The natural length desired for the edges.
14474
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*.
14475
14476
Instance Properties:
14477
14478
canvas - Access a <Canvas> instance.
14479
graph - Access a <Graph> instance.
14480
op - Access a <ForceDirected.Op> instance.
14481
fx - Access a <ForceDirected.Plot> instance.
14482
labels - Access a <ForceDirected.Label> interface implementation.
14483
14484
*/
14485
14486
$jit.ForceDirected = new Class( {
14487
14488
Implements: [ Loader, Extras, Layouts.ForceDirected ],
14489
14490
initialize: function(controller) {
14491
var $ForceDirected = $jit.ForceDirected;
14492
14493
var config = {
14494
iterations: 50,
14495
levelDistance: 50
14496
};
14497
14498
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14499
"Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14500
14501
var canvasConfig = this.config;
14502
if(canvasConfig.useCanvas) {
14503
this.canvas = canvasConfig.useCanvas;
14504
this.config.labelContainer = this.canvas.id + '-label';
14505
} else {
14506
if(canvasConfig.background) {
14507
canvasConfig.background = $.merge({
14508
type: 'Circles'
14509
}, canvasConfig.background);
14510
}
14511
this.canvas = new Canvas(this, canvasConfig);
14512
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14513
}
14514
14515
this.graphOptions = {
14516
'klass': Complex,
14517
'Node': {
14518
'selected': false,
14519
'exist': true,
14520
'drawn': true
14521
}
14522
};
14523
this.graph = new Graph(this.graphOptions, this.config.Node,
14524
this.config.Edge);
14525
this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
14526
this.fx = new $ForceDirected.Plot(this, $ForceDirected);
14527
this.op = new $ForceDirected.Op(this);
14528
this.json = null;
14529
this.busy = false;
14530
// initialize extras
14531
this.initializeExtras();
14532
},
14533
14534
/*
14535
Method: refresh
14536
14537
Computes positions and plots the tree.
14538
*/
14539
refresh: function() {
14540
this.compute();
14541
this.plot();
14542
},
14543
14544
reposition: function() {
14545
this.compute('end');
14546
},
14547
14548
/*
14549
Method: computeIncremental
14550
14551
Performs the Force Directed algorithm incrementally.
14552
14553
Description:
14554
14555
ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
14556
This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
14557
avoiding browser messages such as "This script is taking too long to complete".
14558
14559
Parameters:
14560
14561
opt - (object) The object properties are described below
14562
14563
iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
14564
of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
14565
14566
property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
14567
You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
14568
computations for final animation positions then you can just choose 'end'.
14569
14570
onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
14571
parameter a percentage value.
14572
14573
onComplete - A callback function called when the algorithm completed.
14574
14575
Example:
14576
14577
In this example I calculate the end positions and then animate the graph to those positions
14578
14579
(start code js)
14580
var fd = new $jit.ForceDirected(...);
14581
fd.computeIncremental({
14582
iter: 20,
14583
property: 'end',
14584
onStep: function(perc) {
14585
Log.write("loading " + perc + "%");
14586
},
14587
onComplete: function() {
14588
Log.write("done");
14589
fd.animate();
14590
}
14591
});
14592
(end code)
14593
14594
In this example I calculate all positions and (re)plot the graph
14595
14596
(start code js)
14597
var fd = new ForceDirected(...);
14598
fd.computeIncremental({
14599
iter: 20,
14600
property: ['end', 'start', 'current'],
14601
onStep: function(perc) {
14602
Log.write("loading " + perc + "%");
14603
},
14604
onComplete: function() {
14605
Log.write("done");
14606
fd.plot();
14607
}
14608
});
14609
(end code)
14610
14611
*/
14612
computeIncremental: function(opt) {
14613
opt = $.merge( {
14614
iter: 20,
14615
property: 'end',
14616
onStep: $.empty,
14617
onComplete: $.empty
14618
}, opt || {});
14619
14620
this.config.onBeforeCompute(this.graph.getNode(this.root));
14621
this.compute(opt.property, opt);
14622
},
14623
14624
/*
14625
Method: plot
14626
14627
Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
14628
*/
14629
plot: function() {
14630
this.fx.plot();
14631
},
14632
14633
/*
14634
Method: animate
14635
14636
Animates the graph from the current positions to the 'end' node positions.
14637
*/
14638
animate: function(opt) {
14639
this.fx.animate($.merge( {
14640
modes: [ 'linear' ]
14641
}, opt || {}));
14642
}
14643
});
14644
14645
$jit.ForceDirected.$extend = true;
14646
14647
(function(ForceDirected) {
14648
14649
/*
14650
Class: ForceDirected.Op
14651
14652
Custom extension of <Graph.Op>.
14653
14654
Extends:
14655
14656
All <Graph.Op> methods
14657
14658
See also:
14659
14660
<Graph.Op>
14661
14662
*/
14663
ForceDirected.Op = new Class( {
14664
14665
Implements: Graph.Op
14666
14667
});
14668
14669
/*
14670
Class: ForceDirected.Plot
14671
14672
Custom extension of <Graph.Plot>.
14673
14674
Extends:
14675
14676
All <Graph.Plot> methods
14677
14678
See also:
14679
14680
<Graph.Plot>
14681
14682
*/
14683
ForceDirected.Plot = new Class( {
14684
14685
Implements: Graph.Plot
14686
14687
});
14688
14689
/*
14690
Class: ForceDirected.Label
14691
14692
Custom extension of <Graph.Label>.
14693
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14694
14695
Extends:
14696
14697
All <Graph.Label> methods and subclasses.
14698
14699
See also:
14700
14701
<Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14702
14703
*/
14704
ForceDirected.Label = {};
14705
14706
/*
14707
ForceDirected.Label.Native
14708
14709
Custom extension of <Graph.Label.Native>.
14710
14711
Extends:
14712
14713
All <Graph.Label.Native> methods
14714
14715
See also:
14716
14717
<Graph.Label.Native>
14718
14719
*/
14720
ForceDirected.Label.Native = new Class( {
14721
Implements: Graph.Label.Native
14722
});
14723
14724
/*
14725
ForceDirected.Label.SVG
14726
14727
Custom extension of <Graph.Label.SVG>.
14728
14729
Extends:
14730
14731
All <Graph.Label.SVG> methods
14732
14733
See also:
14734
14735
<Graph.Label.SVG>
14736
14737
*/
14738
ForceDirected.Label.SVG = new Class( {
14739
Implements: Graph.Label.SVG,
14740
14741
initialize: function(viz) {
14742
this.viz = viz;
14743
},
14744
14745
/*
14746
placeLabel
14747
14748
Overrides abstract method placeLabel in <Graph.Label>.
14749
14750
Parameters:
14751
14752
tag - A DOM label element.
14753
node - A <Graph.Node>.
14754
controller - A configuration/controller object passed to the visualization.
14755
14756
*/
14757
placeLabel: function(tag, node, controller) {
14758
var pos = node.pos.getc(true),
14759
canvas = this.viz.canvas,
14760
ox = canvas.translateOffsetX,
14761
oy = canvas.translateOffsetY,
14762
sx = canvas.scaleOffsetX,
14763
sy = canvas.scaleOffsetY,
14764
radius = canvas.getSize();
14765
var labelPos = {
14766
x: Math.round(pos.x * sx + ox + radius.width / 2),
14767
y: Math.round(pos.y * sy + oy + radius.height / 2)
14768
};
14769
tag.setAttribute('x', labelPos.x);
14770
tag.setAttribute('y', labelPos.y);
14771
14772
controller.onPlaceLabel(tag, node);
14773
}
14774
});
14775
14776
/*
14777
ForceDirected.Label.HTML
14778
14779
Custom extension of <Graph.Label.HTML>.
14780
14781
Extends:
14782
14783
All <Graph.Label.HTML> methods.
14784
14785
See also:
14786
14787
<Graph.Label.HTML>
14788
14789
*/
14790
ForceDirected.Label.HTML = new Class( {
14791
Implements: Graph.Label.HTML,
14792
14793
initialize: function(viz) {
14794
this.viz = viz;
14795
},
14796
/*
14797
placeLabel
14798
14799
Overrides abstract method placeLabel in <Graph.Plot>.
14800
14801
Parameters:
14802
14803
tag - A DOM label element.
14804
node - A <Graph.Node>.
14805
controller - A configuration/controller object passed to the visualization.
14806
14807
*/
14808
placeLabel: function(tag, node, controller) {
14809
var pos = node.pos.getc(true),
14810
canvas = this.viz.canvas,
14811
ox = canvas.translateOffsetX,
14812
oy = canvas.translateOffsetY,
14813
sx = canvas.scaleOffsetX,
14814
sy = canvas.scaleOffsetY,
14815
radius = canvas.getSize();
14816
var labelPos = {
14817
x: Math.round(pos.x * sx + ox + radius.width / 2),
14818
y: Math.round(pos.y * sy + oy + radius.height / 2)
14819
};
14820
var style = tag.style;
14821
style.left = labelPos.x + 'px';
14822
style.top = labelPos.y + 'px';
14823
style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14824
14825
controller.onPlaceLabel(tag, node);
14826
}
14827
});
14828
14829
/*
14830
Class: ForceDirected.Plot.NodeTypes
14831
14832
This class contains a list of <Graph.Node> built-in types.
14833
Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
14834
14835
You can add your custom node types, customizing your visualization to the extreme.
14836
14837
Example:
14838
14839
(start code js)
14840
ForceDirected.Plot.NodeTypes.implement({
14841
'mySpecialType': {
14842
'render': function(node, canvas) {
14843
//print your custom node to canvas
14844
},
14845
//optional
14846
'contains': function(node, pos) {
14847
//return true if pos is inside the node or false otherwise
14848
}
14849
}
14850
});
14851
(end code)
14852
14853
*/
14854
ForceDirected.Plot.NodeTypes = new Class({
14855
'none': {
14856
'render': $.empty,
14857
'contains': $.lambda(false)
14858
},
14859
'circle': {
14860
'render': function(node, canvas){
14861
var pos = node.pos.getc(true),
14862
dim = node.getData('dim');
14863
this.nodeHelper.circle.render('fill', pos, dim, canvas);
14864
},
14865
'contains': function(node, pos){
14866
var npos = node.pos.getc(true),
14867
dim = node.getData('dim');
14868
return this.nodeHelper.circle.contains(npos, pos, dim);
14869
}
14870
},
14871
'ellipse': {
14872
'render': function(node, canvas){
14873
var pos = node.pos.getc(true),
14874
width = node.getData('width'),
14875
height = node.getData('height');
14876
this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
14877
},
14878
'contains': function(node, pos){
14879
var npos = node.pos.getc(true),
14880
width = node.getData('width'),
14881
height = node.getData('height');
14882
return this.nodeHelper.ellipse.contains(npos, pos, width, height);
14883
}
14884
},
14885
'square': {
14886
'render': function(node, canvas){
14887
var pos = node.pos.getc(true),
14888
dim = node.getData('dim');
14889
this.nodeHelper.square.render('fill', pos, dim, canvas);
14890
},
14891
'contains': function(node, pos){
14892
var npos = node.pos.getc(true),
14893
dim = node.getData('dim');
14894
return this.nodeHelper.square.contains(npos, pos, dim);
14895
}
14896
},
14897
'rectangle': {
14898
'render': function(node, canvas){
14899
var pos = node.pos.getc(true),
14900
width = node.getData('width'),
14901
height = node.getData('height');
14902
this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
14903
},
14904
'contains': function(node, pos){
14905
var npos = node.pos.getc(true),
14906
width = node.getData('width'),
14907
height = node.getData('height');
14908
return this.nodeHelper.rectangle.contains(npos, pos, width, height);
14909
}
14910
},
14911
'triangle': {
14912
'render': function(node, canvas){
14913
var pos = node.pos.getc(true),
14914
dim = node.getData('dim');
14915
this.nodeHelper.triangle.render('fill', pos, dim, canvas);
14916
},
14917
'contains': function(node, pos) {
14918
var npos = node.pos.getc(true),
14919
dim = node.getData('dim');
14920
return this.nodeHelper.triangle.contains(npos, pos, dim);
14921
}
14922
},
14923
'star': {
14924
'render': function(node, canvas){
14925
var pos = node.pos.getc(true),
14926
dim = node.getData('dim');
14927
this.nodeHelper.star.render('fill', pos, dim, canvas);
14928
},
14929
'contains': function(node, pos) {
14930
var npos = node.pos.getc(true),
14931
dim = node.getData('dim');
14932
return this.nodeHelper.star.contains(npos, pos, dim);
14933
}
14934
}
14935
});
14936
14937
/*
14938
Class: ForceDirected.Plot.EdgeTypes
14939
14940
This class contains a list of <Graph.Adjacence> built-in types.
14941
Edge types implemented are 'none', 'line' and 'arrow'.
14942
14943
You can add your custom edge types, customizing your visualization to the extreme.
14944
14945
Example:
14946
14947
(start code js)
14948
ForceDirected.Plot.EdgeTypes.implement({
14949
'mySpecialType': {
14950
'render': function(adj, canvas) {
14951
//print your custom edge to canvas
14952
},
14953
//optional
14954
'contains': function(adj, pos) {
14955
//return true if pos is inside the arc or false otherwise
14956
}
14957
}
14958
});
14959
(end code)
14960
14961
*/
14962
ForceDirected.Plot.EdgeTypes = new Class({
14963
'none': $.empty,
14964
'line': {
14965
'render': function(adj, canvas) {
14966
var from = adj.nodeFrom.pos.getc(true),
14967
to = adj.nodeTo.pos.getc(true);
14968
this.edgeHelper.line.render(from, to, canvas);
14969
},
14970
'contains': function(adj, pos) {
14971
var from = adj.nodeFrom.pos.getc(true),
14972
to = adj.nodeTo.pos.getc(true);
14973
return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14974
}
14975
},
14976
'arrow': {
14977
'render': function(adj, canvas) {
14978
var from = adj.nodeFrom.pos.getc(true),
14979
to = adj.nodeTo.pos.getc(true),
14980
dim = adj.getData('dim'),
14981
direction = adj.data.$direction,
14982
inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14983
this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14984
},
14985
'contains': function(adj, pos) {
14986
var from = adj.nodeFrom.pos.getc(true),
14987
to = adj.nodeTo.pos.getc(true);
14988
return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
14989
}
14990
}
14991
});
14992
14993
})($jit.ForceDirected);
14994
14995
14996
/*
14997
* File: Treemap.js
14998
*
14999
*/
15000
15001
$jit.TM = {};
15002
15003
var TM = $jit.TM;
15004
15005
$jit.TM.$extend = true;
15006
15007
/*
15008
Class: TM.Base
15009
15010
Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
15011
15012
Implements:
15013
15014
All <Loader> methods
15015
15016
Constructor Options:
15017
15018
Inherits options from
15019
15020
- <Options.Canvas>
15021
- <Options.Controller>
15022
- <Options.Node>
15023
- <Options.Edge>
15024
- <Options.Label>
15025
- <Options.Events>
15026
- <Options.Tips>
15027
- <Options.NodeStyles>
15028
- <Options.Navigation>
15029
15030
Additionally, there are other parameters and some default values changed
15031
15032
orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
15033
titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
15034
offset - (number) Default's *2*. Boxes offset.
15035
constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
15036
levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
15037
animate - (boolean) Default's *false*. Whether to animate transitions.
15038
Node.type - Described in <Options.Node>. Default's *rectangle*.
15039
duration - Described in <Options.Fx>. Default's *700*.
15040
fps - Described in <Options.Fx>. Default's *45*.
15041
15042
Instance Properties:
15043
15044
canvas - Access a <Canvas> instance.
15045
graph - Access a <Graph> instance.
15046
op - Access a <TM.Op> instance.
15047
fx - Access a <TM.Plot> instance.
15048
labels - Access a <TM.Label> interface implementation.
15049
15050
Inspired by:
15051
15052
Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
15053
15054
Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
15055
15056
Note:
15057
15058
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.
15059
15060
*/
15061
TM.Base = {
15062
layout: {
15063
orientation: "h",
15064
vertical: function(){
15065
return this.orientation == "v";
15066
},
15067
horizontal: function(){
15068
return this.orientation == "h";
15069
},
15070
change: function(){
15071
this.orientation = this.vertical()? "h" : "v";
15072
}
15073
},
15074
15075
initialize: function(controller){
15076
var config = {
15077
orientation: "h",
15078
titleHeight: 13,
15079
offset: 2,
15080
levelsToShow: 0,
15081
constrained: false,
15082
animate: false,
15083
Node: {
15084
type: 'rectangle',
15085
overridable: true,
15086
//we all know why this is not zero,
15087
//right, Firefox?
15088
width: 3,
15089
height: 3,
15090
color: '#444'
15091
},
15092
Label: {
15093
textAlign: 'center',
15094
textBaseline: 'top'
15095
},
15096
Edge: {
15097
type: 'none'
15098
},
15099
duration: 700,
15100
fps: 45
15101
};
15102
15103
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15104
"Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15105
this.layout.orientation = this.config.orientation;
15106
15107
var canvasConfig = this.config;
15108
if (canvasConfig.useCanvas) {
15109
this.canvas = canvasConfig.useCanvas;
15110
this.config.labelContainer = this.canvas.id + '-label';
15111
} else {
15112
if(canvasConfig.background) {
15113
canvasConfig.background = $.merge({
15114
type: 'Circles'
15115
}, canvasConfig.background);
15116
}
15117
this.canvas = new Canvas(this, canvasConfig);
15118
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15119
}
15120
15121
this.graphOptions = {
15122
'klass': Complex,
15123
'Node': {
15124
'selected': false,
15125
'exist': true,
15126
'drawn': true
15127
}
15128
};
15129
this.graph = new Graph(this.graphOptions, this.config.Node,
15130
this.config.Edge);
15131
this.labels = new TM.Label[canvasConfig.Label.type](this);
15132
this.fx = new TM.Plot(this);
15133
this.op = new TM.Op(this);
15134
this.group = new TM.Group(this);
15135
this.geom = new TM.Geom(this);
15136
this.clickedNode = null;
15137
this.busy = false;
15138
// initialize extras
15139
this.initializeExtras();
15140
},
15141
15142
/*
15143
Method: refresh
15144
15145
Computes positions and plots the tree.
15146
*/
15147
refresh: function(){
15148
if(this.busy) return;
15149
this.busy = true;
15150
var that = this;
15151
if(this.config.animate) {
15152
this.compute('end');
15153
this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15154
&& this.clickedNode.id || this.root));
15155
this.fx.animate($.merge(this.config, {
15156
modes: ['linear', 'node-property:width:height'],
15157
onComplete: function() {
15158
that.busy = false;
15159
}
15160
}));
15161
} else {
15162
var labelType = this.config.Label.type;
15163
if(labelType != 'Native') {
15164
var that = this;
15165
this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
15166
}
15167
this.busy = false;
15168
this.compute();
15169
this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15170
&& this.clickedNode.id || this.root));
15171
this.plot();
15172
}
15173
},
15174
15175
/*
15176
Method: plot
15177
15178
Plots the TreeMap. This is a shortcut to *fx.plot*.
15179
15180
*/
15181
plot: function(){
15182
this.fx.plot();
15183
},
15184
15185
/*
15186
Method: leaf
15187
15188
Returns whether the node is a leaf.
15189
15190
Parameters:
15191
15192
n - (object) A <Graph.Node>.
15193
15194
*/
15195
leaf: function(n){
15196
return n.getSubnodes([
15197
1, 1
15198
], "ignore").length == 0;
15199
},
15200
15201
/*
15202
Method: enter
15203
15204
Sets the node as root.
15205
15206
Parameters:
15207
15208
n - (object) A <Graph.Node>.
15209
15210
*/
15211
enter: function(n){
15212
if(this.busy) return;
15213
this.busy = true;
15214
15215
var that = this,
15216
config = this.config,
15217
graph = this.graph,
15218
clickedNode = n,
15219
previousClickedNode = this.clickedNode;
15220
15221
var callback = {
15222
onComplete: function() {
15223
//ensure that nodes are shown for that level
15224
if(config.levelsToShow > 0) {
15225
that.geom.setRightLevelToShow(n);
15226
}
15227
//compute positions of newly inserted nodes
15228
if(config.levelsToShow > 0 || config.request) that.compute();
15229
if(config.animate) {
15230
//fade nodes
15231
graph.nodeList.setData('alpha', 0, 'end');
15232
n.eachSubgraph(function(n) {
15233
n.setData('alpha', 1, 'end');
15234
}, "ignore");
15235
that.fx.animate({
15236
duration: 500,
15237
modes:['node-property:alpha'],
15238
onComplete: function() {
15239
//compute end positions
15240
that.clickedNode = clickedNode;
15241
that.compute('end');
15242
//animate positions
15243
//TODO(nico) commenting this line didn't seem to throw errors...
15244
that.clickedNode = previousClickedNode;
15245
that.fx.animate({
15246
modes:['linear', 'node-property:width:height'],
15247
duration: 1000,
15248
onComplete: function() {
15249
that.busy = false;
15250
//TODO(nico) check comment above
15251
that.clickedNode = clickedNode;
15252
}
15253
});
15254
}
15255
});
15256
} else {
15257
that.busy = false;
15258
that.clickedNode = n;
15259
that.refresh();
15260
}
15261
}
15262
};
15263
if(config.request) {
15264
this.requestNodes(clickedNode, callback);
15265
} else {
15266
callback.onComplete();
15267
}
15268
},
15269
15270
/*
15271
Method: out
15272
15273
Sets the parent node of the current selected node as root.
15274
15275
*/
15276
out: function(){
15277
if(this.busy) return;
15278
this.busy = true;
15279
this.events.hoveredNode = false;
15280
var that = this,
15281
config = this.config,
15282
graph = this.graph,
15283
parents = graph.getNode(this.clickedNode
15284
&& this.clickedNode.id || this.root).getParents(),
15285
parent = parents[0],
15286
clickedNode = parent,
15287
previousClickedNode = this.clickedNode;
15288
15289
//if no parents return
15290
if(!parent) {
15291
this.busy = false;
15292
return;
15293
}
15294
//final plot callback
15295
callback = {
15296
onComplete: function() {
15297
that.clickedNode = parent;
15298
if(config.request) {
15299
that.requestNodes(parent, {
15300
onComplete: function() {
15301
that.compute();
15302
that.plot();
15303
that.busy = false;
15304
}
15305
});
15306
} else {
15307
that.compute();
15308
that.plot();
15309
that.busy = false;
15310
}
15311
}
15312
};
15313
//prune tree
15314
if (config.levelsToShow > 0)
15315
this.geom.setRightLevelToShow(parent);
15316
//animate node positions
15317
if(config.animate) {
15318
this.clickedNode = clickedNode;
15319
this.compute('end');
15320
//animate the visible subtree only
15321
this.clickedNode = previousClickedNode;
15322
this.fx.animate({
15323
modes:['linear', 'node-property:width:height'],
15324
duration: 1000,
15325
onComplete: function() {
15326
//animate the parent subtree
15327
that.clickedNode = clickedNode;
15328
//change nodes alpha
15329
graph.eachNode(function(n) {
15330
n.setDataset(['current', 'end'], {
15331
'alpha': [0, 1]
15332
});
15333
}, "ignore");
15334
previousClickedNode.eachSubgraph(function(node) {
15335
node.setData('alpha', 1);
15336
}, "ignore");
15337
that.fx.animate({
15338
duration: 500,
15339
modes:['node-property:alpha'],
15340
onComplete: function() {
15341
callback.onComplete();
15342
}
15343
});
15344
}
15345
});
15346
} else {
15347
callback.onComplete();
15348
}
15349
},
15350
15351
requestNodes: function(node, onComplete){
15352
var handler = $.merge(this.controller, onComplete),
15353
lev = this.config.levelsToShow;
15354
if (handler.request) {
15355
var leaves = [], d = node._depth;
15356
node.eachLevel(0, lev, function(n){
15357
var nodeLevel = lev - (n._depth - d);
15358
if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
15359
leaves.push(n);
15360
n._level = nodeLevel;
15361
}
15362
});
15363
this.group.requestNodes(leaves, handler);
15364
} else {
15365
handler.onComplete();
15366
}
15367
},
15368
15369
reposition: function() {
15370
this.compute('end');
15371
}
15372
};
15373
15374
/*
15375
Class: TM.Op
15376
15377
Custom extension of <Graph.Op>.
15378
15379
Extends:
15380
15381
All <Graph.Op> methods
15382
15383
See also:
15384
15385
<Graph.Op>
15386
15387
*/
15388
TM.Op = new Class({
15389
Implements: Graph.Op,
15390
15391
initialize: function(viz){
15392
this.viz = viz;
15393
}
15394
});
15395
15396
//extend level methods of Graph.Geom
15397
TM.Geom = new Class({
15398
Implements: Graph.Geom,
15399
15400
getRightLevelToShow: function() {
15401
return this.viz.config.levelsToShow;
15402
},
15403
15404
setRightLevelToShow: function(node) {
15405
var level = this.getRightLevelToShow(),
15406
fx = this.viz.labels;
15407
node.eachLevel(0, level+1, function(n) {
15408
var d = n._depth - node._depth;
15409
if(d > level) {
15410
n.drawn = false;
15411
n.exist = false;
15412
n.ignore = true;
15413
fx.hideLabel(n, false);
15414
} else {
15415
n.drawn = true;
15416
n.exist = true;
15417
delete n.ignore;
15418
}
15419
});
15420
node.drawn = true;
15421
delete node.ignore;
15422
}
15423
});
15424
15425
/*
15426
15427
Performs operations on group of nodes.
15428
15429
*/
15430
TM.Group = new Class( {
15431
15432
initialize: function(viz){
15433
this.viz = viz;
15434
this.canvas = viz.canvas;
15435
this.config = viz.config;
15436
},
15437
15438
/*
15439
15440
Calls the request method on the controller to request a subtree for each node.
15441
*/
15442
requestNodes: function(nodes, controller){
15443
var counter = 0, len = nodes.length, nodeSelected = {};
15444
var complete = function(){
15445
controller.onComplete();
15446
};
15447
var viz = this.viz;
15448
if (len == 0)
15449
complete();
15450
for ( var i = 0; i < len; i++) {
15451
nodeSelected[nodes[i].id] = nodes[i];
15452
controller.request(nodes[i].id, nodes[i]._level, {
15453
onComplete: function(nodeId, data){
15454
if (data && data.children) {
15455
data.id = nodeId;
15456
viz.op.sum(data, {
15457
type: 'nothing'
15458
});
15459
}
15460
if (++counter == len) {
15461
viz.graph.computeLevels(viz.root, 0);
15462
complete();
15463
}
15464
}
15465
});
15466
}
15467
}
15468
});
15469
15470
/*
15471
Class: TM.Plot
15472
15473
Custom extension of <Graph.Plot>.
15474
15475
Extends:
15476
15477
All <Graph.Plot> methods
15478
15479
See also:
15480
15481
<Graph.Plot>
15482
15483
*/
15484
TM.Plot = new Class({
15485
15486
Implements: Graph.Plot,
15487
15488
initialize: function(viz){
15489
this.viz = viz;
15490
this.config = viz.config;
15491
this.node = this.config.Node;
15492
this.edge = this.config.Edge;
15493
this.animation = new Animation;
15494
this.nodeTypes = new TM.Plot.NodeTypes;
15495
this.edgeTypes = new TM.Plot.EdgeTypes;
15496
this.labels = viz.labels;
15497
},
15498
15499
plot: function(opt, animating){
15500
var viz = this.viz,
15501
graph = viz.graph;
15502
viz.canvas.clear();
15503
this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
15504
'withLabels': true,
15505
'hideLabels': false,
15506
'plotSubtree': function(n, ch){
15507
return n.anySubnode("exist");
15508
}
15509
}), animating);
15510
}
15511
});
15512
15513
/*
15514
Class: TM.Label
15515
15516
Custom extension of <Graph.Label>.
15517
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15518
15519
Extends:
15520
15521
All <Graph.Label> methods and subclasses.
15522
15523
See also:
15524
15525
<Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15526
15527
*/
15528
TM.Label = {};
15529
15530
/*
15531
TM.Label.Native
15532
15533
Custom extension of <Graph.Label.Native>.
15534
15535
Extends:
15536
15537
All <Graph.Label.Native> methods
15538
15539
See also:
15540
15541
<Graph.Label.Native>
15542
*/
15543
TM.Label.Native = new Class({
15544
Implements: Graph.Label.Native,
15545
15546
initialize: function(viz) {
15547
this.config = viz.config;
15548
this.leaf = viz.leaf;
15549
},
15550
15551
renderLabel: function(canvas, node, controller){
15552
if(!this.leaf(node) && !this.config.titleHeight) return;
15553
var pos = node.pos.getc(true),
15554
ctx = canvas.getCtx(),
15555
width = node.getData('width'),
15556
height = node.getData('height'),
15557
x = pos.x + width/2,
15558
y = pos.y;
15559
15560
ctx.fillText(node.name, x, y, width);
15561
}
15562
});
15563
15564
/*
15565
TM.Label.SVG
15566
15567
Custom extension of <Graph.Label.SVG>.
15568
15569
Extends:
15570
15571
All <Graph.Label.SVG> methods
15572
15573
See also:
15574
15575
<Graph.Label.SVG>
15576
*/
15577
TM.Label.SVG = new Class( {
15578
Implements: Graph.Label.SVG,
15579
15580
initialize: function(viz){
15581
this.viz = viz;
15582
this.leaf = viz.leaf;
15583
this.config = viz.config;
15584
},
15585
15586
/*
15587
placeLabel
15588
15589
Overrides abstract method placeLabel in <Graph.Plot>.
15590
15591
Parameters:
15592
15593
tag - A DOM label element.
15594
node - A <Graph.Node>.
15595
controller - A configuration/controller object passed to the visualization.
15596
15597
*/
15598
placeLabel: function(tag, node, controller){
15599
var pos = node.pos.getc(true),
15600
canvas = this.viz.canvas,
15601
ox = canvas.translateOffsetX,
15602
oy = canvas.translateOffsetY,
15603
sx = canvas.scaleOffsetX,
15604
sy = canvas.scaleOffsetY,
15605
radius = canvas.getSize();
15606
var labelPos = {
15607
x: Math.round(pos.x * sx + ox + radius.width / 2),
15608
y: Math.round(pos.y * sy + oy + radius.height / 2)
15609
};
15610
tag.setAttribute('x', labelPos.x);
15611
tag.setAttribute('y', labelPos.y);
15612
15613
if(!this.leaf(node) && !this.config.titleHeight) {
15614
tag.style.display = 'none';
15615
}
15616
controller.onPlaceLabel(tag, node);
15617
}
15618
});
15619
15620
/*
15621
TM.Label.HTML
15622
15623
Custom extension of <Graph.Label.HTML>.
15624
15625
Extends:
15626
15627
All <Graph.Label.HTML> methods.
15628
15629
See also:
15630
15631
<Graph.Label.HTML>
15632
15633
*/
15634
TM.Label.HTML = new Class( {
15635
Implements: Graph.Label.HTML,
15636
15637
initialize: function(viz){
15638
this.viz = viz;
15639
this.leaf = viz.leaf;
15640
this.config = viz.config;
15641
},
15642
15643
/*
15644
placeLabel
15645
15646
Overrides abstract method placeLabel in <Graph.Plot>.
15647
15648
Parameters:
15649
15650
tag - A DOM label element.
15651
node - A <Graph.Node>.
15652
controller - A configuration/controller object passed to the visualization.
15653
15654
*/
15655
placeLabel: function(tag, node, controller){
15656
var pos = node.pos.getc(true),
15657
canvas = this.viz.canvas,
15658
ox = canvas.translateOffsetX,
15659
oy = canvas.translateOffsetY,
15660
sx = canvas.scaleOffsetX,
15661
sy = canvas.scaleOffsetY,
15662
radius = canvas.getSize();
15663
var labelPos = {
15664
x: Math.round(pos.x * sx + ox + radius.width / 2),
15665
y: Math.round(pos.y * sy + oy + radius.height / 2)
15666
};
15667
15668
var style = tag.style;
15669
style.left = labelPos.x + 'px';
15670
style.top = labelPos.y + 'px';
15671
style.width = node.getData('width') * sx + 'px';
15672
style.height = node.getData('height') * sy + 'px';
15673
style.zIndex = node._depth * 100;
15674
style.display = '';
15675
15676
if(!this.leaf(node) && !this.config.titleHeight) {
15677
tag.style.display = 'none';
15678
}
15679
controller.onPlaceLabel(tag, node);
15680
}
15681
});
15682
15683
/*
15684
Class: TM.Plot.NodeTypes
15685
15686
This class contains a list of <Graph.Node> built-in types.
15687
Node types implemented are 'none', 'rectangle'.
15688
15689
You can add your custom node types, customizing your visualization to the extreme.
15690
15691
Example:
15692
15693
(start code js)
15694
TM.Plot.NodeTypes.implement({
15695
'mySpecialType': {
15696
'render': function(node, canvas) {
15697
//print your custom node to canvas
15698
},
15699
//optional
15700
'contains': function(node, pos) {
15701
//return true if pos is inside the node or false otherwise
15702
}
15703
}
15704
});
15705
(end code)
15706
15707
*/
15708
TM.Plot.NodeTypes = new Class( {
15709
'none': {
15710
'render': $.empty
15711
},
15712
15713
'rectangle': {
15714
'render': function(node, canvas, animating){
15715
var leaf = this.viz.leaf(node),
15716
config = this.config,
15717
offst = config.offset,
15718
titleHeight = config.titleHeight,
15719
pos = node.pos.getc(true),
15720
width = node.getData('width'),
15721
height = node.getData('height'),
15722
border = node.getData('border'),
15723
ctx = canvas.getCtx(),
15724
posx = pos.x + offst / 2,
15725
posy = pos.y + offst / 2;
15726
if(width <= offst || height <= offst) return;
15727
if (leaf) {
15728
if(config.cushion) {
15729
var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
15730
posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
15731
var color = node.getData('color');
15732
var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
15733
function(r) { return r * 0.2 >> 0; }));
15734
lg.addColorStop(0, color);
15735
lg.addColorStop(1, colorGrad);
15736
ctx.fillStyle = lg;
15737
}
15738
ctx.fillRect(posx, posy, width - offst, height - offst);
15739
if(border) {
15740
ctx.save();
15741
ctx.strokeStyle = border;
15742
ctx.strokeRect(posx, posy, width - offst, height - offst);
15743
ctx.restore();
15744
}
15745
} else if(titleHeight > 0){
15746
ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15747
titleHeight - offst);
15748
if(border) {
15749
ctx.save();
15750
ctx.strokeStyle = border;
15751
ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15752
height - offst);
15753
ctx.restore();
15754
}
15755
}
15756
},
15757
'contains': function(node, pos) {
15758
if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
15759
var npos = node.pos.getc(true),
15760
width = node.getData('width'),
15761
leaf = this.viz.leaf(node),
15762
height = leaf? node.getData('height') : this.config.titleHeight;
15763
return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
15764
}
15765
}
15766
});
15767
15768
TM.Plot.EdgeTypes = new Class( {
15769
'none': $.empty
15770
});
15771
15772
/*
15773
Class: TM.SliceAndDice
15774
15775
A slice and dice TreeMap visualization.
15776
15777
Implements:
15778
15779
All <TM.Base> methods and properties.
15780
*/
15781
TM.SliceAndDice = new Class( {
15782
Implements: [
15783
Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
15784
]
15785
});
15786
15787
/*
15788
Class: TM.Squarified
15789
15790
A squarified TreeMap visualization.
15791
15792
Implements:
15793
15794
All <TM.Base> methods and properties.
15795
*/
15796
TM.Squarified = new Class( {
15797
Implements: [
15798
Loader, Extras, TM.Base, Layouts.TM.Squarified
15799
]
15800
});
15801
15802
/*
15803
Class: TM.Strip
15804
15805
A strip TreeMap visualization.
15806
15807
Implements:
15808
15809
All <TM.Base> methods and properties.
15810
*/
15811
TM.Strip = new Class( {
15812
Implements: [
15813
Loader, Extras, TM.Base, Layouts.TM.Strip
15814
]
15815
});
15816
15817
15818
/*
15819
* File: RGraph.js
15820
*
15821
*/
15822
15823
/*
15824
Class: RGraph
15825
15826
A radial graph visualization with advanced animations.
15827
15828
Inspired by:
15829
15830
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>
15831
15832
Note:
15833
15834
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.
15835
15836
Implements:
15837
15838
All <Loader> methods
15839
15840
Constructor Options:
15841
15842
Inherits options from
15843
15844
- <Options.Canvas>
15845
- <Options.Controller>
15846
- <Options.Node>
15847
- <Options.Edge>
15848
- <Options.Label>
15849
- <Options.Events>
15850
- <Options.Tips>
15851
- <Options.NodeStyles>
15852
- <Options.Navigation>
15853
15854
Additionally, there are other parameters and some default values changed
15855
15856
interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
15857
levelDistance - (number) Default's *100*. The distance between levels of the tree.
15858
15859
Instance Properties:
15860
15861
canvas - Access a <Canvas> instance.
15862
graph - Access a <Graph> instance.
15863
op - Access a <RGraph.Op> instance.
15864
fx - Access a <RGraph.Plot> instance.
15865
labels - Access a <RGraph.Label> interface implementation.
15866
*/
15867
15868
$jit.RGraph = new Class( {
15869
15870
Implements: [
15871
Loader, Extras, Layouts.Radial
15872
],
15873
15874
initialize: function(controller){
15875
var $RGraph = $jit.RGraph;
15876
15877
var config = {
15878
interpolation: 'linear',
15879
levelDistance: 100
15880
};
15881
15882
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15883
"Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15884
15885
var canvasConfig = this.config;
15886
if(canvasConfig.useCanvas) {
15887
this.canvas = canvasConfig.useCanvas;
15888
this.config.labelContainer = this.canvas.id + '-label';
15889
} else {
15890
if(canvasConfig.background) {
15891
canvasConfig.background = $.merge({
15892
type: 'Circles'
15893
}, canvasConfig.background);
15894
}
15895
this.canvas = new Canvas(this, canvasConfig);
15896
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15897
}
15898
15899
this.graphOptions = {
15900
'klass': Polar,
15901
'Node': {
15902
'selected': false,
15903
'exist': true,
15904
'drawn': true
15905
}
15906
};
15907
this.graph = new Graph(this.graphOptions, this.config.Node,
15908
this.config.Edge);
15909
this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
15910
this.fx = new $RGraph.Plot(this, $RGraph);
15911
this.op = new $RGraph.Op(this);
15912
this.json = null;
15913
this.root = null;
15914
this.busy = false;
15915
this.parent = false;
15916
// initialize extras
15917
this.initializeExtras();
15918
},
15919
15920
/*
15921
15922
createLevelDistanceFunc
15923
15924
Returns the levelDistance function used for calculating a node distance
15925
to its origin. This function returns a function that is computed
15926
per level and not per node, such that all nodes with the same depth will have the
15927
same distance to the origin. The resulting function gets the
15928
parent node as parameter and returns a float.
15929
15930
*/
15931
createLevelDistanceFunc: function(){
15932
var ld = this.config.levelDistance;
15933
return function(elem){
15934
return (elem._depth + 1) * ld;
15935
};
15936
},
15937
15938
/*
15939
Method: refresh
15940
15941
Computes positions and plots the tree.
15942
15943
*/
15944
refresh: function(){
15945
this.compute();
15946
this.plot();
15947
},
15948
15949
reposition: function(){
15950
this.compute('end');
15951
},
15952
15953
/*
15954
Method: plot
15955
15956
Plots the RGraph. This is a shortcut to *fx.plot*.
15957
*/
15958
plot: function(){
15959
this.fx.plot();
15960
},
15961
/*
15962
getNodeAndParentAngle
15963
15964
Returns the _parent_ of the given node, also calculating its angle span.
15965
*/
15966
getNodeAndParentAngle: function(id){
15967
var theta = false;
15968
var n = this.graph.getNode(id);
15969
var ps = n.getParents();
15970
var p = (ps.length > 0)? ps[0] : false;
15971
if (p) {
15972
var posParent = p.pos.getc(), posChild = n.pos.getc();
15973
var newPos = posParent.add(posChild.scale(-1));
15974
theta = Math.atan2(newPos.y, newPos.x);
15975
if (theta < 0)
15976
theta += 2 * Math.PI;
15977
}
15978
return {
15979
parent: p,
15980
theta: theta
15981
};
15982
},
15983
/*
15984
tagChildren
15985
15986
Enumerates the children in order to maintain child ordering (second constraint of the paper).
15987
*/
15988
tagChildren: function(par, id){
15989
if (par.angleSpan) {
15990
var adjs = [];
15991
par.eachAdjacency(function(elem){
15992
adjs.push(elem.nodeTo);
15993
}, "ignore");
15994
var len = adjs.length;
15995
for ( var i = 0; i < len && id != adjs[i].id; i++)
15996
;
15997
for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
15998
adjs[j].dist = k++;
15999
}
16000
}
16001
},
16002
/*
16003
Method: onClick
16004
16005
Animates the <RGraph> to center the node specified by *id*.
16006
16007
Parameters:
16008
16009
id - A <Graph.Node> id.
16010
opt - (optional|object) An object containing some extra properties described below
16011
hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16012
16013
Example:
16014
16015
(start code js)
16016
rgraph.onClick('someid');
16017
//or also...
16018
rgraph.onClick('someid', {
16019
hideLabels: false
16020
});
16021
(end code)
16022
16023
*/
16024
onClick: function(id, opt){
16025
if (this.root != id && !this.busy) {
16026
this.busy = true;
16027
this.root = id;
16028
var that = this;
16029
this.controller.onBeforeCompute(this.graph.getNode(id));
16030
var obj = this.getNodeAndParentAngle(id);
16031
16032
// second constraint
16033
this.tagChildren(obj.parent, id);
16034
this.parent = obj.parent;
16035
this.compute('end');
16036
16037
// first constraint
16038
var thetaDiff = obj.theta - obj.parent.endPos.theta;
16039
this.graph.eachNode(function(elem){
16040
elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
16041
});
16042
16043
var mode = this.config.interpolation;
16044
opt = $.merge( {
16045
onComplete: $.empty
16046
}, opt || {});
16047
16048
this.fx.animate($.merge( {
16049
hideLabels: true,
16050
modes: [
16051
mode
16052
]
16053
}, opt, {
16054
onComplete: function(){
16055
that.busy = false;
16056
opt.onComplete();
16057
}
16058
}));
16059
}
16060
}
16061
});
16062
16063
$jit.RGraph.$extend = true;
16064
16065
(function(RGraph){
16066
16067
/*
16068
Class: RGraph.Op
16069
16070
Custom extension of <Graph.Op>.
16071
16072
Extends:
16073
16074
All <Graph.Op> methods
16075
16076
See also:
16077
16078
<Graph.Op>
16079
16080
*/
16081
RGraph.Op = new Class( {
16082
16083
Implements: Graph.Op
16084
16085
});
16086
16087
/*
16088
Class: RGraph.Plot
16089
16090
Custom extension of <Graph.Plot>.
16091
16092
Extends:
16093
16094
All <Graph.Plot> methods
16095
16096
See also:
16097
16098
<Graph.Plot>
16099
16100
*/
16101
RGraph.Plot = new Class( {
16102
16103
Implements: Graph.Plot
16104
16105
});
16106
16107
/*
16108
Object: RGraph.Label
16109
16110
Custom extension of <Graph.Label>.
16111
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16112
16113
Extends:
16114
16115
All <Graph.Label> methods and subclasses.
16116
16117
See also:
16118
16119
<Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16120
16121
*/
16122
RGraph.Label = {};
16123
16124
/*
16125
RGraph.Label.Native
16126
16127
Custom extension of <Graph.Label.Native>.
16128
16129
Extends:
16130
16131
All <Graph.Label.Native> methods
16132
16133
See also:
16134
16135
<Graph.Label.Native>
16136
16137
*/
16138
RGraph.Label.Native = new Class( {
16139
Implements: Graph.Label.Native
16140
});
16141
16142
/*
16143
RGraph.Label.SVG
16144
16145
Custom extension of <Graph.Label.SVG>.
16146
16147
Extends:
16148
16149
All <Graph.Label.SVG> methods
16150
16151
See also:
16152
16153
<Graph.Label.SVG>
16154
16155
*/
16156
RGraph.Label.SVG = new Class( {
16157
Implements: Graph.Label.SVG,
16158
16159
initialize: function(viz){
16160
this.viz = viz;
16161
},
16162
16163
/*
16164
placeLabel
16165
16166
Overrides abstract method placeLabel in <Graph.Plot>.
16167
16168
Parameters:
16169
16170
tag - A DOM label element.
16171
node - A <Graph.Node>.
16172
controller - A configuration/controller object passed to the visualization.
16173
16174
*/
16175
placeLabel: function(tag, node, controller){
16176
var pos = node.pos.getc(true),
16177
canvas = this.viz.canvas,
16178
ox = canvas.translateOffsetX,
16179
oy = canvas.translateOffsetY,
16180
sx = canvas.scaleOffsetX,
16181
sy = canvas.scaleOffsetY,
16182
radius = canvas.getSize();
16183
var labelPos = {
16184
x: Math.round(pos.x * sx + ox + radius.width / 2),
16185
y: Math.round(pos.y * sy + oy + radius.height / 2)
16186
};
16187
tag.setAttribute('x', labelPos.x);
16188
tag.setAttribute('y', labelPos.y);
16189
16190
controller.onPlaceLabel(tag, node);
16191
}
16192
});
16193
16194
/*
16195
RGraph.Label.HTML
16196
16197
Custom extension of <Graph.Label.HTML>.
16198
16199
Extends:
16200
16201
All <Graph.Label.HTML> methods.
16202
16203
See also:
16204
16205
<Graph.Label.HTML>
16206
16207
*/
16208
RGraph.Label.HTML = new Class( {
16209
Implements: Graph.Label.HTML,
16210
16211
initialize: function(viz){
16212
this.viz = viz;
16213
},
16214
/*
16215
placeLabel
16216
16217
Overrides abstract method placeLabel in <Graph.Plot>.
16218
16219
Parameters:
16220
16221
tag - A DOM label element.
16222
node - A <Graph.Node>.
16223
controller - A configuration/controller object passed to the visualization.
16224
16225
*/
16226
placeLabel: function(tag, node, controller){
16227
var pos = node.pos.getc(true),
16228
canvas = this.viz.canvas,
16229
ox = canvas.translateOffsetX,
16230
oy = canvas.translateOffsetY,
16231
sx = canvas.scaleOffsetX,
16232
sy = canvas.scaleOffsetY,
16233
radius = canvas.getSize();
16234
var labelPos = {
16235
x: Math.round(pos.x * sx + ox + radius.width / 2),
16236
y: Math.round(pos.y * sy + oy + radius.height / 2)
16237
};
16238
16239
var style = tag.style;
16240
style.left = labelPos.x + 'px';
16241
style.top = labelPos.y + 'px';
16242
style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
16243
16244
controller.onPlaceLabel(tag, node);
16245
}
16246
});
16247
16248
/*
16249
Class: RGraph.Plot.NodeTypes
16250
16251
This class contains a list of <Graph.Node> built-in types.
16252
Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16253
16254
You can add your custom node types, customizing your visualization to the extreme.
16255
16256
Example:
16257
16258
(start code js)
16259
RGraph.Plot.NodeTypes.implement({
16260
'mySpecialType': {
16261
'render': function(node, canvas) {
16262
//print your custom node to canvas
16263
},
16264
//optional
16265
'contains': function(node, pos) {
16266
//return true if pos is inside the node or false otherwise
16267
}
16268
}
16269
});
16270
(end code)
16271
16272
*/
16273
RGraph.Plot.NodeTypes = new Class({
16274
'none': {
16275
'render': $.empty,
16276
'contains': $.lambda(false)
16277
},
16278
'circle': {
16279
'render': function(node, canvas){
16280
var pos = node.pos.getc(true),
16281
dim = node.getData('dim');
16282
this.nodeHelper.circle.render('fill', pos, dim, canvas);
16283
},
16284
'contains': function(node, pos){
16285
var npos = node.pos.getc(true),
16286
dim = node.getData('dim');
16287
return this.nodeHelper.circle.contains(npos, pos, dim);
16288
}
16289
},
16290
'ellipse': {
16291
'render': function(node, canvas){
16292
var pos = node.pos.getc(true),
16293
width = node.getData('width'),
16294
height = node.getData('height');
16295
this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
16296
},
16297
'contains': function(node, pos){
16298
var npos = node.pos.getc(true),
16299
width = node.getData('width'),
16300
height = node.getData('height');
16301
return this.nodeHelper.ellipse.contains(npos, pos, width, height);
16302
}
16303
},
16304
'square': {
16305
'render': function(node, canvas){
16306
var pos = node.pos.getc(true),
16307
dim = node.getData('dim');
16308
this.nodeHelper.square.render('fill', pos, dim, canvas);
16309
},
16310
'contains': function(node, pos){
16311
var npos = node.pos.getc(true),
16312
dim = node.getData('dim');
16313
return this.nodeHelper.square.contains(npos, pos, dim);
16314
}
16315
},
16316
'rectangle': {
16317
'render': function(node, canvas){
16318
var pos = node.pos.getc(true),
16319
width = node.getData('width'),
16320
height = node.getData('height');
16321
this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
16322
},
16323
'contains': function(node, pos){
16324
var npos = node.pos.getc(true),
16325
width = node.getData('width'),
16326
height = node.getData('height');
16327
return this.nodeHelper.rectangle.contains(npos, pos, width, height);
16328
}
16329
},
16330
'triangle': {
16331
'render': function(node, canvas){
16332
var pos = node.pos.getc(true),
16333
dim = node.getData('dim');
16334
this.nodeHelper.triangle.render('fill', pos, dim, canvas);
16335
},
16336
'contains': function(node, pos) {
16337
var npos = node.pos.getc(true),
16338
dim = node.getData('dim');
16339
return this.nodeHelper.triangle.contains(npos, pos, dim);
16340
}
16341
},
16342
'star': {
16343
'render': function(node, canvas){
16344
var pos = node.pos.getc(true),
16345
dim = node.getData('dim');
16346
this.nodeHelper.star.render('fill', pos, dim, canvas);
16347
},
16348
'contains': function(node, pos) {
16349
var npos = node.pos.getc(true),
16350
dim = node.getData('dim');
16351
return this.nodeHelper.star.contains(npos, pos, dim);
16352
}
16353
}
16354
});
16355
16356
/*
16357
Class: RGraph.Plot.EdgeTypes
16358
16359
This class contains a list of <Graph.Adjacence> built-in types.
16360
Edge types implemented are 'none', 'line' and 'arrow'.
16361
16362
You can add your custom edge types, customizing your visualization to the extreme.
16363
16364
Example:
16365
16366
(start code js)
16367
RGraph.Plot.EdgeTypes.implement({
16368
'mySpecialType': {
16369
'render': function(adj, canvas) {
16370
//print your custom edge to canvas
16371
},
16372
//optional
16373
'contains': function(adj, pos) {
16374
//return true if pos is inside the arc or false otherwise
16375
}
16376
}
16377
});
16378
(end code)
16379
16380
*/
16381
RGraph.Plot.EdgeTypes = new Class({
16382
'none': $.empty,
16383
'line': {
16384
'render': function(adj, canvas) {
16385
var from = adj.nodeFrom.pos.getc(true),
16386
to = adj.nodeTo.pos.getc(true);
16387
this.edgeHelper.line.render(from, to, canvas);
16388
},
16389
'contains': function(adj, pos) {
16390
var from = adj.nodeFrom.pos.getc(true),
16391
to = adj.nodeTo.pos.getc(true);
16392
return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
16393
}
16394
},
16395
'arrow': {
16396
'render': function(adj, canvas) {
16397
var from = adj.nodeFrom.pos.getc(true),
16398
to = adj.nodeTo.pos.getc(true),
16399
dim = adj.getData('dim'),
16400
direction = adj.data.$direction,
16401
inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
16402
this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
16403
},
16404
'contains': function(adj, pos) {
16405
var from = adj.nodeFrom.pos.getc(true),
16406
to = adj.nodeTo.pos.getc(true);
16407
return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
16408
}
16409
}
16410
});
16411
16412
})($jit.RGraph);
16413
16414
16415
/*
16416
* File: Hypertree.js
16417
*
16418
*/
16419
16420
/*
16421
Complex
16422
16423
A multi-purpose Complex Class with common methods. Extended for the Hypertree.
16424
16425
*/
16426
/*
16427
moebiusTransformation
16428
16429
Calculates a moebius transformation for this point / complex.
16430
For more information go to:
16431
http://en.wikipedia.org/wiki/Moebius_transformation.
16432
16433
Parameters:
16434
16435
c - An initialized Complex instance representing a translation Vector.
16436
*/
16437
16438
Complex.prototype.moebiusTransformation = function(c) {
16439
var num = this.add(c);
16440
var den = c.$conjugate().$prod(this);
16441
den.x++;
16442
return num.$div(den);
16443
};
16444
16445
/*
16446
moebiusTransformation
16447
16448
Calculates a moebius transformation for the hyperbolic tree.
16449
16450
<http://en.wikipedia.org/wiki/Moebius_transformation>
16451
16452
Parameters:
16453
16454
graph - A <Graph> instance.
16455
pos - A <Complex>.
16456
prop - A property array.
16457
theta - Rotation angle.
16458
startPos - _optional_ start position.
16459
*/
16460
Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
16461
this.eachNode(graph, function(elem) {
16462
for ( var i = 0; i < prop.length; i++) {
16463
var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
16464
elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
16465
}
16466
}, flags);
16467
};
16468
16469
/*
16470
Class: Hypertree
16471
16472
A Hyperbolic Tree/Graph visualization.
16473
16474
Inspired by:
16475
16476
A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
16477
<http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
16478
16479
Note:
16480
16481
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.
16482
16483
Implements:
16484
16485
All <Loader> methods
16486
16487
Constructor Options:
16488
16489
Inherits options from
16490
16491
- <Options.Canvas>
16492
- <Options.Controller>
16493
- <Options.Node>
16494
- <Options.Edge>
16495
- <Options.Label>
16496
- <Options.Events>
16497
- <Options.Tips>
16498
- <Options.NodeStyles>
16499
- <Options.Navigation>
16500
16501
Additionally, there are other parameters and some default values changed
16502
16503
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*.
16504
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.
16505
fps - Described in <Options.Fx>. It's default value has been changed to *35*.
16506
duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
16507
Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
16508
16509
Instance Properties:
16510
16511
canvas - Access a <Canvas> instance.
16512
graph - Access a <Graph> instance.
16513
op - Access a <Hypertree.Op> instance.
16514
fx - Access a <Hypertree.Plot> instance.
16515
labels - Access a <Hypertree.Label> interface implementation.
16516
16517
*/
16518
16519
$jit.Hypertree = new Class( {
16520
16521
Implements: [ Loader, Extras, Layouts.Radial ],
16522
16523
initialize: function(controller) {
16524
var $Hypertree = $jit.Hypertree;
16525
16526
var config = {
16527
radius: "auto",
16528
offset: 0,
16529
Edge: {
16530
type: 'hyperline'
16531
},
16532
duration: 1500,
16533
fps: 35
16534
};
16535
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
16536
"Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
16537
16538
var canvasConfig = this.config;
16539
if(canvasConfig.useCanvas) {
16540
this.canvas = canvasConfig.useCanvas;
16541
this.config.labelContainer = this.canvas.id + '-label';
16542
} else {
16543
if(canvasConfig.background) {
16544
canvasConfig.background = $.merge({
16545
type: 'Circles'
16546
}, canvasConfig.background);
16547
}
16548
this.canvas = new Canvas(this, canvasConfig);
16549
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
16550
}
16551
16552
this.graphOptions = {
16553
'klass': Polar,
16554
'Node': {
16555
'selected': false,
16556
'exist': true,
16557
'drawn': true
16558
}
16559
};
16560
this.graph = new Graph(this.graphOptions, this.config.Node,
16561
this.config.Edge);
16562
this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
16563
this.fx = new $Hypertree.Plot(this, $Hypertree);
16564
this.op = new $Hypertree.Op(this);
16565
this.json = null;
16566
this.root = null;
16567
this.busy = false;
16568
// initialize extras
16569
this.initializeExtras();
16570
},
16571
16572
/*
16573
16574
createLevelDistanceFunc
16575
16576
Returns the levelDistance function used for calculating a node distance
16577
to its origin. This function returns a function that is computed
16578
per level and not per node, such that all nodes with the same depth will have the
16579
same distance to the origin. The resulting function gets the
16580
parent node as parameter and returns a float.
16581
16582
*/
16583
createLevelDistanceFunc: function() {
16584
// get max viz. length.
16585
var r = this.getRadius();
16586
// get max depth.
16587
var depth = 0, max = Math.max, config = this.config;
16588
this.graph.eachNode(function(node) {
16589
depth = max(node._depth, depth);
16590
}, "ignore");
16591
depth++;
16592
// node distance generator
16593
var genDistFunc = function(a) {
16594
return function(node) {
16595
node.scale = r;
16596
var d = node._depth + 1;
16597
var acum = 0, pow = Math.pow;
16598
while (d) {
16599
acum += pow(a, d--);
16600
}
16601
return acum - config.offset;
16602
};
16603
};
16604
// estimate better edge length.
16605
for ( var i = 0.51; i <= 1; i += 0.01) {
16606
var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
16607
if (valSeries >= 2) { return genDistFunc(i - 0.01); }
16608
}
16609
return genDistFunc(0.75);
16610
},
16611
16612
/*
16613
Method: getRadius
16614
16615
Returns the current radius of the visualization. If *config.radius* is *auto* then it
16616
calculates the radius by taking the smaller size of the <Canvas> widget.
16617
16618
See also:
16619
16620
<Canvas.getSize>
16621
16622
*/
16623
getRadius: function() {
16624
var rad = this.config.radius;
16625
if (rad !== "auto") { return rad; }
16626
var s = this.canvas.getSize();
16627
return Math.min(s.width, s.height) / 2;
16628
},
16629
16630
/*
16631
Method: refresh
16632
16633
Computes positions and plots the tree.
16634
16635
Parameters:
16636
16637
reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
16638
16639
*/
16640
refresh: function(reposition) {
16641
if (reposition) {
16642
this.reposition();
16643
this.graph.eachNode(function(node) {
16644
node.startPos.rho = node.pos.rho = node.endPos.rho;
16645
node.startPos.theta = node.pos.theta = node.endPos.theta;
16646
});
16647
} else {
16648
this.compute();
16649
}
16650
this.plot();
16651
},
16652
16653
/*
16654
reposition
16655
16656
Computes nodes' positions and restores the tree to its previous position.
16657
16658
For calculating nodes' positions the root must be placed on its origin. This method does this
16659
and then attemps to restore the hypertree to its previous position.
16660
16661
*/
16662
reposition: function() {
16663
this.compute('end');
16664
var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
16665
Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
16666
'end', "ignore");
16667
this.graph.eachNode(function(node) {
16668
if (node.ignore) {
16669
node.endPos.rho = node.pos.rho;
16670
node.endPos.theta = node.pos.theta;
16671
}
16672
});
16673
},
16674
16675
/*
16676
Method: plot
16677
16678
Plots the <Hypertree>. This is a shortcut to *fx.plot*.
16679
16680
*/
16681
plot: function() {
16682
this.fx.plot();
16683
},
16684
16685
/*
16686
Method: onClick
16687
16688
Animates the <Hypertree> to center the node specified by *id*.
16689
16690
Parameters:
16691
16692
id - A <Graph.Node> id.
16693
opt - (optional|object) An object containing some extra properties described below
16694
hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16695
16696
Example:
16697
16698
(start code js)
16699
ht.onClick('someid');
16700
//or also...
16701
ht.onClick('someid', {
16702
hideLabels: false
16703
});
16704
(end code)
16705
16706
*/
16707
onClick: function(id, opt) {
16708
var pos = this.graph.getNode(id).pos.getc(true);
16709
this.move(pos, opt);
16710
},
16711
16712
/*
16713
Method: move
16714
16715
Translates the tree to the given position.
16716
16717
Parameters:
16718
16719
pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
16720
opt - This object has been defined in <Hypertree.onClick>
16721
16722
Example:
16723
16724
(start code js)
16725
ht.move({ x: 0, y: 0.7 }, {
16726
hideLabels: false
16727
});
16728
(end code)
16729
16730
*/
16731
move: function(pos, opt) {
16732
var versor = $C(pos.x, pos.y);
16733
if (this.busy === false && versor.norm() < 1) {
16734
this.busy = true;
16735
var root = this.graph.getClosestNodeToPos(versor), that = this;
16736
this.graph.computeLevels(root.id, 0);
16737
this.controller.onBeforeCompute(root);
16738
opt = $.merge( {
16739
onComplete: $.empty
16740
}, opt || {});
16741
this.fx.animate($.merge( {
16742
modes: [ 'moebius' ],
16743
hideLabels: true
16744
}, opt, {
16745
onComplete: function() {
16746
that.busy = false;
16747
opt.onComplete();
16748
}
16749
}), versor);
16750
}
16751
}
16752
});
16753
16754
$jit.Hypertree.$extend = true;
16755
16756
(function(Hypertree) {
16757
16758
/*
16759
Class: Hypertree.Op
16760
16761
Custom extension of <Graph.Op>.
16762
16763
Extends:
16764
16765
All <Graph.Op> methods
16766
16767
See also:
16768
16769
<Graph.Op>
16770
16771
*/
16772
Hypertree.Op = new Class( {
16773
16774
Implements: Graph.Op
16775
16776
});
16777
16778
/*
16779
Class: Hypertree.Plot
16780
16781
Custom extension of <Graph.Plot>.
16782
16783
Extends:
16784
16785
All <Graph.Plot> methods
16786
16787
See also:
16788
16789
<Graph.Plot>
16790
16791
*/
16792
Hypertree.Plot = new Class( {
16793
16794
Implements: Graph.Plot
16795
16796
});
16797
16798
/*
16799
Object: Hypertree.Label
16800
16801
Custom extension of <Graph.Label>.
16802
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16803
16804
Extends:
16805
16806
All <Graph.Label> methods and subclasses.
16807
16808
See also:
16809
16810
<Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16811
16812
*/
16813
Hypertree.Label = {};
16814
16815
/*
16816
Hypertree.Label.Native
16817
16818
Custom extension of <Graph.Label.Native>.
16819
16820
Extends:
16821
16822
All <Graph.Label.Native> methods
16823
16824
See also:
16825
16826
<Graph.Label.Native>
16827
16828
*/
16829
Hypertree.Label.Native = new Class( {
16830
Implements: Graph.Label.Native,
16831
16832
initialize: function(viz) {
16833
this.viz = viz;
16834
},
16835
16836
renderLabel: function(canvas, node, controller) {
16837
var ctx = canvas.getCtx();
16838
var coord = node.pos.getc(true);
16839
var s = this.viz.getRadius();
16840
ctx.fillText(node.name, coord.x * s, coord.y * s);
16841
}
16842
});
16843
16844
/*
16845
Hypertree.Label.SVG
16846
16847
Custom extension of <Graph.Label.SVG>.
16848
16849
Extends:
16850
16851
All <Graph.Label.SVG> methods
16852
16853
See also:
16854
16855
<Graph.Label.SVG>
16856
16857
*/
16858
Hypertree.Label.SVG = new Class( {
16859
Implements: Graph.Label.SVG,
16860
16861
initialize: function(viz) {
16862
this.viz = viz;
16863
},
16864
16865
/*
16866
placeLabel
16867
16868
Overrides abstract method placeLabel in <Graph.Plot>.
16869
16870
Parameters:
16871
16872
tag - A DOM label element.
16873
node - A <Graph.Node>.
16874
controller - A configuration/controller object passed to the visualization.
16875
16876
*/
16877
placeLabel: function(tag, node, controller) {
16878
var pos = node.pos.getc(true),
16879
canvas = this.viz.canvas,
16880
ox = canvas.translateOffsetX,
16881
oy = canvas.translateOffsetY,
16882
sx = canvas.scaleOffsetX,
16883
sy = canvas.scaleOffsetY,
16884
radius = canvas.getSize(),
16885
r = this.viz.getRadius();
16886
var labelPos = {
16887
x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16888
y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16889
};
16890
tag.setAttribute('x', labelPos.x);
16891
tag.setAttribute('y', labelPos.y);
16892
controller.onPlaceLabel(tag, node);
16893
}
16894
});
16895
16896
/*
16897
Hypertree.Label.HTML
16898
16899
Custom extension of <Graph.Label.HTML>.
16900
16901
Extends:
16902
16903
All <Graph.Label.HTML> methods.
16904
16905
See also:
16906
16907
<Graph.Label.HTML>
16908
16909
*/
16910
Hypertree.Label.HTML = new Class( {
16911
Implements: Graph.Label.HTML,
16912
16913
initialize: function(viz) {
16914
this.viz = viz;
16915
},
16916
/*
16917
placeLabel
16918
16919
Overrides abstract method placeLabel in <Graph.Plot>.
16920
16921
Parameters:
16922
16923
tag - A DOM label element.
16924
node - A <Graph.Node>.
16925
controller - A configuration/controller object passed to the visualization.
16926
16927
*/
16928
placeLabel: function(tag, node, controller) {
16929
var pos = node.pos.getc(true),
16930
canvas = this.viz.canvas,
16931
ox = canvas.translateOffsetX,
16932
oy = canvas.translateOffsetY,
16933
sx = canvas.scaleOffsetX,
16934
sy = canvas.scaleOffsetY,
16935
radius = canvas.getSize(),
16936
r = this.viz.getRadius();
16937
var labelPos = {
16938
x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16939
y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16940
};
16941
var style = tag.style;
16942
style.left = labelPos.x + 'px';
16943
style.top = labelPos.y + 'px';
16944
style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
16945
16946
controller.onPlaceLabel(tag, node);
16947
}
16948
});
16949
16950
/*
16951
Class: Hypertree.Plot.NodeTypes
16952
16953
This class contains a list of <Graph.Node> built-in types.
16954
Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16955
16956
You can add your custom node types, customizing your visualization to the extreme.
16957
16958
Example:
16959
16960
(start code js)
16961
Hypertree.Plot.NodeTypes.implement({
16962
'mySpecialType': {
16963
'render': function(node, canvas) {
16964
//print your custom node to canvas
16965
},
16966
//optional
16967
'contains': function(node, pos) {
16968
//return true if pos is inside the node or false otherwise
16969
}
16970
}
16971
});
16972
(end code)
16973
16974
*/
16975
Hypertree.Plot.NodeTypes = new Class({
16976
'none': {
16977
'render': $.empty,
16978
'contains': $.lambda(false)
16979
},
16980
'circle': {
16981
'render': function(node, canvas) {
16982
var nconfig = this.node,
16983
dim = node.getData('dim'),
16984
p = node.pos.getc();
16985
dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
16986
p.$scale(node.scale);
16987
if (dim > 0.2) {
16988
this.nodeHelper.circle.render('fill', p, dim, canvas);
16989
}
16990
},
16991
'contains': function(node, pos) {
16992
var dim = node.getData('dim'),
16993
npos = node.pos.getc().$scale(node.scale);
16994
return this.nodeHelper.circle.contains(npos, pos, dim);
16995
}
16996
},
16997
'ellipse': {
16998
'render': function(node, canvas) {
16999
var pos = node.pos.getc().$scale(node.scale),
17000
width = node.getData('width'),
17001
height = node.getData('height');
17002
this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
17003
},
17004
'contains': function(node, pos) {
17005
var width = node.getData('width'),
17006
height = node.getData('height'),
17007
npos = node.pos.getc().$scale(node.scale);
17008
return this.nodeHelper.circle.contains(npos, pos, width, height);
17009
}
17010
},
17011
'square': {
17012
'render': function(node, canvas) {
17013
var nconfig = this.node,
17014
dim = node.getData('dim'),
17015
p = node.pos.getc();
17016
dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17017
p.$scale(node.scale);
17018
if (dim > 0.2) {
17019
this.nodeHelper.square.render('fill', p, dim, canvas);
17020
}
17021
},
17022
'contains': function(node, pos) {
17023
var dim = node.getData('dim'),
17024
npos = node.pos.getc().$scale(node.scale);
17025
return this.nodeHelper.square.contains(npos, pos, dim);
17026
}
17027
},
17028
'rectangle': {
17029
'render': function(node, canvas) {
17030
var nconfig = this.node,
17031
width = node.getData('width'),
17032
height = node.getData('height'),
17033
pos = node.pos.getc();
17034
width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
17035
height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
17036
pos.$scale(node.scale);
17037
if (width > 0.2 && height > 0.2) {
17038
this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
17039
}
17040
},
17041
'contains': function(node, pos) {
17042
var width = node.getData('width'),
17043
height = node.getData('height'),
17044
npos = node.pos.getc().$scale(node.scale);
17045
return this.nodeHelper.rectangle.contains(npos, pos, width, height);
17046
}
17047
},
17048
'triangle': {
17049
'render': function(node, canvas) {
17050
var nconfig = this.node,
17051
dim = node.getData('dim'),
17052
p = node.pos.getc();
17053
dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17054
p.$scale(node.scale);
17055
if (dim > 0.2) {
17056
this.nodeHelper.triangle.render('fill', p, dim, canvas);
17057
}
17058
},
17059
'contains': function(node, pos) {
17060
var dim = node.getData('dim'),
17061
npos = node.pos.getc().$scale(node.scale);
17062
return this.nodeHelper.triangle.contains(npos, pos, dim);
17063
}
17064
},
17065
'star': {
17066
'render': function(node, canvas) {
17067
var nconfig = this.node,
17068
dim = node.getData('dim'),
17069
p = node.pos.getc();
17070
dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17071
p.$scale(node.scale);
17072
if (dim > 0.2) {
17073
this.nodeHelper.star.render('fill', p, dim, canvas);
17074
}
17075
},
17076
'contains': function(node, pos) {
17077
var dim = node.getData('dim'),
17078
npos = node.pos.getc().$scale(node.scale);
17079
return this.nodeHelper.star.contains(npos, pos, dim);
17080
}
17081
}
17082
});
17083
17084
/*
17085
Class: Hypertree.Plot.EdgeTypes
17086
17087
This class contains a list of <Graph.Adjacence> built-in types.
17088
Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
17089
17090
You can add your custom edge types, customizing your visualization to the extreme.
17091
17092
Example:
17093
17094
(start code js)
17095
Hypertree.Plot.EdgeTypes.implement({
17096
'mySpecialType': {
17097
'render': function(adj, canvas) {
17098
//print your custom edge to canvas
17099
},
17100
//optional
17101
'contains': function(adj, pos) {
17102
//return true if pos is inside the arc or false otherwise
17103
}
17104
}
17105
});
17106
(end code)
17107
17108
*/
17109
Hypertree.Plot.EdgeTypes = new Class({
17110
'none': $.empty,
17111
'line': {
17112
'render': function(adj, canvas) {
17113
var from = adj.nodeFrom.pos.getc(true),
17114
to = adj.nodeTo.pos.getc(true),
17115
r = adj.nodeFrom.scale;
17116
this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
17117
},
17118
'contains': function(adj, pos) {
17119
var from = adj.nodeFrom.pos.getc(true),
17120
to = adj.nodeTo.pos.getc(true),
17121
r = adj.nodeFrom.scale;
17122
this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17123
}
17124
},
17125
'arrow': {
17126
'render': function(adj, canvas) {
17127
var from = adj.nodeFrom.pos.getc(true),
17128
to = adj.nodeTo.pos.getc(true),
17129
r = adj.nodeFrom.scale,
17130
dim = adj.getData('dim'),
17131
direction = adj.data.$direction,
17132
inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
17133
this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
17134
},
17135
'contains': function(adj, pos) {
17136
var from = adj.nodeFrom.pos.getc(true),
17137
to = adj.nodeTo.pos.getc(true),
17138
r = adj.nodeFrom.scale;
17139
this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17140
}
17141
},
17142
'hyperline': {
17143
'render': function(adj, canvas) {
17144
var from = adj.nodeFrom.pos.getc(),
17145
to = adj.nodeTo.pos.getc(),
17146
dim = this.viz.getRadius();
17147
this.edgeHelper.hyperline.render(from, to, dim, canvas);
17148
},
17149
'contains': $.lambda(false)
17150
}
17151
});
17152
17153
})($jit.Hypertree);
17154
})();
17155
17156