html export : not ready
[iramuteq] / webexport / js / sigma.concat.js
1 // Define packages:
2 var sigma = {};
3 sigma.tools = {};
4 sigma.classes = {};
5 sigma.instances = {};
6
7 // Adding Array helpers, if not present yet:
8 (function() {
9   if (!Array.prototype.some) {
10     Array.prototype.some = function(fun /*, thisp*/) {
11       var len = this.length;
12       if (typeof fun != 'function') {
13         throw new TypeError();
14       }
15
16       var thisp = arguments[1];
17       for (var i = 0; i < len; i++) {
18         if (i in this &&
19             fun.call(thisp, this[i], i, this)) {
20           return true;
21         }
22       }
23
24       return false;
25     };
26   }
27
28   if (!Array.prototype.forEach) {
29     Array.prototype.forEach = function(fun /*, thisp*/) {
30       var len = this.length;
31       if (typeof fun != 'function') {
32         throw new TypeError();
33       }
34
35       var thisp = arguments[1];
36       for (var i = 0; i < len; i++) {
37         if (i in this) {
38           fun.call(thisp, this[i], i, this);
39         }
40       }
41     };
42   }
43
44   if (!Array.prototype.map) {
45     Array.prototype.map = function(fun /*, thisp*/) {
46       var len = this.length;
47       if (typeof fun != 'function') {
48         throw new TypeError();
49       }
50
51       var res = new Array(len);
52       var thisp = arguments[1];
53       for (var i = 0; i < len; i++) {
54         if (i in this) {
55           res[i] = fun.call(thisp, this[i], i, this);
56         }
57       }
58
59       return res;
60     };
61   }
62
63   if (!Array.prototype.filter) {
64     Array.prototype.filter = function(fun /*, thisp*/) {
65       var len = this.length;
66       if (typeof fun != 'function')
67         throw new TypeError();
68
69       var res = new Array();
70       var thisp = arguments[1];
71       for (var i = 0; i < len; i++) {
72         if (i in this) {
73           var val = this[i]; // in case fun mutates this
74           if (fun.call(thisp, val, i, this)) {
75             res.push(val);
76           }
77         }
78       }
79
80       return res;
81     };
82   }
83
84   if (!Object.keys) {
85     Object.keys = (function() {
86       var hasOwnProperty = Object.prototype.hasOwnProperty,
87           hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
88           dontEnums = [
89             'toString',
90             'toLocaleString',
91             'valueOf',
92             'hasOwnProperty',
93             'isPrototypeOf',
94             'propertyIsEnumerable',
95             'constructor'
96           ],
97           dontEnumsLength = dontEnums.length;
98
99       return function(obj) {
100         if (typeof obj !== 'object' &&
101             typeof obj !== 'function' ||
102             obj === null
103         ) {
104           throw new TypeError('Object.keys called on non-object');
105         }
106
107         var result = [];
108
109         for (var prop in obj) {
110           if (hasOwnProperty.call(obj, prop)) result.push(prop);
111         }
112
113         if (hasDontEnumBug) {
114           for (var i = 0; i < dontEnumsLength; i++) {
115             if (hasOwnProperty.call(obj, dontEnums[i])) {
116               result.push(dontEnums[i]);
117             }
118           }
119         }
120         return result;
121       }
122     })();
123   }
124 })();
125
126 /**
127  * sigma.js custom event dispatcher class.
128  * @constructor
129  * @this {sigma.classes.EventDispatcher}
130  */
131 sigma.classes.EventDispatcher = function() {
132   /**
133    * An object containing all the different handlers bound to one or many
134    * events, indexed by these events.
135    * @private
136    * @type {Object.<string,Object>}
137    */
138   var _h = {};
139
140   /**
141    * Represents "this", without the well-known scope issue.
142    * @private
143    * @type {sigma.classes.EventDispatcher}
144    */
145   var _self = this;
146
147   /**
148    * Will execute the handler the next (and only the next) time that the
149    * indicated event (or the indicated events) will be triggered.
150    * @param  {string} events            The name of the event (or the events
151    *                                    separated by spaces).
152    * @param  {function(Object)} handler The handler to bind.
153    * @return {sigma.classes.EventDispatcher} Returns itself.
154    */
155   function one(events, handler) {
156     if (!handler || !events) {
157       return _self;
158     }
159
160     var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
161
162     eArray.forEach(function(event) {
163       if (!_h[event]) {
164         _h[event] = [];
165       }
166
167       _h[event].push({
168         'h': handler,
169         'one': true
170       });
171     });
172
173     return _self;
174   }
175
176   /**
177    * Will execute the handler everytime that the indicated event (or the
178    * indicated events) will be triggered.
179    * @param  {string} events            The name of the event (or the events
180    *                                    separated by spaces).
181    * @param  {function(Object)} handler The handler to bind.
182    * @return {sigma.classes.EventDispatcher} Returns itself.
183    */
184   function bind(events, handler) {
185     if (!handler || !events) {
186       return _self;
187     }
188
189     var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
190
191     eArray.forEach(function(event) {
192       if (!_h[event]) {
193         _h[event] = [];
194       }
195
196       _h[event].push({
197         'h': handler,
198         'one': false
199       });
200     });
201
202     return _self;
203   }
204
205   /**
206    * Unbinds the handler from a specified event (or specified events).
207    * @param  {?string} events            The name of the event (or the events
208    *                                     separated by spaces). If undefined,
209    *                                     then all handlers are unbound.
210    * @param  {?function(Object)} handler The handler to unbind. If undefined,
211    *                                     each handler bound to the event or the
212    *                                     events will be unbound.
213    * @return {sigma.classes.EventDispatcher} Returns itself.
214    */
215   function unbind(events, handler) {
216     if (!events) {
217       _h = {};
218     }
219
220     var eArray = typeof events == 'string' ? events.split(' ') : events;
221
222     if (handler) {
223       eArray.forEach(function(event) {
224         if (_h[event]) {
225           _h[event] = _h[event].filter(function(e) {
226             return e['h'] != handler;
227           });
228         }
229
230         if (_h[event] && _h[event].length == 0) {
231           delete _h[event];
232         }
233       });
234     }else {
235       eArray.forEach(function(event) {
236         delete _h[event];
237       });
238     }
239
240     return _self;
241   }
242
243   /**
244    * Executes each handler bound to the event
245    * @param  {string} type     The type of the event.
246    * @param  {?Object} content The content of the event (optional).
247    * @return {sigma.classes.EventDispatcher} Returns itself.
248    */
249   function dispatch(type, content) {
250     if (_h[type]) {
251       _h[type].forEach(function(e) {
252         e['h']({
253           'type': type,
254           'content': content,
255           'target': _self
256         });
257       });
258
259       _h[type] = _h[type].filter(function(e) {
260         return !e['one'];
261       });
262     }
263
264     return _self;
265   }
266
267   /* PUBLIC INTERFACE: */
268   this.one = one;
269   this.bind = bind;
270   this.unbind = unbind;
271   this.dispatch = dispatch;
272 };
273
274 /**
275  * A jQuery like properties management class. It works like jQuery .css()
276  * method: You can call it with juste one string to get the corresponding
277  * property, with a string and anything else to set the corresponding property,
278  * or directly with an object, and then each pair string / object (or any type)
279  * will be set in the properties.
280  * @constructor
281  * @this {sigma.classes.Cascade}
282  */
283 sigma.classes.Cascade = function() {
284   /**
285    * This instance properties.
286    * @protected
287    * @type {Object}
288    */
289   this.p = {};
290
291   /**
292    * The method to use to set/get any property of this instance.
293    * @param  {(string|Object)} a1 If it is a string and if a2 is undefined,
294    *                              then it will return the corresponding
295    *                              property.
296    *                              If it is a string and if a2 is set, then it
297    *                              will set a2 as the property corresponding to
298    *                              a1, and return this.
299    *                              If it is an object, then each pair string /
300    *                              object (or any other type) will be set as a
301    *                              property.
302    * @param  {*?} a2              The new property corresponding to a1 if a1 is
303    *                              a string.
304    * @return {(*|sigma.classes.Cascade)} Returns itself or the corresponding
305    *                                     property.
306    */
307   this.config = function(a1, a2) {
308     if (typeof a1 == 'string' && a2 == undefined) {
309       return this.p[a1];
310     } else {
311       var o = (typeof a1 == 'object' && a2 == undefined) ? a1 : {};
312       if (typeof a1 == 'string') {
313         o[a1] = a2;
314       }
315
316       for (var k in o) {
317         if (this.p[k] != undefined) {
318           this.p[k] = o[k];
319         }
320       }
321       return this;
322     }
323   };
324 };
325
326 (function() {
327 // Define local shortcut:
328 var id = 0;
329
330 // Define local package:
331 var local = {};
332 local.plugins = [];
333
334 sigma.init = function(dom) {
335   var inst = new Sigma(dom, (++id).toString());
336   sigma.instances[id] = new SigmaPublic(inst);
337   return sigma.instances[id];
338 };
339
340 /**
341  * The graph data model used in sigma.js.
342  * @constructor
343  * @extends sigma.classes.Cascade
344  * @extends sigma.classes.EventDispatcher
345  * @this {Graph}
346  */
347 function Graph() {
348   sigma.classes.Cascade.call(this);
349   sigma.classes.EventDispatcher.call(this);
350
351   /**
352    * Represents "this", without the well-known scope issue.
353    * @private
354    * @type {Graph}
355    */
356   var self = this;
357
358   /**
359    * The different parameters that determine how the nodes and edges should be
360    * translated and rescaled.
361    * @type {Object}
362    */
363   this.p = {
364     minNodeSize: 0,
365     maxNodeSize: 0,
366     minEdgeSize: 0,
367     maxEdgeSize: 0,
368     //   Scaling mode:
369     //   - 'inside' (default)
370     //   - 'outside'
371     scalingMode: 'inside',
372     nodesPowRatio: 0.5,
373     edgesPowRatio: 0
374   };
375
376   /**
377    * Contains the borders of the graph. These are useful to avoid the user to
378    * drag the graph out of the canvas.
379    * @type {Object}
380    */
381   this.borders = {};
382
383   /**
384    * Inserts a node in the graph.
385    * @param {string} id     The node's ID.
386    * @param {object} params An object containing the different parameters
387    *                        of the node.
388    * @return {Graph} Returns itself.
389    */
390   function addNode(id, params) {
391     if (self.nodesIndex[id]) {
392       throw new Error('Node "' + id + '" already exists.');
393     }
394
395     params = params || {};
396     var n = {
397       // Numbers :
398       'x': 0,
399       'y': 0,
400       'size': 1,
401       'degree': 0,
402       'inDegree': 0,
403       'outDegree': 0,
404       // Flags :
405       'fixed': false,
406       'active': false,
407       'hidden': false,
408       'forceLabel': false,
409       // Strings :
410       'label': id.toString(),
411       'id': id.toString(),
412       // Custom attributes :
413       'attr': {}
414     };
415
416     for (var k in params) {
417       switch (k) {
418         case 'id':
419           break;
420         case 'x':
421         case 'y':
422         case 'size':
423           n[k] = +params[k];
424           break;
425         case 'fixed':
426         case 'active':
427         case 'hidden':
428         case 'forceLabel':
429           n[k] = !!params[k];
430           break;
431         case 'color':
432         case 'label':
433           n[k] = params[k];
434           break;
435         default:
436           n['attr'][k] = params[k];
437       }
438     }
439
440     self.nodes.push(n);
441     self.nodesIndex[id.toString()] = n;
442
443     return self;
444   };
445
446   /**
447    * Generates the clone of a node, to make it easier to be exported.
448    * @private
449    * @param  {Object} node The node to clone.
450    * @return {Object} The clone of the node.
451    */
452   function cloneNode(node) {
453     return {
454       'x': node['x'],
455       'y': node['y'],
456       'size': node['size'],
457       'degree': node['degree'],
458       'inDegree': node['inDegree'],
459       'outDegree': node['outDegree'],
460       'displayX': node['displayX'],
461       'displayY': node['displayY'],
462       'displaySize': node['displaySize'],
463       'label': node['label'],
464       'id': node['id'],
465       'color': node['color'],
466       'fixed': node['fixed'],
467       'active': node['active'],
468       'hidden': node['hidden'],
469       'forceLabel': node['forceLabel'],
470       'attr': node['attr']
471     };
472   };
473
474   /**
475    * Checks the clone of a node, and inserts its values when possible. For
476    * example, it is possible to modify the size or the color of a node, but it
477    * is not possible to modify its display values or its id.
478    * @private
479    * @param  {Object} node The original node.
480    * @param  {Object} copy The clone.
481    * @return {Graph} Returns itself.
482    */
483   function checkNode(node, copy) {
484     for (var k in copy) {
485       switch (k) {
486         case 'id':
487         case 'attr':
488         case 'degree':
489         case 'inDegree':
490         case 'outDegree':
491         case 'displayX':
492         case 'displayY':
493         case 'displaySize':
494           break;
495         case 'x':
496         case 'y':
497         case 'size':
498           node[k] = +copy[k];
499           break;
500         case 'fixed':
501         case 'active':
502         case 'hidden':
503         case 'forceLabel':
504           node[k] = !!copy[k];
505           break;
506         case 'color':
507         case 'label':
508           node[k] = (copy[k] || '').toString();
509           break;
510         default:
511           node['attr'][k] = copy[k];
512       }
513     }
514
515     return self;
516   };
517
518   /**
519    * Deletes one or several nodes from the graph, and the related edges.
520    * @param  {(string|Array.<string>)} v A string ID, or an Array of several
521    *                                     IDs.
522    * @return {Graph} Returns itself.
523    */
524   function dropNode(v) {
525     var a = (v instanceof Array ? v : [v]) || [];
526     var nodesIdsToRemove = {};
527
528     // Create hash to make lookups faster
529     a.forEach(function(id) {
530       if (self.nodesIndex[id]) {
531         nodesIdsToRemove[id] = true;
532       } else {
533         sigma.log('Node "' + id + '" does not exist.');
534       }
535     });
536
537     var indexesToRemove = [];
538     self.nodes.forEach(function(n, i) {
539       if (n['id'] in nodesIdsToRemove) {
540         // Add to front, so we have a reverse-sorted list
541         indexesToRemove.unshift(i);
542         // No edges means we are done
543         if (n['degree'] == 0) {
544           delete nodesIdsToRemove[n['id']];
545         }
546       }
547     });
548
549     indexesToRemove.forEach(function(index) {
550       self.nodes.splice(index, 1);
551     });
552
553     self.edges = self.edges.filter(function(e) {
554       if (e['source']['id'] in nodesIdsToRemove) {
555         delete self.edgesIndex[e['id']];
556         e['target']['degree']--;
557         e['target']['inDegree']--;
558         return false;
559       }else if (e['target']['id'] in nodesIdsToRemove) {
560         delete self.edgesIndex[e['id']];
561         e['source']['degree']--;
562         e['source']['outDegree']--;
563         return false;
564       }
565       return true;
566     });
567
568     return self;
569   };
570
571   /**
572    * Inserts an edge in the graph.
573    * @param {string} id     The edge ID.
574    * @param {string} source The ID of the edge source.
575    * @param {string} target The ID of the edge target.
576    * @param {object} params An object containing the different parameters
577    *                        of the edge.
578    * @return {Graph} Returns itself.
579    */
580   function addEdge(id, source, target, params) {
581     if (self.edgesIndex[id]) {
582       throw new Error('Edge "' + id + '" already exists.');
583     }
584
585     if (!self.nodesIndex[source]) {
586       var s = 'Edge\'s source "' + source + '" does not exist yet.';
587       throw new Error(s);
588     }
589
590     if (!self.nodesIndex[target]) {
591       var s = 'Edge\'s target "' + target + '" does not exist yet.';
592       throw new Error(s);
593     }
594
595     params = params || {};
596     var e = {
597       'source': self.nodesIndex[source],
598       'target': self.nodesIndex[target],
599       'size': 1,
600       'weight': 1,
601       'displaySize': 0.5,
602       'label': id.toString(),
603       'id': id.toString(),
604       'hidden': false,
605       'attr': {}
606     };
607
608     e['source']['degree']++;
609     e['source']['outDegree']++;
610     e['target']['degree']++;
611     e['target']['inDegree']++;
612
613     for (var k in params) {
614       switch (k) {
615         case 'id':
616         case 'source':
617         case 'target':
618           break;
619         case 'hidden':
620           e[k] = !!params[k];
621           break;
622         case 'size':
623         case 'weight':
624           e[k] = +params[k];
625           break;
626         case 'color':
627           e[k] = params[k].toString();
628           break;
629         case 'type':
630           e[k] = params[k].toString();
631           break;
632         case 'label':
633           e[k] = params[k];
634           break;
635         default:
636           e['attr'][k] = params[k];
637       }
638     }
639
640     self.edges.push(e);
641     self.edgesIndex[id.toString()] = e;
642
643     return self;
644   };
645
646   /**
647    * Generates the clone of a edge, to make it easier to be exported.
648    * @private
649    * @param  {Object} edge The edge to clone.
650    * @return {Object} The clone of the edge.
651    */
652   function cloneEdge(edge) {
653     return {
654       'source': edge['source']['id'],
655       'target': edge['target']['id'],
656       'size': edge['size'],
657       'type': edge['type'],
658       'weight': edge['weight'],
659       'displaySize': edge['displaySize'],
660       'label': edge['label'],
661       'hidden': edge['hidden'],
662       'id': edge['id'],
663       'attr': edge['attr'],
664       'color': edge['color']
665     };
666   };
667
668   /**
669    * Checks the clone of an edge, and inserts its values when possible. For
670    * example, it is possible to modify the label or the type of an edge, but it
671    * is not possible to modify its display values or its id.
672    * @private
673    * @param  {Object} edge The original edge.
674    * @param  {Object} copy The clone.
675    * @return {Graph} Returns itself.
676    */
677   function checkEdge(edge, copy) {
678     for (var k in copy) {
679       switch (k) {
680         case 'id':
681         case 'displaySize':
682           break;
683         case 'weight':
684         case 'size':
685           edge[k] = +copy[k];
686           break;
687         case 'source':
688         case 'target':
689           edge[k] = self.nodesIndex[k] || edge[k];
690           break;
691         case 'hidden':
692           edge[k] = !!copy[k];
693           break;
694         case 'color':
695         case 'label':
696         case 'type':
697           edge[k] = (copy[k] || '').toString();
698           break;
699         default:
700           edge['attr'][k] = copy[k];
701       }
702     }
703
704     return self;
705   };
706
707   /**
708    * Deletes one or several edges from the graph.
709    * @param  {(string|Array.<string>)} v A string ID, or an Array of several
710    *                                     IDs.
711    * @return {Graph} Returns itself.
712    */
713   function dropEdge(v) {
714     var a = (v instanceof Array ? v : [v]) || [];
715
716     a.forEach(function(id) {
717       if (self.edgesIndex[id]) {
718         self.edgesIndex[id]['source']['degree']--;
719         self.edgesIndex[id]['source']['outDegree']--;
720         self.edgesIndex[id]['target']['degree']--;
721         self.edgesIndex[id]['target']['inDegree']--;
722
723         var index = null;
724         self.edges.some(function(n, i) {
725           if (n['id'] == id) {
726             index = i;
727             return true;
728           }
729           return false;
730         });
731
732         index != null && self.edges.splice(index, 1);
733         delete self.edgesIndex[id];
734       }else {
735         sigma.log('Edge "' + id + '" does not exist.');
736       }
737     });
738
739     return self;
740   };
741
742   /**
743    * Deletes every nodes and edges from the graph.
744    * @return {Graph} Returns itself.
745    */
746   function empty() {
747     self.nodes = [];
748     self.nodesIndex = {};
749     self.edges = [];
750     self.edgesIndex = {};
751
752     return self;
753   };
754
755   /**
756    * Computes the display x, y and size of each node, relatively to the
757    * original values and the borders determined in the parameters, such as
758    * each node is in the described area.
759    * @param  {number} w           The area width (actually the width of the DOM
760    *                              root).
761    * @param  {number} h           The area height (actually the height of the
762    *                              DOM root).
763    * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
764    * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
765    * @return {Graph} Returns itself.
766    */
767   function rescale(w, h, parseNodes, parseEdges) {
768     var weightMax = 0, sizeMax = 0;
769
770     parseNodes && self.nodes.forEach(function(node) {
771       sizeMax = Math.max(node['size'], sizeMax);
772     });
773
774     parseEdges && self.edges.forEach(function(edge) {
775       weightMax = Math.max(edge['size'], weightMax);
776     });
777
778     sizeMax = sizeMax || 1;
779     weightMax = weightMax || 1;
780
781     // Recenter the nodes:
782     var xMin, xMax, yMin, yMax;
783     parseNodes && self.nodes.forEach(function(node) {
784       xMax = Math.max(node['x'], xMax || node['x']);
785       xMin = Math.min(node['x'], xMin || node['x']);
786       yMax = Math.max(node['y'], yMax || node['y']);
787       yMin = Math.min(node['y'], yMin || node['y']);
788     });
789
790     // First, we compute the scaling ratio, without considering the sizes
791     // of the nodes : Each node will have its center in the canvas, but might
792     // be partially out of it.
793     var scale = self.p.scalingMode == 'outside' ?
794                 Math.max(w / Math.max(xMax - xMin, 1),
795                          h / Math.max(yMax - yMin, 1)) :
796                 Math.min(w / Math.max(xMax - xMin, 1),
797                          h / Math.max(yMax - yMin, 1));
798
799     // Then, we correct that scaling ratio considering a margin, which is
800     // basically the size of the biggest node.
801     // This has to be done as a correction since to compare the size of the
802     // biggest node to the X and Y values, we have to first get an
803     // approximation of the scaling ratio.
804     var margin = (self.p.maxNodeSize || sizeMax) / scale;
805     xMax += margin;
806     xMin -= margin;
807     yMax += margin;
808     yMin -= margin;
809
810     scale = self.p.scalingMode == 'outside' ?
811             Math.max(w / Math.max(xMax - xMin, 1),
812                      h / Math.max(yMax - yMin, 1)) :
813             Math.min(w / Math.max(xMax - xMin, 1),
814                      h / Math.max(yMax - yMin, 1));
815
816     // Size homothetic parameters:
817     var a, b;
818     if (!self.p.maxNodeSize && !self.p.minNodeSize) {
819       a = 1;
820       b = 0;
821     }else if (self.p.maxNodeSize == self.p.minNodeSize) {
822       a = 0;
823       b = self.p.maxNodeSize;
824     }else {
825       a = (self.p.maxNodeSize - self.p.minNodeSize) / sizeMax;
826       b = self.p.minNodeSize;
827     }
828
829     var c, d;
830     if (!self.p.maxEdgeSize && !self.p.minEdgeSize) {
831       c = 1;
832       d = 0;
833     }else if (self.p.maxEdgeSize == self.p.minEdgeSize) {
834       c = 0;
835       d = self.p.minEdgeSize;
836     }else {
837       c = (self.p.maxEdgeSize - self.p.minEdgeSize) / weightMax;
838       d = self.p.minEdgeSize;
839     }
840
841     // Rescale the nodes:
842     parseNodes && self.nodes.forEach(function(node) {
843       node['displaySize'] = node['size'] * a + b;
844
845       if (!node['fixed']) {
846         node['displayX'] = (node['x'] - (xMax + xMin) / 2) * scale + w / 2;
847         node['displayY'] = (node['y'] - (yMax + yMin) / 2) * scale + h / 2;
848       }
849     });
850
851     parseEdges && self.edges.forEach(function(edge) {
852       edge['displaySize'] = edge['size'] * c + d;
853     });
854
855     return self;
856   };
857
858   /**
859    * Translates the display values of the nodes and edges relatively to the
860    * scene position and zoom ratio.
861    * @param  {number} sceneX      The x position of the scene.
862    * @param  {number} sceneY      The y position of the scene.
863    * @param  {number} ratio       The zoom ratio of the scene.
864    * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
865    * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
866    * @return {Graph} Returns itself.
867    */
868   function translate(sceneX, sceneY, ratio, parseNodes, parseEdges) {
869     var sizeRatio = Math.pow(ratio, self.p.nodesPowRatio);
870     parseNodes && self.nodes.forEach(function(node) {
871       if (!node['fixed']) {
872         node['displayX'] = node['displayX'] * ratio + sceneX;
873         node['displayY'] = node['displayY'] * ratio + sceneY;
874       }
875
876       node['displaySize'] = node['displaySize'] * sizeRatio;
877     });
878
879     sizeRatio = Math.pow(ratio, self.p.edgesPowRatio);
880     parseEdges && self.edges.forEach(function(edge) {
881       edge['displaySize'] = edge['displaySize'] * sizeRatio;
882     });
883
884     return self;
885   };
886
887   /**
888    * Determines the borders of the graph as it will be drawn. It is used to
889    * avoid the user to drag the graph out of the canvas.
890    */
891   function setBorders() {
892     self.borders = {};
893
894     self.nodes.forEach(function(node) {
895       self.borders.minX = Math.min(
896         self.borders.minX == undefined ?
897           node['displayX'] - node['displaySize'] :
898           self.borders.minX,
899         node['displayX'] - node['displaySize']
900       );
901
902       self.borders.maxX = Math.max(
903         self.borders.maxX == undefined ?
904           node['displayX'] + node['displaySize'] :
905           self.borders.maxX,
906         node['displayX'] + node['displaySize']
907       );
908
909       self.borders.minY = Math.min(
910         self.borders.minY == undefined ?
911           node['displayY'] - node['displaySize'] :
912           self.borders.minY,
913         node['displayY'] - node['displaySize']
914       );
915
916       self.borders.maxY = Math.max(
917         self.borders.maxY == undefined ?
918           node['displayY'] - node['displaySize'] :
919           self.borders.maxY,
920         node['displayY'] - node['displaySize']
921       );
922     });
923   }
924
925   /**
926    * Checks which nodes are under the (mX, mY) points, representing the mouse
927    * position.
928    * @param  {number} mX The mouse X position.
929    * @param  {number} mY The mouse Y position.
930    * @return {Graph} Returns itself.
931    */
932   function checkHover(mX, mY) {
933     var dX, dY, s, over = [], out = [];
934     self.nodes.forEach(function(node) {
935       if (node['hidden']) {
936         node['hover'] = false;
937         return;
938       }
939
940       dX = Math.abs(node['displayX'] - mX);
941       dY = Math.abs(node['displayY'] - mY);
942       s = node['displaySize'];
943
944       var oldH = node['hover'];
945       var newH = dX < s && dY < s && Math.sqrt(dX * dX + dY * dY) < s;
946
947       if (oldH && !newH) {
948         node['hover'] = false;
949         out.push(node.id);
950       } else if (newH && !oldH) {
951         node['hover'] = true;
952         over.push(node.id);
953       }
954     });
955
956     over.length && self.dispatch('overnodes', over);
957     out.length && self.dispatch('outnodes', out);
958
959     return self;
960   };
961
962   /**
963    * Applies a function to a clone of each node (or indicated nodes), and then
964    * tries to apply the modifications made on the clones to the original nodes.
965    * @param  {function(Object)} fun The function to execute.
966    * @param  {?Array.<string>} ids  An Array of node IDs (optional).
967    * @return {Graph} Returns itself.
968    */
969   function iterNodes(fun, ids) {
970     var a = ids ? ids.map(function(id) {
971       return self.nodesIndex[id];
972     }) : self.nodes;
973
974     var aCopies = a.map(cloneNode);
975     aCopies.forEach(fun);
976
977     a.forEach(function(n, i) {
978       checkNode(n, aCopies[i]);
979     });
980
981     return self;
982   };
983
984   /**
985    * Applies a function to a clone of each edge (or indicated edges), and then
986    * tries to apply the modifications made on the clones to the original edges.
987    * @param  {function(Object)} fun The function to execute.
988    * @param  {?Array.<string>} ids  An Array of edge IDs (optional).
989    * @return {Graph} Returns itself.
990    */
991   function iterEdges(fun, ids) {
992     var a = ids ? ids.map(function(id) {
993       return self.edgesIndex[id];
994     }) : self.edges;
995
996     var aCopies = a.map(cloneEdge);
997     aCopies.forEach(fun);
998
999     a.forEach(function(e, i) {
1000       checkEdge(e, aCopies[i]);
1001     });
1002
1003     return self;
1004   };
1005
1006   /**
1007    * Returns a specific node clone or an array of specified node clones.
1008    * @param  {(string|Array.<string>)} ids The ID or an array of node IDs.
1009    * @return {(Object|Array.<Object>)} The clone or the array of clones.
1010    */
1011   function getNodes(ids) {
1012     var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
1013       return cloneNode(self.nodesIndex[id]);
1014     });
1015
1016     return (ids instanceof Array ? a : a[0]);
1017   };
1018
1019   /**
1020    * Returns a specific edge clone or an array of specified edge clones.
1021    * @param  {(string|Array.<string>)} ids The ID or an array of edge IDs.
1022    * @return {(Object|Array.<Object>)} The clone or the array of clones.
1023    */
1024   function getEdges(ids) {
1025     var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
1026       return cloneEdge(self.edgesIndex[id]);
1027     });
1028
1029     return (ids instanceof Array ? a : a[0]);
1030   };
1031
1032   empty();
1033
1034   this.addNode = addNode;
1035   this.addEdge = addEdge;
1036   this.dropNode = dropNode;
1037   this.dropEdge = dropEdge;
1038
1039   this.iterEdges = iterEdges;
1040   this.iterNodes = iterNodes;
1041
1042   this.getEdges = getEdges;
1043   this.getNodes = getNodes;
1044
1045   this.empty = empty;
1046   this.rescale = rescale;
1047   this.translate = translate;
1048   this.setBorders = setBorders;
1049   this.checkHover = checkHover;
1050 }
1051
1052 /**
1053  * Sigma is the main class. It represents the core of any instance id sigma.js.
1054  * It is private and can be initialized only from inside sigma.js. To see its
1055  * public interface, see {@link SigmaPublic}.
1056  * It owns its own {@link Graph}, {@link MouseCaptor}, {@link Plotter}
1057  * and {@link Monitor}.
1058  * @constructor
1059  * @extends sigma.classes.Cascade
1060  * @extends sigma.classes.EventDispatcher
1061  * @param {element} root The DOM root of this instance (a div, for example).
1062  * @param {string} id    The ID of this instance.
1063  * @this {Sigma}
1064  */
1065 function Sigma(root, id) {
1066   sigma.classes.Cascade.call(this);
1067   sigma.classes.EventDispatcher.call(this);
1068
1069   /**
1070    * Represents "this", without the well-known scope issue.
1071    * @private
1072    * @type {Sigma}
1073    */
1074   var self = this;
1075
1076   /**
1077    * The ID of the instance.
1078    * @type {string}
1079    */
1080   this.id = id.toString();
1081
1082   /**
1083    * The different parameters that define how this instance should work.
1084    * @see sigma.classes.Cascade
1085    * @type {Object}
1086    */
1087   this.p = {
1088     auto: true,
1089     drawNodes: 2,
1090     drawEdges: 1,
1091     drawLabels: 2,
1092     lastNodes: 2,
1093     lastEdges: 0,
1094     lastLabels: 2,
1095     drawHoverNodes: true,
1096     drawActiveNodes: true
1097   };
1098
1099   /**
1100    * The root DOM element of this instance, containing every other elements.
1101    * @type {element}
1102    */
1103   this.domRoot = root;
1104
1105   /**
1106    * The width of this instance - initially, the root's width.
1107    * @type {number}
1108    */
1109   this.width = this.domRoot.offsetWidth;
1110
1111   /**
1112    * The height of this instance - initially, the root's height.
1113    * @type {number}
1114    */
1115   this.height = this.domRoot.offsetHeight;
1116
1117   /**
1118    * The graph of this instance - initiallyempty.
1119    * @type {Graph}
1120    */
1121   this.graph = new Graph();
1122
1123   /**
1124    * An object referencing every DOM elements used by this instance.
1125    * @type {Object}
1126    */
1127   this.domElements = {};
1128
1129   initDOM('edges', 'canvas');
1130   initDOM('nodes', 'canvas');
1131   initDOM('labels', 'canvas');
1132   initDOM('hover', 'canvas');
1133   initDOM('monitor', 'div');
1134   initDOM('mouse', 'canvas');
1135
1136   /**
1137    * The class dedicated to manage the drawing process of the graph of the
1138    * different canvas.
1139    * @type {Plotter}
1140    */
1141   this.plotter = new Plotter(
1142     this.domElements.nodes.getContext('2d'),
1143     this.domElements.edges.getContext('2d'),
1144     this.domElements.labels.getContext('2d'),
1145     this.domElements.hover.getContext('2d'),
1146     this.graph,
1147     this.width,
1148     this.height
1149   );
1150
1151   /**
1152    * The class dedicated to monitor different probes about the running
1153    * processes or the data, such as the number of nodes or edges, or how
1154    * many times the graph is drawn per second.
1155    * @type {Monitor}
1156    */
1157   this.monitor = new Monitor(
1158     this,
1159     this.domElements.monitor
1160   );
1161
1162   /**
1163    * The class dedicated to manage the different mouse events.
1164    * @type {MouseCaptor}
1165    */
1166   this.mousecaptor = new MouseCaptor(
1167     this.domElements.mouse,
1168     this.id
1169   );
1170
1171   // Interaction listeners:
1172   this.mousecaptor.bind('drag interpolate', function(e) {
1173     self.draw(
1174       self.p.auto ? 2 : self.p.drawNodes,
1175       self.p.auto ? 0 : self.p.drawEdges,
1176       self.p.auto ? 2 : self.p.drawLabels,
1177       true
1178     );
1179   }).bind('stopdrag stopinterpolate', function(e) {
1180     self.draw(
1181       self.p.auto ? 2 : self.p.drawNodes,
1182       self.p.auto ? 1 : self.p.drawEdges,
1183       self.p.auto ? 2 : self.p.drawLabels,
1184       true
1185     );
1186   }).bind('mousedown mouseup', function(e) {
1187     var targeted = self.graph.nodes.filter(function(n) {
1188       return !!n['hover'];
1189     }).map(function(n) {
1190       return n.id;
1191     });
1192
1193     self.dispatch(
1194       e['type'] == 'mousedown' ?
1195         'downgraph' :
1196         'upgraph'
1197     );
1198
1199     if (targeted.length) {
1200       self.dispatch(
1201         e['type'] == 'mousedown' ?
1202           'downnodes' :
1203           'upnodes',
1204         targeted
1205       );
1206     }
1207   }).bind('move', function() {
1208     self.domElements.hover.getContext('2d').clearRect(
1209       0,
1210       0,
1211       self.domElements.hover.width,
1212       self.domElements.hover.height
1213     );
1214
1215     drawHover();
1216     drawActive();
1217   });
1218
1219   sigma.chronos.bind('startgenerators', function() {
1220     if (sigma.chronos.getGeneratorsIDs().some(function(id) {
1221       return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
1222     })) {
1223       self.draw(
1224         self.p.auto ? 2 : self.p.drawNodes,
1225         self.p.auto ? 0 : self.p.drawEdges,
1226         self.p.auto ? 2 : self.p.drawLabels
1227       );
1228     }
1229   }).bind('stopgenerators', function() {
1230     self.draw();
1231   });
1232
1233   /**
1234    * Resizes the element, and redraws the graph with the last settings.
1235    * @param  {?number} w The new width (if undefined, it will use the root
1236    *                     width).
1237    * @param  {?number} h The new height (if undefined, it will use the root
1238    *                     height).
1239    * @return {Sigma} Returns itself.
1240    */
1241   function resize(w, h) {
1242     var oldW = self.width, oldH = self.height;
1243
1244     if (w != undefined && h != undefined) {
1245       self.width = w;
1246       self.height = h;
1247     }else {
1248       self.width = self.domRoot.offsetWidth;
1249       self.height = self.domRoot.offsetHeight;
1250     }
1251
1252     if (oldW != self.width || oldH != self.height) {
1253       for (var k in self.domElements) {
1254         self.domElements[k].setAttribute('width', self.width + 'px');
1255         self.domElements[k].setAttribute('height', self.height + 'px');
1256       }
1257
1258       self.plotter.resize(self.width, self.height);
1259
1260       self.draw(
1261         self.p.lastNodes,
1262         self.p.lastEdges,
1263         self.p.lastLabels,
1264         true
1265       );
1266     }
1267     return self;
1268   };
1269
1270   /**
1271    * Kills every drawing task currently running. Basically, it stops this
1272    * instance's drawing process.
1273    * @return {Sigma} Returns itself.
1274    */
1275   function clearSchedule() {
1276     sigma.chronos.removeTask(
1277       'node_' + self.id, 2
1278     ).removeTask(
1279       'edge_' + self.id, 2
1280     ).removeTask(
1281       'label_' + self.id, 2
1282     ).stopTasks();
1283     return self;
1284   };
1285
1286   /**
1287    * Initialize a DOM element, that will be stores by this instance, to make
1288    * automatic these elements resizing.
1289    * @private
1290    * @param  {string} id   The element's ID.
1291    * @param  {string} type The element's nodeName (Example : canvas, div, ...).
1292    * @return {Sigma} Returns itself.
1293    */
1294   function initDOM(id, type) {
1295     self.domElements[id] = document.createElement(type);
1296     self.domElements[id].style.position = 'absolute';
1297     self.domElements[id].setAttribute('id', 'sigma_' + id + '_' + self.id);
1298     self.domElements[id].setAttribute('class', 'sigma_' + id + '_' + type);
1299     self.domElements[id].setAttribute('width', self.width + 'px');
1300     self.domElements[id].setAttribute('height', self.height + 'px');
1301
1302     self.domRoot.appendChild(self.domElements[id]);
1303     return self;
1304   };
1305
1306   /**
1307    * Starts the graph drawing process. The three first parameters indicate
1308    * how the different layers have to be drawn:
1309    * . -1: The layer is not drawn, but it is not erased.
1310    * . 0:  The layer is not drawn.
1311    * . 1:  The layer is drawn progressively.
1312    * . 2:  The layer is drawn directly.
1313    * @param  {?number} nodes  Determines if and how the nodes must be drawn.
1314    * @param  {?number} edges  Determines if and how the edges must be drawn.
1315    * @param  {?number} labels Determines if and how the labels must be drawn.
1316    * @param  {?boolean} safe  If true, nothing will happen if any generator
1317    *                          affiliated to this instance is currently running
1318    *                          (an iterative layout, for example).
1319    * @return {Sigma} Returns itself.
1320    */
1321   function draw(nodes, edges, labels, safe) {
1322     if (safe && sigma.chronos.getGeneratorsIDs().some(function(id) {
1323       return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
1324     })) {
1325       return self;
1326     }
1327
1328     var n = (nodes == undefined) ? self.p.drawNodes : nodes;
1329     var e = (edges == undefined) ? self.p.drawEdges : edges;
1330     var l = (labels == undefined) ? self.p.drawLabels : labels;
1331
1332     var params = {
1333       nodes: n,
1334       edges: e,
1335       labels: l
1336     };
1337
1338     self.p.lastNodes = n;
1339     self.p.lastEdges = e;
1340     self.p.lastLabels = l;
1341
1342     // Remove tasks:
1343     clearSchedule();
1344
1345     // Rescale graph:
1346     self.graph.rescale(
1347       self.width,
1348       self.height,
1349       n > 0,
1350       e > 0
1351     ).setBorders();
1352
1353     self.mousecaptor.checkBorders(
1354       self.graph.borders,
1355       self.width,
1356       self.height
1357     );
1358
1359     self.graph.translate(
1360       self.mousecaptor.stageX,
1361       self.mousecaptor.stageY,
1362       self.mousecaptor.ratio,
1363       n > 0,
1364       e > 0
1365     );
1366
1367     self.dispatch(
1368       'graphscaled'
1369     );
1370
1371     // Clear scene:
1372     for (var k in self.domElements) {
1373       if (
1374         self.domElements[k].nodeName.toLowerCase() == 'canvas' &&
1375         (params[k] == undefined || params[k] >= 0)
1376       ) {
1377         self.domElements[k].getContext('2d').clearRect(
1378           0,
1379           0,
1380           self.domElements[k].width,
1381           self.domElements[k].height
1382         );
1383       }
1384     }
1385
1386     self.plotter.currentEdgeIndex = 0;
1387     self.plotter.currentNodeIndex = 0;
1388     self.plotter.currentLabelIndex = 0;
1389
1390     var previous = null;
1391     var start = false;
1392
1393     if (n) {
1394       if (n > 1) {
1395         while (self.plotter.task_drawNode()) {}
1396       }else {
1397         sigma.chronos.addTask(
1398           self.plotter.task_drawNode,
1399           'node_' + self.id,
1400           false
1401         );
1402
1403         start = true;
1404         previous = 'node_' + self.id;
1405       }
1406     }
1407
1408     if (l) {
1409       if (l > 1) {
1410         while (self.plotter.task_drawLabel()) {}
1411       } else {
1412         if (previous) {
1413           sigma.chronos.queueTask(
1414             self.plotter.task_drawLabel,
1415             'label_' + self.id,
1416             previous
1417           );
1418         } else {
1419           sigma.chronos.addTask(
1420             self.plotter.task_drawLabel,
1421             'label_' + self.id,
1422             false
1423           );
1424         }
1425
1426         start = true;
1427         previous = 'label_' + self.id;
1428       }
1429     }
1430
1431     if (e) {
1432       if (e > 1) {
1433         while (self.plotter.task_drawEdge()) {}
1434       }else {
1435         if (previous) {
1436           sigma.chronos.queueTask(
1437             self.plotter.task_drawEdge,
1438             'edge_' + self.id,
1439             previous
1440           );
1441         }else {
1442           sigma.chronos.addTask(
1443             self.plotter.task_drawEdge,
1444             'edge_' + self.id,
1445             false
1446           );
1447         }
1448
1449         start = true;
1450         previous = 'edge_' + self.id;
1451       }
1452     }
1453
1454     self.dispatch(
1455       'draw'
1456     );
1457
1458     self.refresh();
1459
1460     start && sigma.chronos.runTasks();
1461     return self;
1462   };
1463
1464   /**
1465    * Draws the hover and active nodes labels.
1466    * @return {Sigma} Returns itself.
1467    */
1468   function refresh() {
1469     self.domElements.hover.getContext('2d').clearRect(
1470       0,
1471       0,
1472       self.domElements.hover.width,
1473       self.domElements.hover.height
1474     );
1475
1476     drawHover();
1477     drawActive();
1478
1479     return self;
1480   }
1481
1482   /**
1483    * Draws the hover nodes labels. This method is applied directly, and does
1484    * not use the pseudo-asynchronous tasks process.
1485    * @return {Sigma} Returns itself.
1486    */
1487   function drawHover() {
1488     if (self.p.drawHoverNodes) {
1489       self.graph.checkHover(
1490         self.mousecaptor.mouseX,
1491         self.mousecaptor.mouseY
1492       );
1493
1494       self.graph.nodes.forEach(function(node) {
1495         if (node.hover && !node.active) {
1496           self.plotter.drawHoverNode(node);
1497         }
1498       });
1499     }
1500
1501     return self;
1502   }
1503
1504   /**
1505    * Draws the active nodes labels. This method is applied directly, and does
1506    * not use the pseudo-asynchronous tasks process.
1507    * @return {Sigma} Returns itself.
1508    */
1509   function drawActive() {
1510     if (self.p.drawActiveNodes) {
1511       self.graph.nodes.forEach(function(node) {
1512         if (node.active) {
1513           self.plotter.drawActiveNode(node);
1514         }
1515       });
1516     }
1517
1518     return self;
1519   }
1520
1521   // Apply plugins:
1522   for (var i = 0; i < local.plugins.length; i++) {
1523     local.plugins[i](this);
1524   }
1525
1526   this.draw = draw;
1527   this.resize = resize;
1528   this.refresh = refresh;
1529   this.drawHover = drawHover;
1530   this.drawActive = drawActive;
1531   this.clearSchedule = clearSchedule;
1532
1533   window.addEventListener('resize', function() {
1534     self.resize();
1535   });
1536 }
1537
1538 function SigmaPublic(sigmaInstance) {
1539   var s = sigmaInstance;
1540   var self = this;
1541   sigma.classes.EventDispatcher.call(this);
1542
1543   this._core = sigmaInstance;
1544
1545   this.kill = function() {
1546     // TODO
1547   };
1548
1549   this.getID = function() {
1550     return s.id;
1551   };
1552
1553   // Config:
1554   this.configProperties = function(a1, a2) {
1555     var res = s.config(a1, a2);
1556     return res == s ? self : res;
1557   };
1558
1559   this.drawingProperties = function(a1, a2) {
1560     var res = s.plotter.config(a1, a2);
1561     return res == s.plotter ? self : res;
1562   };
1563
1564   this.mouseProperties = function(a1, a2) {
1565     var res = s.mousecaptor.config(a1, a2);
1566     return res == s.mousecaptor ? self : res;
1567   };
1568
1569   this.graphProperties = function(a1, a2) {
1570     var res = s.graph.config(a1, a2);
1571     return res == s.graph ? self : res;
1572   };
1573
1574   this.getMouse = function() {
1575     return {
1576       mouseX: s.mousecaptor.mouseX,
1577       mouseY: s.mousecaptor.mouseY,
1578       down: s.mousecaptor.isMouseDown
1579     };
1580   };
1581
1582   // Actions:
1583   this.position = function(stageX, stageY, ratio) {
1584     if (arguments.length == 0) {
1585       return {
1586         stageX: s.mousecaptor.stageX,
1587         stageY: s.mousecaptor.stageY,
1588         ratio: s.mousecaptor.ratio
1589       };
1590     }else {
1591       s.mousecaptor.stageX = stageX != undefined ?
1592         stageX :
1593         s.mousecaptor.stageX;
1594       s.mousecaptor.stageY = stageY != undefined ?
1595         stageY :
1596         s.mousecaptor.stageY;
1597       s.mousecaptor.ratio = ratio != undefined ?
1598         ratio :
1599         s.mousecaptor.ratio;
1600
1601       return self;
1602     }
1603   };
1604
1605   this.goTo = function(stageX, stageY, ratio) {
1606     s.mousecaptor.interpolate(stageX, stageY, ratio);
1607     return self;
1608   };
1609
1610   this.zoomTo = function(x, y, ratio) {
1611     ratio = Math.min(
1612               Math.max(s.mousecaptor.config('minRatio'), ratio),
1613               s.mousecaptor.config('maxRatio')
1614             );
1615     if (ratio == s.mousecaptor.ratio) {
1616       s.mousecaptor.interpolate(
1617         x - s.width / 2 + s.mousecaptor.stageX,
1618         y - s.height / 2 + s.mousecaptor.stageY
1619       );
1620     }else {
1621       s.mousecaptor.interpolate(
1622         (ratio * x - s.mousecaptor.ratio * s.width/2) /
1623         (ratio - s.mousecaptor.ratio),
1624         (ratio * y - s.mousecaptor.ratio * s.height/2) /
1625         (ratio - s.mousecaptor.ratio),
1626         ratio
1627       );
1628     }
1629     return self;
1630   };
1631
1632   this.resize = function(w, h) {
1633     s.resize(w, h);
1634     return self;
1635   };
1636
1637   this.draw = function(nodes, edges, labels, safe) {
1638     s.draw(nodes, edges, labels, safe);
1639     return self;
1640   };
1641
1642   this.refresh = function() {
1643     s.refresh();
1644     return self;
1645   };
1646
1647   // Tasks methods:
1648   this.addGenerator = function(id, task, condition) {
1649     sigma.chronos.addGenerator(id + '_ext_' + s.id, task, condition);
1650     return self;
1651   };
1652
1653   this.removeGenerator = function(id) {
1654     sigma.chronos.removeGenerator(id + '_ext_' + s.id);
1655     return self;
1656   };
1657
1658   // Graph methods:
1659   this.addNode = function(id, params) {
1660     s.graph.addNode(id, params);
1661     return self;
1662   };
1663
1664   this.addEdge = function(id, source, target, params) {
1665     s.graph.addEdge(id, source, target, params);
1666     return self;
1667   }
1668
1669   this.dropNode = function(v) {
1670     s.graph.dropNode(v);
1671     return self;
1672   };
1673
1674   this.dropEdge = function(v) {
1675     s.graph.dropEdge(v);
1676     return self;
1677   };
1678
1679   this.pushGraph = function(object, safe) {
1680     object.nodes && object.nodes.forEach(function(node) {
1681       node['id'] && (!safe || !s.graph.nodesIndex[node['id']]) &&
1682                     self.addNode(node['id'], node);
1683     });
1684
1685     var isEdgeValid;
1686     object.edges && object.edges.forEach(function(edge) {
1687       validID = edge['source'] && edge['target'] && edge['id'];
1688       validID &&
1689         (!safe || !s.graph.edgesIndex[edge['id']]) &&
1690         self.addEdge(
1691           edge['id'],
1692           edge['source'],
1693           edge['target'],
1694           edge
1695         );
1696     });
1697
1698     return self;
1699   };
1700
1701   this.emptyGraph = function() {
1702     s.graph.empty();
1703     return self;
1704   };
1705
1706   this.getNodesCount = function() {
1707     return s.graph.nodes.length;
1708   };
1709
1710   this.getEdgesCount = function() {
1711     return s.graph.edges.length;
1712   };
1713
1714   this.iterNodes = function(fun, ids) {
1715     s.graph.iterNodes(fun, ids);
1716     return self;
1717   };
1718
1719   this.iterEdges = function(fun, ids) {
1720     s.graph.iterEdges(fun, ids);
1721     return self;
1722   };
1723
1724   this.getNodes = function(ids) {
1725     return s.graph.getNodes(ids);
1726   };
1727
1728   this.getEdges = function(ids) {
1729     return s.graph.getEdges(ids);
1730   };
1731
1732   // Monitoring
1733   this.activateMonitoring = function() {
1734     return s.monitor.activate();
1735   };
1736
1737   this.desactivateMonitoring = function() {
1738     return s.monitor.desactivate();
1739   };
1740
1741   // Events
1742   s.bind('downnodes upnodes downgraph upgraph', function(e) {
1743     self.dispatch(e.type, e.content);
1744   });
1745
1746   s.graph.bind('overnodes outnodes', function(e) {
1747     self.dispatch(e.type, e.content);
1748   });
1749 }
1750
1751 /**
1752  * This class listen to all the different mouse events, to normalize them and
1753  * dispatch action events instead (from "startinterpolate" to "isdragging",
1754  * etc).
1755  * @constructor
1756  * @extends sigma.classes.Cascade
1757  * @extends sigma.classes.EventDispatcher
1758  * @param {element} dom The DOM element to bind the handlers on.
1759  * @this {MouseCaptor}
1760  */
1761 function MouseCaptor(dom) {
1762   sigma.classes.Cascade.call(this);
1763   sigma.classes.EventDispatcher.call(this);
1764
1765   /**
1766    * Represents "this", without the well-known scope issue.
1767    * @private
1768    * @type {MouseCaptor}
1769    */
1770   var self = this;
1771
1772   /**
1773    * The DOM element to bind the handlers on.
1774    * @type {element}
1775    */
1776   var dom = dom;
1777
1778   /**
1779    * The different parameters that define how this instance should work.
1780    * @see sigma.classes.Cascade
1781    * @type {Object}
1782    */
1783   this.p = {
1784     minRatio: 1,
1785     maxRatio: 32,
1786     marginRatio: 1,
1787     zoomDelta: 0.1,
1788     dragDelta: 0.3,
1789     zoomMultiply: 2,
1790     directZooming: false,
1791     blockScroll: true,
1792     inertia: 1.1,
1793     mouseEnabled: true
1794   };
1795
1796   var oldMouseX = 0;
1797   var oldMouseY = 0;
1798   var startX = 0;
1799   var startY = 0;
1800
1801   var oldStageX = 0;
1802   var oldStageY = 0;
1803   var oldRatio = 1;
1804
1805   var targetRatio = 1;
1806   var targetStageX = 0;
1807   var targetStageY = 0;
1808
1809   var lastStageX = 0;
1810   var lastStageX2 = 0;
1811   var lastStageY = 0;
1812   var lastStageY2 = 0;
1813
1814   var progress = 0;
1815   var isZooming = false;
1816
1817   this.stageX = 0;
1818   this.stageY = 0;
1819   this.ratio = 1;
1820
1821   this.mouseX = 0;
1822   this.mouseY = 0;
1823
1824   this.isMouseDown = false;
1825
1826   /**
1827    * Extract the local X position from a mouse event.
1828    * @private
1829    * @param  {event} e A mouse event.
1830    * @return {number} The local X value of the mouse.
1831    */
1832   function getX(e) {
1833     return e.offsetX != undefined && e.offsetX ||
1834            e.layerX != undefined && e.layerX ||
1835            e.clientX != undefined && e.clientX;
1836   };
1837
1838   /**
1839    * Extract the local Y position from a mouse event.
1840    * @private
1841    * @param  {event} e A mouse event.
1842    * @return {number} The local Y value of the mouse.
1843    */
1844   function getY(e) {
1845     return e.offsetY != undefined && e.offsetY ||
1846            e.layerY != undefined && e.layerY ||
1847            e.clientY != undefined && e.clientY;
1848   };
1849
1850   /**
1851    * Extract the wheel delta from a mouse event.
1852    * @private
1853    * @param  {event} e A mouse event.
1854    * @return {number} The wheel delta of the mouse.
1855    */
1856   function getDelta(e) {
1857     return e.wheelDelta != undefined && e.wheelDelta ||
1858            e.detail != undefined && -e.detail;
1859   };
1860
1861   /**
1862    * The handler listening to the 'move' mouse event. It will set the mouseX
1863    * and mouseY values as the mouse position values, prevent the default event,
1864    * and dispatch a 'move' event.
1865    * @private
1866    * @param  {event} event A 'move' mouse event.
1867    */
1868   function moveHandler(event) {
1869     oldMouseX = self.mouseX;
1870     oldMouseY = self.mouseY;
1871
1872     self.mouseX = getX(event);
1873     self.mouseY = getY(event);
1874
1875     self.isMouseDown && drag(event);
1876     self.dispatch('move');
1877
1878     if (event.preventDefault) {
1879       event.preventDefault();
1880     } else {
1881       event.returnValue = false;
1882     }
1883   };
1884
1885   /**
1886    * The handler listening to the 'up' mouse event. It will set the isMouseDown
1887    * value as false, dispatch a 'mouseup' event, and trigger stopDrag().
1888    * @private
1889    * @param  {event} event A 'up' mouse event.
1890    */
1891   function upHandler(event) {
1892     if (self.p.mouseEnabled && self.isMouseDown) {
1893       self.isMouseDown = false;
1894       self.dispatch('mouseup');
1895       stopDrag();
1896
1897       if (event.preventDefault) {
1898         event.preventDefault();
1899       } else {
1900         event.returnValue = false;
1901       }
1902     }
1903   };
1904
1905   /**
1906    * The handler listening to the 'down' mouse event. It will set the
1907    * isMouseDown value as true, dispatch a 'mousedown' event, and trigger
1908    * startDrag().
1909    * @private
1910    * @param  {event} event A 'down' mouse event.
1911    */
1912   function downHandler(event) {
1913     if (self.p.mouseEnabled) {
1914       self.isMouseDown = true;
1915       oldMouseX = self.mouseX;
1916       oldMouseY = self.mouseY;
1917
1918       self.dispatch('mousedown');
1919
1920       startDrag();
1921
1922       if (event.preventDefault) {
1923         event.preventDefault();
1924       } else {
1925         event.returnValue = false;
1926       }
1927     }
1928   };
1929
1930   /**
1931    * The handler listening to the 'wheel' mouse event. It will trigger
1932    * {@link startInterpolate} with the event delta as parameter.
1933    * @private
1934    * @param  {event} event A 'wheel' mouse event.
1935    */
1936   function wheelHandler(event) {
1937     if (self.p.mouseEnabled) {
1938       startInterpolate(
1939         self.mouseX,
1940         self.mouseY,
1941         self.ratio * (getDelta(event) > 0 ?
1942           self.p.zoomMultiply :
1943           1 / self.p.zoomMultiply)
1944       );
1945
1946       if (self.p['blockScroll']) {
1947         if (event.preventDefault) {
1948           event.preventDefault();
1949         } else {
1950           event.returnValue = false;
1951         }
1952       }
1953     }
1954   };
1955
1956   /**
1957    * Will start computing the scene X and Y, until {@link stopDrag} is
1958    * triggered.
1959    */
1960   function startDrag() {
1961     oldStageX = self.stageX;
1962     oldStageY = self.stageY;
1963     startX = self.mouseX;
1964     startY = self.mouseY;
1965
1966     lastStageX = self.stageX;
1967     lastStageX2 = self.stageX;
1968     lastStageY = self.stageY;
1969     lastStageY2 = self.stageY;
1970
1971     self.dispatch('startdrag');
1972   };
1973
1974   /**
1975    * Stops computing the scene position.
1976    */
1977   function stopDrag() {
1978     if (oldStageX != self.stageX || oldStageY != self.stageY) {
1979       startInterpolate(
1980         self.stageX + self.p.inertia * (self.stageX - lastStageX2),
1981         self.stageY + self.p.inertia * (self.stageY - lastStageY2)
1982       );
1983     }
1984   };
1985
1986   /**
1987    * Computes the position of the scene, relatively to the mouse position, and
1988    * dispatches a "drag" event.
1989    */
1990   function drag() {
1991     var newStageX = self.mouseX - startX + oldStageX;
1992     var newStageY = self.mouseY - startY + oldStageY;
1993
1994     if (newStageX != self.stageX || newStageY != self.stageY) {
1995       lastStageX2 = lastStageX;
1996       lastStageY2 = lastStageY;
1997
1998       lastStageX = newStageX;
1999       lastStageY = newStageY;
2000
2001       self.stageX = newStageX;
2002       self.stageY = newStageY;
2003       self.dispatch('drag');
2004     }
2005   };
2006
2007   /**
2008    * Will start computing the scene zoom ratio, until {@link stopInterpolate} is
2009    * triggered.
2010    * @param {number} x     The new stage X.
2011    * @param {number} y     The new stage Y.
2012    * @param {number} ratio The new zoom ratio.
2013    */
2014   function startInterpolate(x, y, ratio) {
2015     if (self.isMouseDown) {
2016       return;
2017     }
2018
2019     window.clearInterval(self.interpolationID);
2020     isZooming = ratio != undefined;
2021
2022     oldStageX = self.stageX;
2023     targetStageX = x;
2024
2025     oldStageY = self.stageY;
2026     targetStageY = y;
2027
2028     oldRatio = self.ratio;
2029     targetRatio = ratio || self.ratio;
2030     targetRatio = Math.min(
2031       Math.max(targetRatio, self.p.minRatio),
2032       self.p.maxRatio
2033     );
2034
2035     progress =
2036       self.p.directZooming ?
2037       1 - (isZooming ? self.p.zoomDelta : self.p.dragDelta) :
2038       0;
2039
2040     if (
2041       self.ratio != targetRatio ||
2042       self.stageX != targetStageX ||
2043       self.stageY != targetStageY
2044     ) {
2045       interpolate();
2046       self.interpolationID = window.setInterval(interpolate, 50);
2047       self.dispatch('startinterpolate');
2048     }
2049   };
2050
2051   /**
2052    * Stops the move interpolation.
2053    */
2054   function stopInterpolate() {
2055     var oldRatio = self.ratio;
2056
2057     if (isZooming) {
2058       self.ratio = targetRatio;
2059       self.stageX = targetStageX +
2060                     (self.stageX - targetStageX) *
2061                     self.ratio /
2062                     oldRatio;
2063       self.stageY = targetStageY +
2064                     (self.stageY - targetStageY) *
2065                     self.ratio /
2066                     oldRatio;
2067     }else {
2068       self.stageX = targetStageX;
2069       self.stageY = targetStageY;
2070     }
2071
2072     self.dispatch('stopinterpolate');
2073   };
2074
2075   /**
2076    * Computes the interpolate ratio and the position of the scene, relatively
2077    * to the last mouse event delta received, and dispatches a "interpolate"
2078    * event.
2079    */
2080   function interpolate() {
2081     progress += (isZooming ? self.p.zoomDelta : self.p.dragDelta);
2082     progress = Math.min(progress, 1);
2083
2084     var k = sigma.easing.quadratic.easeout(progress);
2085     var oldRatio = self.ratio;
2086
2087     self.ratio = oldRatio * (1 - k) + targetRatio * k;
2088
2089     if (isZooming) {
2090       self.stageX = targetStageX +
2091                     (self.stageX - targetStageX) *
2092                     self.ratio /
2093                     oldRatio;
2094
2095       self.stageY = targetStageY +
2096                     (self.stageY - targetStageY) *
2097                     self.ratio /
2098                     oldRatio;
2099     } else {
2100       self.stageX = oldStageX * (1 - k) + targetStageX * k;
2101       self.stageY = oldStageY * (1 - k) + targetStageY * k;
2102     }
2103
2104     self.dispatch('interpolate');
2105     if (progress >= 1) {
2106       window.clearInterval(self.interpolationID);
2107       stopInterpolate();
2108     }
2109   };
2110
2111   /**
2112    * Checks that there is always a part of the graph that is displayed, to
2113    * avoid the user to drag the graph out of the stage.
2114    * @param  {Object} b      An object containing the borders of the graph.
2115    * @param  {number} width  The width of the stage.
2116    * @param  {number} height The height of the stage.
2117    * @return {MouseCaptor} Returns itself.
2118    */
2119   function checkBorders(b, width, height) {
2120     // TODO : Find the good formula
2121     /*if (!isNaN(b.minX) && !isNaN(b.maxX)) {
2122       self.stageX = Math.min(
2123         self.stageX = Math.max(
2124           self.stageX,
2125           (b.minX - width) * self.ratio +
2126             self.p.marginRatio*(b.maxX - b.minX)
2127         ),
2128         (b.maxX - width) * self.ratio +
2129           width -
2130           self.p.marginRatio*(b.maxX - b.minX)
2131       );
2132     }
2133
2134     if (!isNaN(b.minY) && !isNaN(b.maxY)) {
2135       self.stageY = Math.min(
2136         self.stageY = Math.max(
2137           self.stageY,
2138           (b.minY - height) * self.ratio +
2139             self.p.marginRatio*(b.maxY - b.minY)
2140         ),
2141         (b.maxY - height) * self.ratio +
2142           height -
2143           self.p.marginRatio*(b.maxY - b.minY)
2144       );
2145     }*/
2146
2147     return self;
2148   };
2149
2150   // ADD CALLBACKS
2151   dom.addEventListener('DOMMouseScroll', wheelHandler, true);
2152   dom.addEventListener('mousewheel', wheelHandler, true);
2153   dom.addEventListener('mousemove', moveHandler, true);
2154   dom.addEventListener('mousedown', downHandler, true);
2155   document.addEventListener('mouseup', upHandler, true);
2156
2157   this.checkBorders = checkBorders;
2158   this.interpolate = startInterpolate;
2159 }
2160
2161 /**
2162  * This class draws the graph on the different canvas DOM elements. It just
2163  * contains all the different methods to draw the graph, synchronously or
2164  * pseudo-asynchronously.
2165  * @constructor
2166  * @param {CanvasRenderingContext2D} nodesCtx  Context dedicated to draw nodes.
2167  * @param {CanvasRenderingContext2D} edgesCtx  Context dedicated to draw edges.
2168  * @param {CanvasRenderingContext2D} labelsCtx Context dedicated to draw
2169  *                                             labels.
2170  * @param {CanvasRenderingContext2D} hoverCtx  Context dedicated to draw hover
2171  *                                             nodes labels.
2172  * @param {Graph} graph                        A reference to the graph to
2173  *                                             draw.
2174  * @param {number} w                           The width of the DOM root
2175  *                                             element.
2176  * @param {number} h                           The width of the DOM root
2177  *                                             element.
2178  * @extends sigma.classes.Cascade
2179  * @this {Plotter}
2180  */
2181 function Plotter(nodesCtx, edgesCtx, labelsCtx, hoverCtx, graph, w, h) {
2182   sigma.classes.Cascade.call(this);
2183
2184   /**
2185    * Represents "this", without the well-known scope issue.
2186    * @private
2187    * @type {Plotter}
2188    */
2189   var self = this;
2190
2191   /**
2192    * The different parameters that define how this instance should work.
2193    * @see sigma.classes.Cascade
2194    * @type {Object}
2195    */
2196   this.p = {
2197     // -------
2198     // LABELS:
2199     // -------
2200     //   Label color:
2201     //   - 'node'
2202     //   - default (then defaultLabelColor
2203     //              will be used instead)
2204     labelColor: 'default',
2205     defaultLabelColor: '#000',
2206     //   Label hover background color:
2207     //   - 'node'
2208     //   - default (then defaultHoverLabelBGColor
2209     //              will be used instead)
2210     labelHoverBGColor: 'default',
2211     defaultHoverLabelBGColor: '#fff',
2212     //   Label hover shadow:
2213     labelHoverShadow: true,
2214     labelHoverShadowColor: '#000',
2215     //   Label hover color:
2216     //   - 'node'
2217     //   - default (then defaultLabelHoverColor
2218     //              will be used instead)
2219     labelHoverColor: 'default',
2220     defaultLabelHoverColor: '#000',
2221     //   Label active background color:
2222     //   - 'node'
2223     //   - default (then defaultActiveLabelBGColor
2224     //              will be used instead)
2225     labelActiveBGColor: 'default',
2226     defaultActiveLabelBGColor: '#fff',
2227     //   Label active shadow:
2228     labelActiveShadow: true,
2229     labelActiveShadowColor: '#000',
2230     //   Label active color:
2231     //   - 'node'
2232     //   - default (then defaultLabelActiveColor
2233     //              will be used instead)
2234     labelActiveColor: 'default',
2235     defaultLabelActiveColor: '#000',
2236     //   Label size:
2237     //   - 'fixed'
2238     //   - 'proportional'
2239     //   Label size:
2240     //   - 'fixed'
2241     //   - 'proportional'
2242     labelSize: 'fixed',
2243     defaultLabelSize: 12, // for fixed display only
2244     labelSizeRatio: 2,    // for proportional display only
2245     labelThreshold: 6,
2246     font: 'Arial',
2247     hoverFont: '',
2248     activeFont: '',
2249     fontStyle: '',
2250     hoverFontStyle: '',
2251     activeFontStyle: '',
2252     // ------
2253     // EDGES:
2254     // ------
2255     //   Edge color:
2256     //   - 'source'
2257     //   - 'target'
2258     //   - default (then defaultEdgeColor or edge['color']
2259     //              will be used instead)
2260     edgeColor: 'source',
2261     defaultEdgeColor: '#aaa',
2262     defaultEdgeType: 'line',
2263     // ------
2264     // NODES:
2265     // ------
2266     defaultNodeColor: '#aaa',
2267     // HOVER:
2268     //   Node hover color:
2269     //   - 'node'
2270     //   - default (then defaultNodeHoverColor
2271     //              will be used instead)
2272     nodeHoverColor: 'node',
2273     defaultNodeHoverColor: '#fff',
2274     // ACTIVE:
2275     //   Node active color:
2276     //   - 'node'
2277     //   - default (then defaultNodeActiveColor
2278     //              will be used instead)
2279     nodeActiveColor: 'node',
2280     defaultNodeActiveColor: '#fff',
2281     //   Node border color:
2282     //   - 'node'
2283     //   - default (then defaultNodeBorderColor
2284     //              will be used instead)
2285     borderSize: 0,
2286     nodeBorderColor: 'node',
2287     defaultNodeBorderColor: '#fff',
2288     // --------
2289     // PROCESS:
2290     // --------
2291     edgesSpeed: 200,
2292     nodesSpeed: 200,
2293     labelsSpeed: 200
2294   };
2295
2296   /**
2297    * The canvas context dedicated to draw the nodes.
2298    * @type {CanvasRenderingContext2D}
2299    */
2300   var nodesCtx = nodesCtx;
2301
2302   /**
2303    * The canvas context dedicated to draw the edges.
2304    * @type {CanvasRenderingContext2D}
2305    */
2306   var edgesCtx = edgesCtx;
2307
2308   /**
2309    * The canvas context dedicated to draw the labels.
2310    * @type {CanvasRenderingContext2D}
2311    */
2312   var labelsCtx = labelsCtx;
2313
2314   /**
2315    * The canvas context dedicated to draw the hover nodes.
2316    * @type {CanvasRenderingContext2D}
2317    */
2318   var hoverCtx = hoverCtx;
2319
2320   /**
2321    * A reference to the graph to draw.
2322    * @type {Graph}
2323    */
2324   var graph = graph;
2325
2326   /**
2327    * The width of the stage to draw on.
2328    * @type {number}
2329    */
2330   var width = w;
2331
2332   /**
2333    * The height of the stage to draw on.
2334    * @type {number}
2335    */
2336   var height = h;
2337
2338   /**
2339    * The index of the next edge to draw.
2340    * @type {number}
2341    */
2342   this.currentEdgeIndex = 0;
2343
2344   /**
2345    * The index of the next node to draw.
2346    * @type {number}
2347    */
2348   this.currentNodeIndex = 0;
2349
2350   /**
2351    * The index of the next label to draw.
2352    * @type {number}
2353    */
2354   this.currentLabelIndex = 0;
2355
2356   /**
2357    * An atomic function to drawn the N next edges, with N as edgesSpeed.
2358    * The counter is {@link this.currentEdgeIndex}.
2359    * This function has been designed to work with {@link sigma.chronos}, that
2360    * will insert frames at the middle of the calls, to make the edges drawing
2361    * process fluid for the user.
2362    * @see sigma.chronos
2363    * @return {boolean} Returns true if all the edges are drawn and false else.
2364    */
2365   function task_drawEdge() {
2366     var c = graph.edges.length;
2367     var s, t, i = 0;
2368
2369     while (i++< self.p.edgesSpeed && self.currentEdgeIndex < c) {
2370       e = graph.edges[self.currentEdgeIndex];
2371       s = e['source'];
2372       t = e['target'];
2373       if (e['hidden'] ||
2374           s['hidden'] ||
2375           t['hidden'] ||
2376           (!self.isOnScreen(s) && !self.isOnScreen(t))) {
2377         self.currentEdgeIndex++;
2378       }else {
2379         drawEdge(graph.edges[self.currentEdgeIndex++]);
2380       }
2381     }
2382
2383     return self.currentEdgeIndex < c;
2384   };
2385
2386   /**
2387    * An atomic function to drawn the N next nodes, with N as nodesSpeed.
2388    * The counter is {@link this.currentEdgeIndex}.
2389    * This function has been designed to work with {@link sigma.chronos}, that
2390    * will insert frames at the middle of the calls, to make the nodes drawing
2391    * process fluid for the user.
2392    * @see sigma.chronos
2393    * @return {boolean} Returns true if all the nodes are drawn and false else.
2394    */
2395   function task_drawNode() {
2396     var c = graph.nodes.length;
2397     var i = 0;
2398
2399     while (i++< self.p.nodesSpeed && self.currentNodeIndex < c) {
2400       if (!self.isOnScreen(graph.nodes[self.currentNodeIndex])) {
2401         self.currentNodeIndex++;
2402       }else {
2403         drawNode(graph.nodes[self.currentNodeIndex++]);
2404       }
2405     }
2406
2407     return self.currentNodeIndex < c;
2408   };
2409
2410   /**
2411    * An atomic function to drawn the N next labels, with N as labelsSpeed.
2412    * The counter is {@link this.currentEdgeIndex}.
2413    * This function has been designed to work with {@link sigma.chronos}, that
2414    * will insert frames at the middle of the calls, to make the labels drawing
2415    * process fluid for the user.
2416    * @see sigma.chronos
2417    * @return {boolean} Returns true if all the labels are drawn and false else.
2418    */
2419   function task_drawLabel() {
2420     var c = graph.nodes.length;
2421     var i = 0;
2422
2423     while (i++< self.p.labelsSpeed && self.currentLabelIndex < c) {
2424       if (!self.isOnScreen(graph.nodes[self.currentLabelIndex])) {
2425         self.currentLabelIndex++;
2426       }else {
2427         drawLabel(graph.nodes[self.currentLabelIndex++]);
2428       }
2429     }
2430
2431     return self.currentLabelIndex < c;
2432   };
2433
2434   /**
2435    * Draws one node to the corresponding canvas.
2436    * @param  {Object} node The node to draw.
2437    * @return {Plotter} Returns itself.
2438    */
2439   function drawNode(node) {
2440     var size = Math.round(node['displaySize'] * 10) / 10;
2441     var ctx = nodesCtx;
2442
2443     ctx.fillStyle = node['color'];
2444     ctx.beginPath();
2445     ctx.arc(node['displayX'],
2446             node['displayY'],
2447             size,
2448             0,
2449             Math.PI * 2,
2450             true);
2451
2452     ctx.closePath();
2453     ctx.fill();
2454
2455     node['hover'] && drawHoverNode(node);
2456     return self;
2457   };
2458
2459   /**
2460    * Draws one edge to the corresponding canvas.
2461    * @param  {Object} edge The edge to draw.
2462    * @return {Plotter} Returns itself.
2463    */
2464   function drawEdge(edge) {
2465     var x1 = edge['source']['displayX'];
2466     var y1 = edge['source']['displayY'];
2467     var x2 = edge['target']['displayX'];
2468     var y2 = edge['target']['displayY'];
2469     var color = edge['color'];
2470
2471     if (!color) {
2472       switch (self.p.edgeColor) {
2473         case 'source':
2474           color = edge['source']['color'] ||
2475                   self.p.defaultNodeColor;
2476           break;
2477         case 'target':
2478           color = edge['target']['color'] ||
2479                   self.p.defaultNodeColor;
2480           break;
2481         default:
2482           color = self.p.defaultEdgeColor;
2483           break;
2484       }
2485     }
2486
2487     var ctx = edgesCtx;
2488
2489     switch (edge['type'] || self.p.defaultEdgeType) {
2490       case 'curve':
2491         ctx.strokeStyle = color;
2492         ctx.lineWidth = edge['displaySize'] / 3;
2493         ctx.beginPath();
2494         ctx.moveTo(x1, y1);
2495         ctx.quadraticCurveTo((x1 + x2) / 2 + (y2 - y1) / 4,
2496                              (y1 + y2) / 2 + (x1 - x2) / 4,
2497                              x2,
2498                              y2);
2499         ctx.stroke();
2500         break;
2501       case 'line':
2502       default:
2503         ctx.strokeStyle = color;
2504         ctx.lineWidth = edge['displaySize'] / 3;
2505         ctx.beginPath();
2506         ctx.moveTo(x1, y1);
2507         ctx.lineTo(x2, y2);
2508
2509         ctx.stroke();
2510         break;
2511     }
2512
2513     return self;
2514   };
2515
2516   /**
2517    * Draws one label to the corresponding canvas.
2518    * @param  {Object} node The label to draw.
2519    * @return {Plotter} Returns itself.
2520    */
2521   function drawLabel(node) {
2522     var ctx = labelsCtx;
2523
2524     if (node['displaySize'] >= self.p.labelThreshold || node['forceLabel']) {
2525       var fontSize = self.p.labelSize == 'fixed' ?
2526                      self.p.defaultLabelSize :
2527                      self.p.labelSizeRatio * node['displaySize'];
2528
2529       ctx.font = self.p.fontStyle + fontSize + 'px ' + self.p.font;
2530
2531       ctx.fillStyle = self.p.labelColor == 'node' ?
2532                       (node['color'] || self.p.defaultNodeColor) :
2533                       self.p.defaultLabelColor;
2534       ctx.fillText(
2535         node['label'],
2536         Math.round(node['displayX'] + node['displaySize'] * 1.5),
2537         Math.round(node['displayY'] + fontSize / 2 - 3)
2538       );
2539     }
2540
2541     return self;
2542   };
2543
2544   /**
2545    * Draws one hover node to the corresponding canvas.
2546    * @param  {Object} node The hover node to draw.
2547    * @return {Plotter} Returns itself.
2548    */
2549   function drawHoverNode(node) {
2550     var ctx = hoverCtx;
2551
2552     var fontSize = self.p.labelSize == 'fixed' ?
2553                    self.p.defaultLabelSize :
2554                    self.p.labelSizeRatio * node['displaySize'];
2555
2556     ctx.font = (self.p.hoverFontStyle || self.p.fontStyle || '') + ' ' +
2557                fontSize + 'px ' +
2558                (self.p.hoverFont || self.p.font || '');
2559
2560     ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
2561                     (node['color'] || self.p.defaultNodeColor) :
2562                     self.p.defaultHoverLabelBGColor;
2563
2564     // Label background:
2565     ctx.beginPath();
2566
2567     if (self.p.labelHoverShadow) {
2568       ctx.shadowOffsetX = 0;
2569       ctx.shadowOffsetY = 0;
2570       ctx.shadowBlur = 4;
2571       ctx.shadowColor = self.p.labelHoverShadowColor;
2572     }
2573
2574     sigma.tools.drawRoundRect(
2575       ctx,
2576       Math.round(node['displayX'] - fontSize / 2 - 2),
2577       Math.round(node['displayY'] - fontSize / 2 - 2),
2578       Math.round(ctx.measureText(node['label']).width +
2579         node['displaySize'] * 1.5 +
2580         fontSize / 2 + 4),
2581       Math.round(fontSize + 4),
2582       Math.round(fontSize / 2 + 2),
2583       'left'
2584     );
2585     ctx.closePath();
2586     ctx.fill();
2587
2588     ctx.shadowOffsetX = 0;
2589     ctx.shadowOffsetY = 0;
2590     ctx.shadowBlur = 0;
2591
2592     // Node border:
2593     ctx.beginPath();
2594     ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
2595                     (node['color'] || self.p.defaultNodeColor) :
2596                     self.p.defaultNodeBorderColor;
2597     ctx.arc(Math.round(node['displayX']),
2598             Math.round(node['displayY']),
2599             node['displaySize'] + self.p.borderSize,
2600             0,
2601             Math.PI * 2,
2602             true);
2603     ctx.closePath();
2604     ctx.fill();
2605
2606     // Node:
2607     ctx.beginPath();
2608     ctx.fillStyle = self.p.nodeHoverColor == 'node' ?
2609                     (node['color'] || self.p.defaultNodeColor) :
2610                     self.p.defaultNodeHoverColor;
2611     ctx.arc(Math.round(node['displayX']),
2612             Math.round(node['displayY']),
2613             node['displaySize'],
2614             0,
2615             Math.PI * 2,
2616             true);
2617
2618     ctx.closePath();
2619     ctx.fill();
2620
2621     // Label:
2622     ctx.fillStyle = self.p.labelHoverColor == 'node' ?
2623                     (node['color'] || self.p.defaultNodeColor) :
2624                     self.p.defaultLabelHoverColor;
2625     ctx.fillText(
2626       node['label'],
2627       Math.round(node['displayX'] + node['displaySize'] * 1.5),
2628       Math.round(node['displayY'] + fontSize / 2 - 3)
2629     );
2630
2631     return self;
2632   };
2633
2634   /**
2635    * Draws one active node to the corresponding canvas.
2636    * @param  {Object} node The active node to draw.
2637    * @return {Plotter} Returns itself.
2638    */
2639   function drawActiveNode(node) {
2640     var ctx = hoverCtx;
2641
2642     if (!isOnScreen(node)) {
2643       return self;
2644     }
2645
2646     var fontSize = self.p.labelSize == 'fixed' ?
2647                    self.p.defaultLabelSize :
2648                    self.p.labelSizeRatio * node['displaySize'];
2649
2650     ctx.font = (self.p.activeFontStyle || self.p.fontStyle || '') + ' ' +
2651                fontSize + 'px ' +
2652                (self.p.activeFont || self.p.font || '');
2653
2654     ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
2655                     (node['color'] || self.p.defaultNodeColor) :
2656                     self.p.defaultActiveLabelBGColor;
2657
2658     // Label background:
2659     ctx.beginPath();
2660
2661     if (self.p.labelActiveShadow) {
2662       ctx.shadowOffsetX = 0;
2663       ctx.shadowOffsetY = 0;
2664       ctx.shadowBlur = 4;
2665       ctx.shadowColor = self.p.labelActiveShadowColor;
2666     }
2667
2668     sigma.tools.drawRoundRect(
2669       ctx,
2670       Math.round(node['displayX'] - fontSize / 2 - 2),
2671       Math.round(node['displayY'] - fontSize / 2 - 2),
2672       Math.round(ctx.measureText(node['label']).width +
2673         node['displaySize'] * 1.5 +
2674         fontSize / 2 + 4),
2675       Math.round(fontSize + 4),
2676       Math.round(fontSize / 2 + 2),
2677       'left'
2678     );
2679     ctx.closePath();
2680     ctx.fill();
2681
2682     ctx.shadowOffsetX = 0;
2683     ctx.shadowOffsetY = 0;
2684     ctx.shadowBlur = 0;
2685
2686     // Node border:
2687     ctx.beginPath();
2688     ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
2689                     (node['color'] || self.p.defaultNodeColor) :
2690                     self.p.defaultNodeBorderColor;
2691     ctx.arc(Math.round(node['displayX']),
2692             Math.round(node['displayY']),
2693             node['displaySize'] + self.p.borderSize,
2694             0,
2695             Math.PI * 2,
2696             true);
2697     ctx.closePath();
2698     ctx.fill();
2699
2700     // Node:
2701     ctx.beginPath();
2702     ctx.fillStyle = self.p.nodeActiveColor == 'node' ?
2703                     (node['color'] || self.p.defaultNodeColor) :
2704                     self.p.defaultNodeActiveColor;
2705     ctx.arc(Math.round(node['displayX']),
2706             Math.round(node['displayY']),
2707             node['displaySize'],
2708             0,
2709             Math.PI * 2,
2710             true);
2711
2712     ctx.closePath();
2713     ctx.fill();
2714
2715     // Label:
2716     ctx.fillStyle = self.p.labelActiveColor == 'node' ?
2717                     (node['color'] || self.p.defaultNodeColor) :
2718                     self.p.defaultLabelActiveColor;
2719     ctx.fillText(
2720       node['label'],
2721       Math.round(node['displayX'] + node['displaySize'] * 1.5),
2722       Math.round(node['displayY'] + fontSize / 2 - 3)
2723     );
2724
2725     return self;
2726   };
2727
2728   /**
2729    * Determines if a node is on the screen or not. The limits here are
2730    * bigger than the actual screen, to avoid seeing labels disappear during
2731    * the graph manipulation.
2732    * @param  {Object}  node The node to check if it is on or out the screen.
2733    * @return {boolean} Returns false if the node is hidden or not on the screen
2734    *                   or true else.
2735    */
2736   function isOnScreen(node) {
2737     if (isNaN(node['x']) || isNaN(node['y'])) {
2738       throw (new Error('A node\'s coordinate is not a ' +
2739                        'number (id: ' + node['id'] + ')')
2740       );
2741     }
2742
2743     return !node['hidden'] &&
2744            (node['displayX'] + node['displaySize'] > -width / 3) &&
2745            (node['displayX'] - node['displaySize'] < width * 4 / 3) &&
2746            (node['displayY'] + node['displaySize'] > -height / 3) &&
2747            (node['displayY'] - node['displaySize'] < height * 4 / 3);
2748   };
2749
2750   /**
2751    * Resizes this instance.
2752    * @param  {number} w The new width.
2753    * @param  {number} h The new height.
2754    * @return {Plotter} Returns itself.
2755    */
2756   function resize(w, h) {
2757     width = w;
2758     height = h;
2759
2760     return self;
2761   }
2762
2763   this.task_drawLabel = task_drawLabel;
2764   this.task_drawEdge = task_drawEdge;
2765   this.task_drawNode = task_drawNode;
2766   this.drawActiveNode = drawActiveNode;
2767   this.drawHoverNode = drawHoverNode;
2768   this.isOnScreen = isOnScreen;
2769   this.resize = resize;
2770 }
2771
2772 /**
2773  * A class to monitor some local / global probes directly on an instance,
2774  * inside a div DOM element.
2775  * It executes different methods (called "probes") regularly, and displays
2776  * the results on the element.
2777  * @constructor
2778  * @extends sigma.classes.Cascade
2779  * @param {Sigma} instance The instance to monitor.
2780  * @param {element} dom    The div DOM element to draw write on.
2781  * @this {Monitor}
2782  */
2783 function Monitor(instance, dom) {
2784   sigma.classes.Cascade.call(this);
2785
2786   /**
2787    * Represents "this", without the well-known scope issue.
2788    * @private
2789    * @type {Monitor}
2790    */
2791   var self = this;
2792
2793   /**
2794    * {@link Sigma} instance owning this Monitor instance.
2795    * @type {Sigma}
2796    */
2797   this.instance = instance;
2798
2799   /**
2800    * Determines if the monitoring is activated or not.
2801    * @type {Boolean}
2802    */
2803   this.monitoring = false;
2804
2805   /**
2806    * The different parameters that define how this instance should work. It
2807    * also contains the different probes.
2808    * @see sigma.classes.Cascade
2809    * @type {Object}
2810    */
2811   this.p = {
2812     fps: 40,
2813     dom: dom,
2814     globalProbes: {
2815       'Time (ms)': sigma.chronos.getExecutionTime,
2816       'Queue': sigma.chronos.getQueuedTasksCount,
2817       'Tasks': sigma.chronos.getTasksCount,
2818       'FPS': sigma.chronos.getFPS
2819     },
2820     localProbes: {
2821       'Nodes count': function() { return self.instance.graph.nodes.length; },
2822       'Edges count': function() { return self.instance.graph.edges.length; }
2823     }
2824   };
2825
2826   /**
2827    * Activates the monitoring: Some texts describing some values about sigma.js
2828    * or the owning {@link Sigma} instance will appear over the graph, but
2829    * beneath the mouse sensible DOM element.
2830    * @return {Monitor} Returns itself.
2831    */
2832   function activate() {
2833     if (!self.monitoring) {
2834       self.monitoring = window.setInterval(routine, 1000 / self.p.fps);
2835     }
2836
2837     return self;
2838   }
2839
2840   /**
2841    * Desactivates the monitoring: Will disappear, and stop computing the
2842    * different probes.
2843    * @return {Monitor} Returns itself.
2844    */
2845   function desactivate() {
2846     if (self.monitoring) {
2847       window.clearInterval(self.monitoring);
2848       self.monitoring = null;
2849
2850       self.p.dom.innerHTML = '';
2851     }
2852
2853     return self;
2854   }
2855
2856   /**
2857    * The private method dedicated to compute the different values to observe.
2858    * @private
2859    * @return {Monitor} Returns itself.
2860    */
2861   function routine() {
2862     var s = '';
2863
2864     s += '<p>GLOBAL :</p>';
2865     for (var k in self.p.globalProbes) {
2866       s += '<p>' + k + ' : ' + self.p.globalProbes[k]() + '</p>';
2867     }
2868
2869     s += '<br><p>LOCAL :</p>';
2870     for (var k in self.p.localProbes) {
2871       s += '<p>' + k + ' : ' + self.p.localProbes[k]() + '</p>';
2872     }
2873
2874     self.p.dom.innerHTML = s;
2875
2876     return self;
2877   }
2878
2879   this.activate = activate;
2880   this.desactivate = desactivate;
2881 }
2882
2883 /**
2884  * Add a function to the prototype of SigmaPublic, but with access to the
2885  * Sigma class properties.
2886  * @param {string} pluginName        [description].
2887  * @param {function} caller          [description].
2888  * @param {function(Sigma)} launcher [description].
2889  */
2890 sigma.addPlugin = function(pluginName, caller, launcher) {
2891   SigmaPublic.prototype[pluginName] = caller;
2892   local.plugins.push(launcher);
2893 };
2894 sigma.tools.drawRoundRect = function(ctx, x, y, w, h, ellipse, corners) {
2895   var e = ellipse ? ellipse : 0;
2896   var c = corners ? corners : [];
2897   c = ((typeof c) == 'string') ? c.split(' ') : c;
2898
2899   var tl = e && (c.indexOf('topleft') >= 0 ||
2900                  c.indexOf('top') >= 0 ||
2901                  c.indexOf('left') >= 0);
2902   var tr = e && (c.indexOf('topright') >= 0 ||
2903                  c.indexOf('top') >= 0 ||
2904                  c.indexOf('right') >= 0);
2905   var bl = e && (c.indexOf('bottomleft') >= 0 ||
2906                  c.indexOf('bottom') >= 0 ||
2907                  c.indexOf('left') >= 0);
2908   var br = e && (c.indexOf('bottomright') >= 0 ||
2909                  c.indexOf('bottom') >= 0 ||
2910                  c.indexOf('right') >= 0);
2911
2912   ctx.moveTo(x, y + e);
2913
2914   if (tl) {
2915     ctx.arcTo(x, y, x + e, y, e);
2916   }else {
2917     ctx.lineTo(x, y);
2918   }
2919
2920   if (tr) {
2921     ctx.lineTo(x + w - e, y);
2922     ctx.arcTo(x + w, y, x + w, y + e, e);
2923   }else {
2924     ctx.lineTo(x + w, y);
2925   }
2926
2927   if (br) {
2928     ctx.lineTo(x + w, y + h - e);
2929     ctx.arcTo(x + w, y + h, x + w - e, y + h, e);
2930   }else {
2931     ctx.lineTo(x + w, y + h);
2932   }
2933
2934   if (bl) {
2935     ctx.lineTo(x + e, y + h);
2936     ctx.arcTo(x, y + h, x, y + h - e, e);
2937   }else {
2938     ctx.lineTo(x, y + h);
2939   }
2940
2941   ctx.lineTo(x, y + e);
2942 };
2943
2944 sigma.tools.getRGB = function(s, asArray) {
2945   s = s.toString();
2946   var res = {
2947     'r': 0,
2948     'g': 0,
2949     'b': 0
2950   };
2951
2952   if (s.length >= 3) {
2953     if (s.charAt(0) == '#') {
2954       var l = s.length - 1;
2955       if (l == 6) {
2956         res = {
2957           'r': parseInt(s.charAt(1) + s.charAt(2), 16),
2958           'g': parseInt(s.charAt(3) + s.charAt(4), 16),
2959           'b': parseInt(s.charAt(5) + s.charAt(5), 16)
2960         };
2961       }else if (l == 3) {
2962         res = {
2963           'r': parseInt(s.charAt(1) + s.charAt(1), 16),
2964           'g': parseInt(s.charAt(2) + s.charAt(2), 16),
2965           'b': parseInt(s.charAt(3) + s.charAt(3), 16)
2966         };
2967       }
2968     }
2969   }
2970
2971   if (asArray) {
2972     res = [
2973       res['r'],
2974       res['g'],
2975       res['b']
2976     ];
2977   }
2978
2979   return res;
2980 };
2981
2982 sigma.tools.rgbToHex = function(R, G, B) {
2983   return sigma.tools.toHex(R) + sigma.tools.toHex(G) + sigma.tools.toHex(B);
2984 };
2985
2986 sigma.tools.toHex = function(n) {
2987   n = parseInt(n, 10);
2988
2989   if (isNaN(n)) {
2990     return '00';
2991   }
2992   n = Math.max(0, Math.min(n, 255));
2993   return '0123456789ABCDEF'.charAt((n - n % 16) / 16) +
2994          '0123456789ABCDEF'.charAt(n % 16);
2995 };
2996
2997 sigma.easing = {
2998   linear: {},
2999   quadratic: {}
3000 };
3001
3002 sigma.easing.linear.easenone = function(k) {
3003   return k;
3004 };
3005
3006 sigma.easing.quadratic.easein = function(k) {
3007   return k * k;
3008 };
3009
3010 sigma.easing.quadratic.easeout = function(k) {
3011   return - k * (k - 2);
3012 };
3013
3014 sigma.easing.quadratic.easeinout = function(k) {
3015   if ((k *= 2) < 1) return 0.5 * k * k;
3016   return - 0.5 * (--k * (k - 2) - 1);
3017 };
3018
3019 /**
3020  * sigma.chronos manages frames insertion to simulate asynchronous computing.
3021  * It has been designed to make possible to execute heavy computing tasks
3022  * for the browser, without freezing it.
3023  * @constructor
3024  * @extends sigma.classes.Cascade
3025  * @extends sigma.classes.EventDispatcher
3026  * @this {sigma.chronos}
3027  */
3028 sigma.chronos = new (function() {
3029   sigma.classes.EventDispatcher.call(this);
3030
3031   /**
3032    * Represents "this", without the well-known scope issue.
3033    * @private
3034    * @type {sigma.chronos}
3035    */
3036   var self = this;
3037
3038   /**
3039    * Indicates whether any task is actively running or not.
3040    * @private
3041    * @type {boolean}
3042    */
3043   var isRunning = false;
3044
3045   /**
3046    * Indicates the FPS "goal", that will define the theoretical
3047    * frame length.
3048    * @private
3049    * @type {number}
3050    */
3051   var fpsReq = 80;
3052
3053   /**
3054    * Stores the last computed FPS value (FPS is computed only when any
3055    * task is running).
3056    * @private
3057    * @type {number}
3058    */
3059   var lastFPS = 0;
3060
3061   /**
3062    * The number of frames inserted since the last start.
3063    * @private
3064    * @type {number}
3065    */
3066   var framesCount = 0;
3067
3068   /**
3069    * The theoretical frame time.
3070    * @private
3071    * @type {number}
3072    */
3073   var frameTime = 1000 / fpsReq;
3074
3075   /**
3076    * The theoretical frame length, minus the last measured delay.
3077    * @private
3078    * @type {number}
3079    */
3080   var correctedFrameTime = frameTime;
3081
3082   /**
3083    * The measured length of the last frame.
3084    * @private
3085    * @type {number}
3086    */
3087   var effectiveTime = 0;
3088
3089   /**
3090    * The time passed since the last runTasks action.
3091    * @private
3092    * @type {number}
3093    */
3094   var currentTime = 0;
3095
3096   /**
3097    * The time when the last frame was inserted.
3098    * @private
3099    * @type {number}
3100    */
3101   var startTime = 0;
3102
3103   /**
3104    * The difference between the theoretical frame length and the
3105    * last measured frame length.
3106    * @private
3107    * @type {number}
3108    */
3109   var delay = 0;
3110
3111   /**
3112    * The container of all active generators.
3113    * @private
3114    * @type {Object.<string, Object>}
3115    */
3116   var generators = {};
3117
3118   /**
3119    * The array of all the referenced and active tasks.
3120    * @private
3121    * @type {Array.<Object>}
3122    */
3123   var tasks = [];
3124
3125   /**
3126    * The array of all the referenced and queued tasks.
3127    * @private
3128    * @type {Array.<Object>}
3129    */
3130   var queuedTasks = [];
3131
3132   /**
3133    * The index of the next task to execute.
3134    * @private
3135    * @type {number}
3136    */
3137   var taskIndex = 0;
3138
3139
3140   /**
3141    * Inserts a frame before executing the callback.
3142    * @param  {function()} callback The callback to execute after having
3143    *                               inserted the frame.
3144    * @return {sigma.chronos} Returns itself.
3145    */
3146   function insertFrame(callback) {
3147     window.setTimeout(callback, 0);
3148     return self;
3149   }
3150
3151   /**
3152    * The local method that executes routine, and inserts frames when needed.
3153    * It dispatches a "frameinserted" event after having inserted any frame,
3154    * and an "insertframe" event before.
3155    * @private
3156    */
3157   function frameInserter() {
3158     self.dispatch('frameinserted');
3159     while (isRunning && tasks.length && routine()) {}
3160
3161     if (!isRunning || !tasks.length) {
3162       stopTasks();
3163     } else {
3164       startTime = (new Date()).getTime();
3165       framesCount++;
3166       delay = effectiveTime - frameTime;
3167       correctedFrameTime = frameTime - delay;
3168
3169       self.dispatch('insertframe');
3170       insertFrame(frameInserter);
3171     }
3172   };
3173
3174   /**
3175    * The local method that executes the tasks, and compares the current frame
3176    * length to the ideal frame length.
3177    * @private
3178    * @return {boolean} Returns false if the current frame should be ended,
3179    *                   and true else.
3180    */
3181   function routine() {
3182     taskIndex = taskIndex % tasks.length;
3183
3184     if (!tasks[taskIndex].task()) {
3185       var n = tasks[taskIndex].taskName;
3186
3187       queuedTasks = queuedTasks.filter(function(e) {
3188         (e.taskParent == n) && tasks.push({
3189           taskName: e.taskName,
3190           task: e.task
3191         });
3192         return e.taskParent != n;
3193       });
3194
3195       self.dispatch('killed', tasks.splice(taskIndex--, 1)[0]);
3196     }
3197
3198     taskIndex++;
3199     effectiveTime = (new Date()).getTime() - startTime;
3200     return effectiveTime <= correctedFrameTime;
3201   };
3202
3203   /**
3204    * Starts tasks execution.
3205    * @return {sigma.chronos} Returns itself.
3206    */
3207   function runTasks() {
3208     isRunning = true;
3209     taskIndex = 0;
3210     framesCount = 0;
3211
3212     startTime = (new Date()).getTime();
3213     currentTime = startTime;
3214
3215     self.dispatch('start');
3216     self.dispatch('insertframe');
3217     insertFrame(frameInserter);
3218     return self;
3219   };
3220
3221   /**
3222    * Stops tasks execution, and dispatch a "stop" event.
3223    * @return {sigma.chronos} Returns itself.
3224    */
3225   function stopTasks() {
3226     self.dispatch('stop');
3227     isRunning = false;
3228     return self;
3229   };
3230
3231   /**
3232    * A task is a function that will be executed continuously while it returns
3233    * true. As soon as it return false, the task will be removed.
3234    * If several tasks are present, they will be executed in parallele.
3235    * This method will add the task to this execution process.
3236    * @param {function(): boolean} task     The task to add.
3237    * @param {string} name                  The name of the worker, used for
3238    *                                       managing the different tasks.
3239    * @param {boolean} autostart            If true, sigma.chronos will start
3240    *                                       automatically if it is not working
3241    *                                       yet.
3242    * @return {sigma.chronos} Returns itself.
3243    */
3244   function addTask(task, name, autostart) {
3245     if (typeof task != 'function') {
3246       throw new Error('Task "' + name + '" is not a function');
3247     }
3248
3249     tasks.push({
3250       taskName: name,
3251       task: task
3252     });
3253
3254     isRunning = !!(isRunning || (autostart && runTasks()) || true);
3255     return self;
3256   };
3257
3258   /**
3259    * Will add a task that will be start to be executed as soon as a task
3260    * named as the parent will be removed.
3261    * @param {function(): boolean} task     The task to add.
3262    * @param {string} name                  The name of the worker, used for
3263    *                                       managing the different tasks.
3264    * @param {string} parent                The name of the parent task.
3265    * @return {sigma.chronos} Returns itself.
3266    */
3267   function queueTask(task, name, parent) {
3268     if (typeof task != 'function') {
3269       throw new Error('Task "' + name + '" is not a function');
3270     }
3271
3272     if (!tasks.concat(queuedTasks).some(function(e) {
3273       return e.taskName == parent;
3274     })) {
3275       throw new Error(
3276         'Parent task "' + parent + '" of "' + name + '" is not attached.'
3277       );
3278     }
3279
3280     queuedTasks.push({
3281       taskParent: parent,
3282       taskName: name,
3283       task: task
3284     });
3285
3286     return self;
3287   };
3288
3289   /**
3290    * Removes a task.
3291    * @param  {string} v           If v is undefined, then every tasks will
3292    *                              be removed. If not, each task named v will
3293    *                              be removed.
3294    * @param  {number} queueStatus Determines the queued tasks behaviour. If 0,
3295    *                              then nothing will happen. If 1, the tasks
3296    *                              queued to any removed task will be triggered.
3297    *                              If 2, the tasks queued to any removed task
3298    *                              will be removed as well.
3299    * @return {sigma.chronos} Returns itself.
3300    */
3301   function removeTask(v, queueStatus) {
3302     if (v == undefined) {
3303       tasks = [];
3304       if (queueStatus == 1) {
3305         queuedTasks = [];
3306       }else if (queueStatus == 2) {
3307         tasks = queuedTasks;
3308         queuedTasks = [];
3309       }
3310       stopTasks();
3311     } else {
3312       var n = (typeof v == 'string') ? v : '';
3313       tasks = tasks.filter(function(e) {
3314         if ((typeof v == 'string') ? e.taskName == v : e.task == v) {
3315           n = e.taskName;
3316           return false;
3317         }
3318         return true;
3319       });
3320
3321       if (queueStatus > 0) {
3322         queuedTasks = queuedTasks.filter(function(e) {
3323           if (queueStatus == 1 && e.taskParent == n) {
3324             tasks.push(e);
3325           }
3326           return e.taskParent != n;
3327         });
3328       }
3329     }
3330
3331     isRunning = !!(!tasks.length || (stopTasks() && false));
3332     return self;
3333   };
3334
3335   /**
3336    * A generator is a pair task/condition. The task will be executed
3337    * while it returns true.
3338    * When it returns false, the condition will be tested. If
3339    * the condition returns true, the task will be executed
3340    * again at the next process iteration. If not, the generator
3341    * is removed.
3342    * If several generators are present, they will be executed one
3343    * by one: When the first stops, the second will start, etc. When
3344    * they are all ended, then the conditions will be tested to know
3345    * which generators have to be started again.
3346    * @param {string} id                     The generators ID.
3347    * @param {function(): boolean} task      The generator's task.
3348    * @param {function(): boolean} condition The generator's condition.
3349    * @return {sigma.chronos} Returns itself.
3350    */
3351   function addGenerator(id, task, condition) {
3352     if (generators[id] != undefined) {
3353       return self;
3354     }
3355
3356     generators[id] = {
3357       task: task,
3358       condition: condition
3359     };
3360
3361     getGeneratorsCount(true) == 0 && startGenerators();
3362     return self;
3363   };
3364
3365   /**
3366    * Removes a generator. It means that the task will continue being eecuted
3367    * until it returns false, but then the
3368    * condition will not be tested.
3369    * @param  {string} id The generator's ID.
3370    * @return {sigma.chronos} Returns itself.
3371    */
3372   function removeGenerator(id) {
3373     if (generators[id]) {
3374       generators[id].on = false;
3375       generators[id].del = true;
3376     }
3377     return self;
3378   };
3379
3380   /**
3381    * Returns the number of generators.
3382    * @private
3383    * @param  {boolean} running If true, returns the number of active
3384    *                          generators instead.
3385    * @return {sigma.chronos} Returns itself.
3386    */
3387   function getGeneratorsCount(running) {
3388     return running ?
3389       Object.keys(generators).filter(function(id) {
3390         return !!generators[id].on;
3391       }).length :
3392       Object.keys(generators).length;
3393   };
3394
3395   /**
3396    * Returns the array of the generators IDs.
3397    * @return {array.<string>} The array of IDs.
3398    */
3399   function getGeneratorsIDs() {
3400     return Object.keys(generators);
3401   }
3402
3403   /**
3404    * startGenerators is the method that manages which generator
3405    * is the next to start when another one stops. It will dispatch
3406    * a "stopgenerators" event if there is no more generator to start,
3407    * and a "startgenerators" event else.
3408    * @return {sigma.chronos} Returns itself.
3409    */
3410   function startGenerators() {
3411     if (!Object.keys(generators).length) {
3412       self.dispatch('stopgenerators');
3413     }else {
3414       self.dispatch('startgenerators');
3415
3416       self.unbind('killed', onTaskEnded);
3417       insertFrame(function() {
3418         for (var k in generators) {
3419           generators[k].on = true;
3420           addTask(
3421             generators[k].task,
3422             k,
3423             false
3424           );
3425         }
3426       });
3427
3428       self.bind('killed', onTaskEnded).runTasks();
3429     }
3430
3431     return self;
3432   };
3433
3434   /**
3435    * A callback triggered everytime the task of a generator stops, that will
3436    * test the related generator's condition, and see if there is still any
3437    * generator to start.
3438    * @private
3439    * @param  {Object} e The sigma.chronos "killed" event.
3440    */
3441   function onTaskEnded(e) {
3442     if (generators[e['content'].taskName] != undefined) {
3443       if (generators[e['content'].taskName].del ||
3444           !generators[e['content'].taskName].condition()) {
3445         delete generators[e['content'].taskName];
3446       }else {
3447         generators[e['content'].taskName].on = false;
3448       }
3449
3450       if (getGeneratorsCount(true) == 0) {
3451         startGenerators();
3452       }
3453     }
3454   };
3455
3456   /**
3457    * Either set or returns the fpsReq property. This property determines
3458    * the number of frames that should be inserted per second.
3459    * @param  {?number} v The frequency asked.
3460    * @return {(Chronos|number)} Returns the frequency if v is undefined, and
3461    *                          itself else.
3462    */
3463   function frequency(v) {
3464     if (v != undefined) {
3465       fpsReq = Math.abs(1 * v);
3466       frameTime = 1000 / fpsReq;
3467       framesCount = 0;
3468       return self;
3469     } else {
3470       return fpsReq;
3471     }
3472   };
3473
3474   /**
3475    * Returns the actual average number of frames that are inserted per
3476    * second.
3477    * @return {number} The actual average FPS.
3478    */
3479   function getFPS() {
3480     if (isRunning) {
3481       lastFPS =
3482         Math.round(
3483           framesCount /
3484           ((new Date()).getTime() - currentTime) *
3485           10000
3486         ) / 10;
3487     }
3488
3489     return lastFPS;
3490   };
3491
3492   /**
3493    * Returns the number of tasks.
3494    * @return {number} The number of tasks.
3495    */
3496   function getTasksCount() {
3497     return tasks.length;
3498   }
3499
3500   /**
3501    * Returns the number of queued tasks.
3502    * @return {number} The number of queued tasks.
3503    */
3504   function getQueuedTasksCount() {
3505     return queuedTasks.length;
3506   }
3507
3508   /**
3509    * Returns how long sigma.chronos has active tasks running
3510    * without interuption for, in ms.
3511    * @return {number} The time chronos is running without interuption for.
3512    */
3513   function getExecutionTime() {
3514     return startTime - currentTime;
3515   }
3516
3517   this.frequency = frequency;
3518
3519   this.runTasks = runTasks;
3520   this.stopTasks = stopTasks;
3521   this.insertFrame = insertFrame;
3522
3523   this.addTask = addTask;
3524   this.queueTask = queueTask;
3525   this.removeTask = removeTask;
3526
3527   this.addGenerator = addGenerator;
3528   this.removeGenerator = removeGenerator;
3529   this.startGenerators = startGenerators;
3530   this.getGeneratorsIDs = getGeneratorsIDs;
3531
3532   this.getFPS = getFPS;
3533   this.getTasksCount = getTasksCount;
3534   this.getQueuedTasksCount = getQueuedTasksCount;
3535   this.getExecutionTime = getExecutionTime;
3536
3537   return this;
3538 })();
3539
3540 sigma.debugMode = 0;
3541
3542 sigma.log = function() {
3543   if (sigma.debugMode == 1) {
3544     for (var k in arguments) {
3545       console.log(arguments[k]);
3546     }
3547   }else if (sigma.debugMode > 1) {
3548     for (var k in arguments) {
3549       throw new Error(arguments[k]);
3550     }
3551   }
3552
3553   return sigma;
3554 };
3555
3556 sigma.publicPrototype = SigmaPublic.prototype;
3557 })();
3558