html export : not ready
[iramuteq] / webexport / js / sigma.concat.js
diff --git a/webexport/js/sigma.concat.js b/webexport/js/sigma.concat.js
new file mode 100644 (file)
index 0000000..862a327
--- /dev/null
@@ -0,0 +1,3558 @@
+// Define packages:
+var sigma = {};
+sigma.tools = {};
+sigma.classes = {};
+sigma.instances = {};
+
+// Adding Array helpers, if not present yet:
+(function() {
+  if (!Array.prototype.some) {
+    Array.prototype.some = function(fun /*, thisp*/) {
+      var len = this.length;
+      if (typeof fun != 'function') {
+        throw new TypeError();
+      }
+
+      var thisp = arguments[1];
+      for (var i = 0; i < len; i++) {
+        if (i in this &&
+            fun.call(thisp, this[i], i, this)) {
+          return true;
+        }
+      }
+
+      return false;
+    };
+  }
+
+  if (!Array.prototype.forEach) {
+    Array.prototype.forEach = function(fun /*, thisp*/) {
+      var len = this.length;
+      if (typeof fun != 'function') {
+        throw new TypeError();
+      }
+
+      var thisp = arguments[1];
+      for (var i = 0; i < len; i++) {
+        if (i in this) {
+          fun.call(thisp, this[i], i, this);
+        }
+      }
+    };
+  }
+
+  if (!Array.prototype.map) {
+    Array.prototype.map = function(fun /*, thisp*/) {
+      var len = this.length;
+      if (typeof fun != 'function') {
+        throw new TypeError();
+      }
+
+      var res = new Array(len);
+      var thisp = arguments[1];
+      for (var i = 0; i < len; i++) {
+        if (i in this) {
+          res[i] = fun.call(thisp, this[i], i, this);
+        }
+      }
+
+      return res;
+    };
+  }
+
+  if (!Array.prototype.filter) {
+    Array.prototype.filter = function(fun /*, thisp*/) {
+      var len = this.length;
+      if (typeof fun != 'function')
+        throw new TypeError();
+
+      var res = new Array();
+      var thisp = arguments[1];
+      for (var i = 0; i < len; i++) {
+        if (i in this) {
+          var val = this[i]; // in case fun mutates this
+          if (fun.call(thisp, val, i, this)) {
+            res.push(val);
+          }
+        }
+      }
+
+      return res;
+    };
+  }
+
+  if (!Object.keys) {
+    Object.keys = (function() {
+      var hasOwnProperty = Object.prototype.hasOwnProperty,
+          hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
+          dontEnums = [
+            'toString',
+            'toLocaleString',
+            'valueOf',
+            'hasOwnProperty',
+            'isPrototypeOf',
+            'propertyIsEnumerable',
+            'constructor'
+          ],
+          dontEnumsLength = dontEnums.length;
+
+      return function(obj) {
+        if (typeof obj !== 'object' &&
+            typeof obj !== 'function' ||
+            obj === null
+        ) {
+          throw new TypeError('Object.keys called on non-object');
+        }
+
+        var result = [];
+
+        for (var prop in obj) {
+          if (hasOwnProperty.call(obj, prop)) result.push(prop);
+        }
+
+        if (hasDontEnumBug) {
+          for (var i = 0; i < dontEnumsLength; i++) {
+            if (hasOwnProperty.call(obj, dontEnums[i])) {
+              result.push(dontEnums[i]);
+            }
+          }
+        }
+        return result;
+      }
+    })();
+  }
+})();
+
+/**
+ * sigma.js custom event dispatcher class.
+ * @constructor
+ * @this {sigma.classes.EventDispatcher}
+ */
+sigma.classes.EventDispatcher = function() {
+  /**
+   * An object containing all the different handlers bound to one or many
+   * events, indexed by these events.
+   * @private
+   * @type {Object.<string,Object>}
+   */
+  var _h = {};
+
+  /**
+   * Represents "this", without the well-known scope issue.
+   * @private
+   * @type {sigma.classes.EventDispatcher}
+   */
+  var _self = this;
+
+  /**
+   * Will execute the handler the next (and only the next) time that the
+   * indicated event (or the indicated events) will be triggered.
+   * @param  {string} events            The name of the event (or the events
+   *                                    separated by spaces).
+   * @param  {function(Object)} handler The handler to bind.
+   * @return {sigma.classes.EventDispatcher} Returns itself.
+   */
+  function one(events, handler) {
+    if (!handler || !events) {
+      return _self;
+    }
+
+    var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
+
+    eArray.forEach(function(event) {
+      if (!_h[event]) {
+        _h[event] = [];
+      }
+
+      _h[event].push({
+        'h': handler,
+        'one': true
+      });
+    });
+
+    return _self;
+  }
+
+  /**
+   * Will execute the handler everytime that the indicated event (or the
+   * indicated events) will be triggered.
+   * @param  {string} events            The name of the event (or the events
+   *                                    separated by spaces).
+   * @param  {function(Object)} handler The handler to bind.
+   * @return {sigma.classes.EventDispatcher} Returns itself.
+   */
+  function bind(events, handler) {
+    if (!handler || !events) {
+      return _self;
+    }
+
+    var eArray = ((typeof events) == 'string') ? events.split(' ') : events;
+
+    eArray.forEach(function(event) {
+      if (!_h[event]) {
+        _h[event] = [];
+      }
+
+      _h[event].push({
+        'h': handler,
+        'one': false
+      });
+    });
+
+    return _self;
+  }
+
+  /**
+   * Unbinds the handler from a specified event (or specified events).
+   * @param  {?string} events            The name of the event (or the events
+   *                                     separated by spaces). If undefined,
+   *                                     then all handlers are unbound.
+   * @param  {?function(Object)} handler The handler to unbind. If undefined,
+   *                                     each handler bound to the event or the
+   *                                     events will be unbound.
+   * @return {sigma.classes.EventDispatcher} Returns itself.
+   */
+  function unbind(events, handler) {
+    if (!events) {
+      _h = {};
+    }
+
+    var eArray = typeof events == 'string' ? events.split(' ') : events;
+
+    if (handler) {
+      eArray.forEach(function(event) {
+        if (_h[event]) {
+          _h[event] = _h[event].filter(function(e) {
+            return e['h'] != handler;
+          });
+        }
+
+        if (_h[event] && _h[event].length == 0) {
+          delete _h[event];
+        }
+      });
+    }else {
+      eArray.forEach(function(event) {
+        delete _h[event];
+      });
+    }
+
+    return _self;
+  }
+
+  /**
+   * Executes each handler bound to the event
+   * @param  {string} type     The type of the event.
+   * @param  {?Object} content The content of the event (optional).
+   * @return {sigma.classes.EventDispatcher} Returns itself.
+   */
+  function dispatch(type, content) {
+    if (_h[type]) {
+      _h[type].forEach(function(e) {
+        e['h']({
+          'type': type,
+          'content': content,
+          'target': _self
+        });
+      });
+
+      _h[type] = _h[type].filter(function(e) {
+        return !e['one'];
+      });
+    }
+
+    return _self;
+  }
+
+  /* PUBLIC INTERFACE: */
+  this.one = one;
+  this.bind = bind;
+  this.unbind = unbind;
+  this.dispatch = dispatch;
+};
+
+/**
+ * A jQuery like properties management class. It works like jQuery .css()
+ * method: You can call it with juste one string to get the corresponding
+ * property, with a string and anything else to set the corresponding property,
+ * or directly with an object, and then each pair string / object (or any type)
+ * will be set in the properties.
+ * @constructor
+ * @this {sigma.classes.Cascade}
+ */
+sigma.classes.Cascade = function() {
+  /**
+   * This instance properties.
+   * @protected
+   * @type {Object}
+   */
+  this.p = {};
+
+  /**
+   * The method to use to set/get any property of this instance.
+   * @param  {(string|Object)} a1 If it is a string and if a2 is undefined,
+   *                              then it will return the corresponding
+   *                              property.
+   *                              If it is a string and if a2 is set, then it
+   *                              will set a2 as the property corresponding to
+   *                              a1, and return this.
+   *                              If it is an object, then each pair string /
+   *                              object (or any other type) will be set as a
+   *                              property.
+   * @param  {*?} a2              The new property corresponding to a1 if a1 is
+   *                              a string.
+   * @return {(*|sigma.classes.Cascade)} Returns itself or the corresponding
+   *                                     property.
+   */
+  this.config = function(a1, a2) {
+    if (typeof a1 == 'string' && a2 == undefined) {
+      return this.p[a1];
+    } else {
+      var o = (typeof a1 == 'object' && a2 == undefined) ? a1 : {};
+      if (typeof a1 == 'string') {
+        o[a1] = a2;
+      }
+
+      for (var k in o) {
+        if (this.p[k] != undefined) {
+          this.p[k] = o[k];
+        }
+      }
+      return this;
+    }
+  };
+};
+
+(function() {
+// Define local shortcut:
+var id = 0;
+
+// Define local package:
+var local = {};
+local.plugins = [];
+
+sigma.init = function(dom) {
+  var inst = new Sigma(dom, (++id).toString());
+  sigma.instances[id] = new SigmaPublic(inst);
+  return sigma.instances[id];
+};
+
+/**
+ * The graph data model used in sigma.js.
+ * @constructor
+ * @extends sigma.classes.Cascade
+ * @extends sigma.classes.EventDispatcher
+ * @this {Graph}
+ */
+function Graph() {
+  sigma.classes.Cascade.call(this);
+  sigma.classes.EventDispatcher.call(this);
+
+  /**
+   * Represents "this", without the well-known scope issue.
+   * @private
+   * @type {Graph}
+   */
+  var self = this;
+
+  /**
+   * The different parameters that determine how the nodes and edges should be
+   * translated and rescaled.
+   * @type {Object}
+   */
+  this.p = {
+    minNodeSize: 0,
+    maxNodeSize: 0,
+    minEdgeSize: 0,
+    maxEdgeSize: 0,
+    //   Scaling mode:
+    //   - 'inside' (default)
+    //   - 'outside'
+    scalingMode: 'inside',
+    nodesPowRatio: 0.5,
+    edgesPowRatio: 0
+  };
+
+  /**
+   * Contains the borders of the graph. These are useful to avoid the user to
+   * drag the graph out of the canvas.
+   * @type {Object}
+   */
+  this.borders = {};
+
+  /**
+   * Inserts a node in the graph.
+   * @param {string} id     The node's ID.
+   * @param {object} params An object containing the different parameters
+   *                        of the node.
+   * @return {Graph} Returns itself.
+   */
+  function addNode(id, params) {
+    if (self.nodesIndex[id]) {
+      throw new Error('Node "' + id + '" already exists.');
+    }
+
+    params = params || {};
+    var n = {
+      // Numbers :
+      'x': 0,
+      'y': 0,
+      'size': 1,
+      'degree': 0,
+      'inDegree': 0,
+      'outDegree': 0,
+      // Flags :
+      'fixed': false,
+      'active': false,
+      'hidden': false,
+      'forceLabel': false,
+      // Strings :
+      'label': id.toString(),
+      'id': id.toString(),
+      // Custom attributes :
+      'attr': {}
+    };
+
+    for (var k in params) {
+      switch (k) {
+        case 'id':
+          break;
+        case 'x':
+        case 'y':
+        case 'size':
+          n[k] = +params[k];
+          break;
+        case 'fixed':
+        case 'active':
+        case 'hidden':
+        case 'forceLabel':
+          n[k] = !!params[k];
+          break;
+        case 'color':
+        case 'label':
+          n[k] = params[k];
+          break;
+        default:
+          n['attr'][k] = params[k];
+      }
+    }
+
+    self.nodes.push(n);
+    self.nodesIndex[id.toString()] = n;
+
+    return self;
+  };
+
+  /**
+   * Generates the clone of a node, to make it easier to be exported.
+   * @private
+   * @param  {Object} node The node to clone.
+   * @return {Object} The clone of the node.
+   */
+  function cloneNode(node) {
+    return {
+      'x': node['x'],
+      'y': node['y'],
+      'size': node['size'],
+      'degree': node['degree'],
+      'inDegree': node['inDegree'],
+      'outDegree': node['outDegree'],
+      'displayX': node['displayX'],
+      'displayY': node['displayY'],
+      'displaySize': node['displaySize'],
+      'label': node['label'],
+      'id': node['id'],
+      'color': node['color'],
+      'fixed': node['fixed'],
+      'active': node['active'],
+      'hidden': node['hidden'],
+      'forceLabel': node['forceLabel'],
+      'attr': node['attr']
+    };
+  };
+
+  /**
+   * Checks the clone of a node, and inserts its values when possible. For
+   * example, it is possible to modify the size or the color of a node, but it
+   * is not possible to modify its display values or its id.
+   * @private
+   * @param  {Object} node The original node.
+   * @param  {Object} copy The clone.
+   * @return {Graph} Returns itself.
+   */
+  function checkNode(node, copy) {
+    for (var k in copy) {
+      switch (k) {
+        case 'id':
+        case 'attr':
+        case 'degree':
+        case 'inDegree':
+        case 'outDegree':
+        case 'displayX':
+        case 'displayY':
+        case 'displaySize':
+          break;
+        case 'x':
+        case 'y':
+        case 'size':
+          node[k] = +copy[k];
+          break;
+        case 'fixed':
+        case 'active':
+        case 'hidden':
+        case 'forceLabel':
+          node[k] = !!copy[k];
+          break;
+        case 'color':
+        case 'label':
+          node[k] = (copy[k] || '').toString();
+          break;
+        default:
+          node['attr'][k] = copy[k];
+      }
+    }
+
+    return self;
+  };
+
+  /**
+   * Deletes one or several nodes from the graph, and the related edges.
+   * @param  {(string|Array.<string>)} v A string ID, or an Array of several
+   *                                     IDs.
+   * @return {Graph} Returns itself.
+   */
+  function dropNode(v) {
+    var a = (v instanceof Array ? v : [v]) || [];
+    var nodesIdsToRemove = {};
+
+    // Create hash to make lookups faster
+    a.forEach(function(id) {
+      if (self.nodesIndex[id]) {
+        nodesIdsToRemove[id] = true;
+      } else {
+        sigma.log('Node "' + id + '" does not exist.');
+      }
+    });
+
+    var indexesToRemove = [];
+    self.nodes.forEach(function(n, i) {
+      if (n['id'] in nodesIdsToRemove) {
+        // Add to front, so we have a reverse-sorted list
+        indexesToRemove.unshift(i);
+        // No edges means we are done
+        if (n['degree'] == 0) {
+          delete nodesIdsToRemove[n['id']];
+        }
+      }
+    });
+
+    indexesToRemove.forEach(function(index) {
+      self.nodes.splice(index, 1);
+    });
+
+    self.edges = self.edges.filter(function(e) {
+      if (e['source']['id'] in nodesIdsToRemove) {
+        delete self.edgesIndex[e['id']];
+        e['target']['degree']--;
+        e['target']['inDegree']--;
+        return false;
+      }else if (e['target']['id'] in nodesIdsToRemove) {
+        delete self.edgesIndex[e['id']];
+        e['source']['degree']--;
+        e['source']['outDegree']--;
+        return false;
+      }
+      return true;
+    });
+
+    return self;
+  };
+
+  /**
+   * Inserts an edge in the graph.
+   * @param {string} id     The edge ID.
+   * @param {string} source The ID of the edge source.
+   * @param {string} target The ID of the edge target.
+   * @param {object} params An object containing the different parameters
+   *                        of the edge.
+   * @return {Graph} Returns itself.
+   */
+  function addEdge(id, source, target, params) {
+    if (self.edgesIndex[id]) {
+      throw new Error('Edge "' + id + '" already exists.');
+    }
+
+    if (!self.nodesIndex[source]) {
+      var s = 'Edge\'s source "' + source + '" does not exist yet.';
+      throw new Error(s);
+    }
+
+    if (!self.nodesIndex[target]) {
+      var s = 'Edge\'s target "' + target + '" does not exist yet.';
+      throw new Error(s);
+    }
+
+    params = params || {};
+    var e = {
+      'source': self.nodesIndex[source],
+      'target': self.nodesIndex[target],
+      'size': 1,
+      'weight': 1,
+      'displaySize': 0.5,
+      'label': id.toString(),
+      'id': id.toString(),
+      'hidden': false,
+      'attr': {}
+    };
+
+    e['source']['degree']++;
+    e['source']['outDegree']++;
+    e['target']['degree']++;
+    e['target']['inDegree']++;
+
+    for (var k in params) {
+      switch (k) {
+        case 'id':
+        case 'source':
+        case 'target':
+          break;
+        case 'hidden':
+          e[k] = !!params[k];
+          break;
+        case 'size':
+        case 'weight':
+          e[k] = +params[k];
+          break;
+        case 'color':
+          e[k] = params[k].toString();
+          break;
+        case 'type':
+          e[k] = params[k].toString();
+          break;
+        case 'label':
+          e[k] = params[k];
+          break;
+        default:
+          e['attr'][k] = params[k];
+      }
+    }
+
+    self.edges.push(e);
+    self.edgesIndex[id.toString()] = e;
+
+    return self;
+  };
+
+  /**
+   * Generates the clone of a edge, to make it easier to be exported.
+   * @private
+   * @param  {Object} edge The edge to clone.
+   * @return {Object} The clone of the edge.
+   */
+  function cloneEdge(edge) {
+    return {
+      'source': edge['source']['id'],
+      'target': edge['target']['id'],
+      'size': edge['size'],
+      'type': edge['type'],
+      'weight': edge['weight'],
+      'displaySize': edge['displaySize'],
+      'label': edge['label'],
+      'hidden': edge['hidden'],
+      'id': edge['id'],
+      'attr': edge['attr'],
+      'color': edge['color']
+    };
+  };
+
+  /**
+   * Checks the clone of an edge, and inserts its values when possible. For
+   * example, it is possible to modify the label or the type of an edge, but it
+   * is not possible to modify its display values or its id.
+   * @private
+   * @param  {Object} edge The original edge.
+   * @param  {Object} copy The clone.
+   * @return {Graph} Returns itself.
+   */
+  function checkEdge(edge, copy) {
+    for (var k in copy) {
+      switch (k) {
+        case 'id':
+        case 'displaySize':
+          break;
+        case 'weight':
+        case 'size':
+          edge[k] = +copy[k];
+          break;
+        case 'source':
+        case 'target':
+          edge[k] = self.nodesIndex[k] || edge[k];
+          break;
+        case 'hidden':
+          edge[k] = !!copy[k];
+          break;
+        case 'color':
+        case 'label':
+        case 'type':
+          edge[k] = (copy[k] || '').toString();
+          break;
+        default:
+          edge['attr'][k] = copy[k];
+      }
+    }
+
+    return self;
+  };
+
+  /**
+   * Deletes one or several edges from the graph.
+   * @param  {(string|Array.<string>)} v A string ID, or an Array of several
+   *                                     IDs.
+   * @return {Graph} Returns itself.
+   */
+  function dropEdge(v) {
+    var a = (v instanceof Array ? v : [v]) || [];
+
+    a.forEach(function(id) {
+      if (self.edgesIndex[id]) {
+        self.edgesIndex[id]['source']['degree']--;
+        self.edgesIndex[id]['source']['outDegree']--;
+        self.edgesIndex[id]['target']['degree']--;
+        self.edgesIndex[id]['target']['inDegree']--;
+
+        var index = null;
+        self.edges.some(function(n, i) {
+          if (n['id'] == id) {
+            index = i;
+            return true;
+          }
+          return false;
+        });
+
+        index != null && self.edges.splice(index, 1);
+        delete self.edgesIndex[id];
+      }else {
+        sigma.log('Edge "' + id + '" does not exist.');
+      }
+    });
+
+    return self;
+  };
+
+  /**
+   * Deletes every nodes and edges from the graph.
+   * @return {Graph} Returns itself.
+   */
+  function empty() {
+    self.nodes = [];
+    self.nodesIndex = {};
+    self.edges = [];
+    self.edgesIndex = {};
+
+    return self;
+  };
+
+  /**
+   * Computes the display x, y and size of each node, relatively to the
+   * original values and the borders determined in the parameters, such as
+   * each node is in the described area.
+   * @param  {number} w           The area width (actually the width of the DOM
+   *                              root).
+   * @param  {number} h           The area height (actually the height of the
+   *                              DOM root).
+   * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
+   * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
+   * @return {Graph} Returns itself.
+   */
+  function rescale(w, h, parseNodes, parseEdges) {
+    var weightMax = 0, sizeMax = 0;
+
+    parseNodes && self.nodes.forEach(function(node) {
+      sizeMax = Math.max(node['size'], sizeMax);
+    });
+
+    parseEdges && self.edges.forEach(function(edge) {
+      weightMax = Math.max(edge['size'], weightMax);
+    });
+
+    sizeMax = sizeMax || 1;
+    weightMax = weightMax || 1;
+
+    // Recenter the nodes:
+    var xMin, xMax, yMin, yMax;
+    parseNodes && self.nodes.forEach(function(node) {
+      xMax = Math.max(node['x'], xMax || node['x']);
+      xMin = Math.min(node['x'], xMin || node['x']);
+      yMax = Math.max(node['y'], yMax || node['y']);
+      yMin = Math.min(node['y'], yMin || node['y']);
+    });
+
+    // First, we compute the scaling ratio, without considering the sizes
+    // of the nodes : Each node will have its center in the canvas, but might
+    // be partially out of it.
+    var scale = self.p.scalingMode == 'outside' ?
+                Math.max(w / Math.max(xMax - xMin, 1),
+                         h / Math.max(yMax - yMin, 1)) :
+                Math.min(w / Math.max(xMax - xMin, 1),
+                         h / Math.max(yMax - yMin, 1));
+
+    // Then, we correct that scaling ratio considering a margin, which is
+    // basically the size of the biggest node.
+    // This has to be done as a correction since to compare the size of the
+    // biggest node to the X and Y values, we have to first get an
+    // approximation of the scaling ratio.
+    var margin = (self.p.maxNodeSize || sizeMax) / scale;
+    xMax += margin;
+    xMin -= margin;
+    yMax += margin;
+    yMin -= margin;
+
+    scale = self.p.scalingMode == 'outside' ?
+            Math.max(w / Math.max(xMax - xMin, 1),
+                     h / Math.max(yMax - yMin, 1)) :
+            Math.min(w / Math.max(xMax - xMin, 1),
+                     h / Math.max(yMax - yMin, 1));
+
+    // Size homothetic parameters:
+    var a, b;
+    if (!self.p.maxNodeSize && !self.p.minNodeSize) {
+      a = 1;
+      b = 0;
+    }else if (self.p.maxNodeSize == self.p.minNodeSize) {
+      a = 0;
+      b = self.p.maxNodeSize;
+    }else {
+      a = (self.p.maxNodeSize - self.p.minNodeSize) / sizeMax;
+      b = self.p.minNodeSize;
+    }
+
+    var c, d;
+    if (!self.p.maxEdgeSize && !self.p.minEdgeSize) {
+      c = 1;
+      d = 0;
+    }else if (self.p.maxEdgeSize == self.p.minEdgeSize) {
+      c = 0;
+      d = self.p.minEdgeSize;
+    }else {
+      c = (self.p.maxEdgeSize - self.p.minEdgeSize) / weightMax;
+      d = self.p.minEdgeSize;
+    }
+
+    // Rescale the nodes:
+    parseNodes && self.nodes.forEach(function(node) {
+      node['displaySize'] = node['size'] * a + b;
+
+      if (!node['fixed']) {
+        node['displayX'] = (node['x'] - (xMax + xMin) / 2) * scale + w / 2;
+        node['displayY'] = (node['y'] - (yMax + yMin) / 2) * scale + h / 2;
+      }
+    });
+
+    parseEdges && self.edges.forEach(function(edge) {
+      edge['displaySize'] = edge['size'] * c + d;
+    });
+
+    return self;
+  };
+
+  /**
+   * Translates the display values of the nodes and edges relatively to the
+   * scene position and zoom ratio.
+   * @param  {number} sceneX      The x position of the scene.
+   * @param  {number} sceneY      The y position of the scene.
+   * @param  {number} ratio       The zoom ratio of the scene.
+   * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
+   * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
+   * @return {Graph} Returns itself.
+   */
+  function translate(sceneX, sceneY, ratio, parseNodes, parseEdges) {
+    var sizeRatio = Math.pow(ratio, self.p.nodesPowRatio);
+    parseNodes && self.nodes.forEach(function(node) {
+      if (!node['fixed']) {
+        node['displayX'] = node['displayX'] * ratio + sceneX;
+        node['displayY'] = node['displayY'] * ratio + sceneY;
+      }
+
+      node['displaySize'] = node['displaySize'] * sizeRatio;
+    });
+
+    sizeRatio = Math.pow(ratio, self.p.edgesPowRatio);
+    parseEdges && self.edges.forEach(function(edge) {
+      edge['displaySize'] = edge['displaySize'] * sizeRatio;
+    });
+
+    return self;
+  };
+
+  /**
+   * Determines the borders of the graph as it will be drawn. It is used to
+   * avoid the user to drag the graph out of the canvas.
+   */
+  function setBorders() {
+    self.borders = {};
+
+    self.nodes.forEach(function(node) {
+      self.borders.minX = Math.min(
+        self.borders.minX == undefined ?
+          node['displayX'] - node['displaySize'] :
+          self.borders.minX,
+        node['displayX'] - node['displaySize']
+      );
+
+      self.borders.maxX = Math.max(
+        self.borders.maxX == undefined ?
+          node['displayX'] + node['displaySize'] :
+          self.borders.maxX,
+        node['displayX'] + node['displaySize']
+      );
+
+      self.borders.minY = Math.min(
+        self.borders.minY == undefined ?
+          node['displayY'] - node['displaySize'] :
+          self.borders.minY,
+        node['displayY'] - node['displaySize']
+      );
+
+      self.borders.maxY = Math.max(
+        self.borders.maxY == undefined ?
+          node['displayY'] - node['displaySize'] :
+          self.borders.maxY,
+        node['displayY'] - node['displaySize']
+      );
+    });
+  }
+
+  /**
+   * Checks which nodes are under the (mX, mY) points, representing the mouse
+   * position.
+   * @param  {number} mX The mouse X position.
+   * @param  {number} mY The mouse Y position.
+   * @return {Graph} Returns itself.
+   */
+  function checkHover(mX, mY) {
+    var dX, dY, s, over = [], out = [];
+    self.nodes.forEach(function(node) {
+      if (node['hidden']) {
+        node['hover'] = false;
+        return;
+      }
+
+      dX = Math.abs(node['displayX'] - mX);
+      dY = Math.abs(node['displayY'] - mY);
+      s = node['displaySize'];
+
+      var oldH = node['hover'];
+      var newH = dX < s && dY < s && Math.sqrt(dX * dX + dY * dY) < s;
+
+      if (oldH && !newH) {
+        node['hover'] = false;
+        out.push(node.id);
+      } else if (newH && !oldH) {
+        node['hover'] = true;
+        over.push(node.id);
+      }
+    });
+
+    over.length && self.dispatch('overnodes', over);
+    out.length && self.dispatch('outnodes', out);
+
+    return self;
+  };
+
+  /**
+   * Applies a function to a clone of each node (or indicated nodes), and then
+   * tries to apply the modifications made on the clones to the original nodes.
+   * @param  {function(Object)} fun The function to execute.
+   * @param  {?Array.<string>} ids  An Array of node IDs (optional).
+   * @return {Graph} Returns itself.
+   */
+  function iterNodes(fun, ids) {
+    var a = ids ? ids.map(function(id) {
+      return self.nodesIndex[id];
+    }) : self.nodes;
+
+    var aCopies = a.map(cloneNode);
+    aCopies.forEach(fun);
+
+    a.forEach(function(n, i) {
+      checkNode(n, aCopies[i]);
+    });
+
+    return self;
+  };
+
+  /**
+   * Applies a function to a clone of each edge (or indicated edges), and then
+   * tries to apply the modifications made on the clones to the original edges.
+   * @param  {function(Object)} fun The function to execute.
+   * @param  {?Array.<string>} ids  An Array of edge IDs (optional).
+   * @return {Graph} Returns itself.
+   */
+  function iterEdges(fun, ids) {
+    var a = ids ? ids.map(function(id) {
+      return self.edgesIndex[id];
+    }) : self.edges;
+
+    var aCopies = a.map(cloneEdge);
+    aCopies.forEach(fun);
+
+    a.forEach(function(e, i) {
+      checkEdge(e, aCopies[i]);
+    });
+
+    return self;
+  };
+
+  /**
+   * Returns a specific node clone or an array of specified node clones.
+   * @param  {(string|Array.<string>)} ids The ID or an array of node IDs.
+   * @return {(Object|Array.<Object>)} The clone or the array of clones.
+   */
+  function getNodes(ids) {
+    var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
+      return cloneNode(self.nodesIndex[id]);
+    });
+
+    return (ids instanceof Array ? a : a[0]);
+  };
+
+  /**
+   * Returns a specific edge clone or an array of specified edge clones.
+   * @param  {(string|Array.<string>)} ids The ID or an array of edge IDs.
+   * @return {(Object|Array.<Object>)} The clone or the array of clones.
+   */
+  function getEdges(ids) {
+    var a = ((ids instanceof Array ? ids : [ids]) || []).map(function(id) {
+      return cloneEdge(self.edgesIndex[id]);
+    });
+
+    return (ids instanceof Array ? a : a[0]);
+  };
+
+  empty();
+
+  this.addNode = addNode;
+  this.addEdge = addEdge;
+  this.dropNode = dropNode;
+  this.dropEdge = dropEdge;
+
+  this.iterEdges = iterEdges;
+  this.iterNodes = iterNodes;
+
+  this.getEdges = getEdges;
+  this.getNodes = getNodes;
+
+  this.empty = empty;
+  this.rescale = rescale;
+  this.translate = translate;
+  this.setBorders = setBorders;
+  this.checkHover = checkHover;
+}
+
+/**
+ * Sigma is the main class. It represents the core of any instance id sigma.js.
+ * It is private and can be initialized only from inside sigma.js. To see its
+ * public interface, see {@link SigmaPublic}.
+ * It owns its own {@link Graph}, {@link MouseCaptor}, {@link Plotter}
+ * and {@link Monitor}.
+ * @constructor
+ * @extends sigma.classes.Cascade
+ * @extends sigma.classes.EventDispatcher
+ * @param {element} root The DOM root of this instance (a div, for example).
+ * @param {string} id    The ID of this instance.
+ * @this {Sigma}
+ */
+function Sigma(root, id) {
+  sigma.classes.Cascade.call(this);
+  sigma.classes.EventDispatcher.call(this);
+
+  /**
+   * Represents "this", without the well-known scope issue.
+   * @private
+   * @type {Sigma}
+   */
+  var self = this;
+
+  /**
+   * The ID of the instance.
+   * @type {string}
+   */
+  this.id = id.toString();
+
+  /**
+   * The different parameters that define how this instance should work.
+   * @see sigma.classes.Cascade
+   * @type {Object}
+   */
+  this.p = {
+    auto: true,
+    drawNodes: 2,
+    drawEdges: 1,
+    drawLabels: 2,
+    lastNodes: 2,
+    lastEdges: 0,
+    lastLabels: 2,
+    drawHoverNodes: true,
+    drawActiveNodes: true
+  };
+
+  /**
+   * The root DOM element of this instance, containing every other elements.
+   * @type {element}
+   */
+  this.domRoot = root;
+
+  /**
+   * The width of this instance - initially, the root's width.
+   * @type {number}
+   */
+  this.width = this.domRoot.offsetWidth;
+
+  /**
+   * The height of this instance - initially, the root's height.
+   * @type {number}
+   */
+  this.height = this.domRoot.offsetHeight;
+
+  /**
+   * The graph of this instance - initiallyempty.
+   * @type {Graph}
+   */
+  this.graph = new Graph();
+
+  /**
+   * An object referencing every DOM elements used by this instance.
+   * @type {Object}
+   */
+  this.domElements = {};
+
+  initDOM('edges', 'canvas');
+  initDOM('nodes', 'canvas');
+  initDOM('labels', 'canvas');
+  initDOM('hover', 'canvas');
+  initDOM('monitor', 'div');
+  initDOM('mouse', 'canvas');
+
+  /**
+   * The class dedicated to manage the drawing process of the graph of the
+   * different canvas.
+   * @type {Plotter}
+   */
+  this.plotter = new Plotter(
+    this.domElements.nodes.getContext('2d'),
+    this.domElements.edges.getContext('2d'),
+    this.domElements.labels.getContext('2d'),
+    this.domElements.hover.getContext('2d'),
+    this.graph,
+    this.width,
+    this.height
+  );
+
+  /**
+   * The class dedicated to monitor different probes about the running
+   * processes or the data, such as the number of nodes or edges, or how
+   * many times the graph is drawn per second.
+   * @type {Monitor}
+   */
+  this.monitor = new Monitor(
+    this,
+    this.domElements.monitor
+  );
+
+  /**
+   * The class dedicated to manage the different mouse events.
+   * @type {MouseCaptor}
+   */
+  this.mousecaptor = new MouseCaptor(
+    this.domElements.mouse,
+    this.id
+  );
+
+  // Interaction listeners:
+  this.mousecaptor.bind('drag interpolate', function(e) {
+    self.draw(
+      self.p.auto ? 2 : self.p.drawNodes,
+      self.p.auto ? 0 : self.p.drawEdges,
+      self.p.auto ? 2 : self.p.drawLabels,
+      true
+    );
+  }).bind('stopdrag stopinterpolate', function(e) {
+    self.draw(
+      self.p.auto ? 2 : self.p.drawNodes,
+      self.p.auto ? 1 : self.p.drawEdges,
+      self.p.auto ? 2 : self.p.drawLabels,
+      true
+    );
+  }).bind('mousedown mouseup', function(e) {
+    var targeted = self.graph.nodes.filter(function(n) {
+      return !!n['hover'];
+    }).map(function(n) {
+      return n.id;
+    });
+
+    self.dispatch(
+      e['type'] == 'mousedown' ?
+        'downgraph' :
+        'upgraph'
+    );
+
+    if (targeted.length) {
+      self.dispatch(
+        e['type'] == 'mousedown' ?
+          'downnodes' :
+          'upnodes',
+        targeted
+      );
+    }
+  }).bind('move', function() {
+    self.domElements.hover.getContext('2d').clearRect(
+      0,
+      0,
+      self.domElements.hover.width,
+      self.domElements.hover.height
+    );
+
+    drawHover();
+    drawActive();
+  });
+
+  sigma.chronos.bind('startgenerators', function() {
+    if (sigma.chronos.getGeneratorsIDs().some(function(id) {
+      return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
+    })) {
+      self.draw(
+        self.p.auto ? 2 : self.p.drawNodes,
+        self.p.auto ? 0 : self.p.drawEdges,
+        self.p.auto ? 2 : self.p.drawLabels
+      );
+    }
+  }).bind('stopgenerators', function() {
+    self.draw();
+  });
+
+  /**
+   * Resizes the element, and redraws the graph with the last settings.
+   * @param  {?number} w The new width (if undefined, it will use the root
+   *                     width).
+   * @param  {?number} h The new height (if undefined, it will use the root
+   *                     height).
+   * @return {Sigma} Returns itself.
+   */
+  function resize(w, h) {
+    var oldW = self.width, oldH = self.height;
+
+    if (w != undefined && h != undefined) {
+      self.width = w;
+      self.height = h;
+    }else {
+      self.width = self.domRoot.offsetWidth;
+      self.height = self.domRoot.offsetHeight;
+    }
+
+    if (oldW != self.width || oldH != self.height) {
+      for (var k in self.domElements) {
+        self.domElements[k].setAttribute('width', self.width + 'px');
+        self.domElements[k].setAttribute('height', self.height + 'px');
+      }
+
+      self.plotter.resize(self.width, self.height);
+
+      self.draw(
+        self.p.lastNodes,
+        self.p.lastEdges,
+        self.p.lastLabels,
+        true
+      );
+    }
+    return self;
+  };
+
+  /**
+   * Kills every drawing task currently running. Basically, it stops this
+   * instance's drawing process.
+   * @return {Sigma} Returns itself.
+   */
+  function clearSchedule() {
+    sigma.chronos.removeTask(
+      'node_' + self.id, 2
+    ).removeTask(
+      'edge_' + self.id, 2
+    ).removeTask(
+      'label_' + self.id, 2
+    ).stopTasks();
+    return self;
+  };
+
+  /**
+   * Initialize a DOM element, that will be stores by this instance, to make
+   * automatic these elements resizing.
+   * @private
+   * @param  {string} id   The element's ID.
+   * @param  {string} type The element's nodeName (Example : canvas, div, ...).
+   * @return {Sigma} Returns itself.
+   */
+  function initDOM(id, type) {
+    self.domElements[id] = document.createElement(type);
+    self.domElements[id].style.position = 'absolute';
+    self.domElements[id].setAttribute('id', 'sigma_' + id + '_' + self.id);
+    self.domElements[id].setAttribute('class', 'sigma_' + id + '_' + type);
+    self.domElements[id].setAttribute('width', self.width + 'px');
+    self.domElements[id].setAttribute('height', self.height + 'px');
+
+    self.domRoot.appendChild(self.domElements[id]);
+    return self;
+  };
+
+  /**
+   * Starts the graph drawing process. The three first parameters indicate
+   * how the different layers have to be drawn:
+   * . -1: The layer is not drawn, but it is not erased.
+   * . 0:  The layer is not drawn.
+   * . 1:  The layer is drawn progressively.
+   * . 2:  The layer is drawn directly.
+   * @param  {?number} nodes  Determines if and how the nodes must be drawn.
+   * @param  {?number} edges  Determines if and how the edges must be drawn.
+   * @param  {?number} labels Determines if and how the labels must be drawn.
+   * @param  {?boolean} safe  If true, nothing will happen if any generator
+   *                          affiliated to this instance is currently running
+   *                          (an iterative layout, for example).
+   * @return {Sigma} Returns itself.
+   */
+  function draw(nodes, edges, labels, safe) {
+    if (safe && sigma.chronos.getGeneratorsIDs().some(function(id) {
+      return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
+    })) {
+      return self;
+    }
+
+    var n = (nodes == undefined) ? self.p.drawNodes : nodes;
+    var e = (edges == undefined) ? self.p.drawEdges : edges;
+    var l = (labels == undefined) ? self.p.drawLabels : labels;
+
+    var params = {
+      nodes: n,
+      edges: e,
+      labels: l
+    };
+
+    self.p.lastNodes = n;
+    self.p.lastEdges = e;
+    self.p.lastLabels = l;
+
+    // Remove tasks:
+    clearSchedule();
+
+    // Rescale graph:
+    self.graph.rescale(
+      self.width,
+      self.height,
+      n > 0,
+      e > 0
+    ).setBorders();
+
+    self.mousecaptor.checkBorders(
+      self.graph.borders,
+      self.width,
+      self.height
+    );
+
+    self.graph.translate(
+      self.mousecaptor.stageX,
+      self.mousecaptor.stageY,
+      self.mousecaptor.ratio,
+      n > 0,
+      e > 0
+    );
+
+    self.dispatch(
+      'graphscaled'
+    );
+
+    // Clear scene:
+    for (var k in self.domElements) {
+      if (
+        self.domElements[k].nodeName.toLowerCase() == 'canvas' &&
+        (params[k] == undefined || params[k] >= 0)
+      ) {
+        self.domElements[k].getContext('2d').clearRect(
+          0,
+          0,
+          self.domElements[k].width,
+          self.domElements[k].height
+        );
+      }
+    }
+
+    self.plotter.currentEdgeIndex = 0;
+    self.plotter.currentNodeIndex = 0;
+    self.plotter.currentLabelIndex = 0;
+
+    var previous = null;
+    var start = false;
+
+    if (n) {
+      if (n > 1) {
+        while (self.plotter.task_drawNode()) {}
+      }else {
+        sigma.chronos.addTask(
+          self.plotter.task_drawNode,
+          'node_' + self.id,
+          false
+        );
+
+        start = true;
+        previous = 'node_' + self.id;
+      }
+    }
+
+    if (l) {
+      if (l > 1) {
+        while (self.plotter.task_drawLabel()) {}
+      } else {
+        if (previous) {
+          sigma.chronos.queueTask(
+            self.plotter.task_drawLabel,
+            'label_' + self.id,
+            previous
+          );
+        } else {
+          sigma.chronos.addTask(
+            self.plotter.task_drawLabel,
+            'label_' + self.id,
+            false
+          );
+        }
+
+        start = true;
+        previous = 'label_' + self.id;
+      }
+    }
+
+    if (e) {
+      if (e > 1) {
+        while (self.plotter.task_drawEdge()) {}
+      }else {
+        if (previous) {
+          sigma.chronos.queueTask(
+            self.plotter.task_drawEdge,
+            'edge_' + self.id,
+            previous
+          );
+        }else {
+          sigma.chronos.addTask(
+            self.plotter.task_drawEdge,
+            'edge_' + self.id,
+            false
+          );
+        }
+
+        start = true;
+        previous = 'edge_' + self.id;
+      }
+    }
+
+    self.dispatch(
+      'draw'
+    );
+
+    self.refresh();
+
+    start && sigma.chronos.runTasks();
+    return self;
+  };
+
+  /**
+   * Draws the hover and active nodes labels.
+   * @return {Sigma} Returns itself.
+   */
+  function refresh() {
+    self.domElements.hover.getContext('2d').clearRect(
+      0,
+      0,
+      self.domElements.hover.width,
+      self.domElements.hover.height
+    );
+
+    drawHover();
+    drawActive();
+
+    return self;
+  }
+
+  /**
+   * Draws the hover nodes labels. This method is applied directly, and does
+   * not use the pseudo-asynchronous tasks process.
+   * @return {Sigma} Returns itself.
+   */
+  function drawHover() {
+    if (self.p.drawHoverNodes) {
+      self.graph.checkHover(
+        self.mousecaptor.mouseX,
+        self.mousecaptor.mouseY
+      );
+
+      self.graph.nodes.forEach(function(node) {
+        if (node.hover && !node.active) {
+          self.plotter.drawHoverNode(node);
+        }
+      });
+    }
+
+    return self;
+  }
+
+  /**
+   * Draws the active nodes labels. This method is applied directly, and does
+   * not use the pseudo-asynchronous tasks process.
+   * @return {Sigma} Returns itself.
+   */
+  function drawActive() {
+    if (self.p.drawActiveNodes) {
+      self.graph.nodes.forEach(function(node) {
+        if (node.active) {
+          self.plotter.drawActiveNode(node);
+        }
+      });
+    }
+
+    return self;
+  }
+
+  // Apply plugins:
+  for (var i = 0; i < local.plugins.length; i++) {
+    local.plugins[i](this);
+  }
+
+  this.draw = draw;
+  this.resize = resize;
+  this.refresh = refresh;
+  this.drawHover = drawHover;
+  this.drawActive = drawActive;
+  this.clearSchedule = clearSchedule;
+
+  window.addEventListener('resize', function() {
+    self.resize();
+  });
+}
+
+function SigmaPublic(sigmaInstance) {
+  var s = sigmaInstance;
+  var self = this;
+  sigma.classes.EventDispatcher.call(this);
+
+  this._core = sigmaInstance;
+
+  this.kill = function() {
+    // TODO
+  };
+
+  this.getID = function() {
+    return s.id;
+  };
+
+  // Config:
+  this.configProperties = function(a1, a2) {
+    var res = s.config(a1, a2);
+    return res == s ? self : res;
+  };
+
+  this.drawingProperties = function(a1, a2) {
+    var res = s.plotter.config(a1, a2);
+    return res == s.plotter ? self : res;
+  };
+
+  this.mouseProperties = function(a1, a2) {
+    var res = s.mousecaptor.config(a1, a2);
+    return res == s.mousecaptor ? self : res;
+  };
+
+  this.graphProperties = function(a1, a2) {
+    var res = s.graph.config(a1, a2);
+    return res == s.graph ? self : res;
+  };
+
+  this.getMouse = function() {
+    return {
+      mouseX: s.mousecaptor.mouseX,
+      mouseY: s.mousecaptor.mouseY,
+      down: s.mousecaptor.isMouseDown
+    };
+  };
+
+  // Actions:
+  this.position = function(stageX, stageY, ratio) {
+    if (arguments.length == 0) {
+      return {
+        stageX: s.mousecaptor.stageX,
+        stageY: s.mousecaptor.stageY,
+        ratio: s.mousecaptor.ratio
+      };
+    }else {
+      s.mousecaptor.stageX = stageX != undefined ?
+        stageX :
+        s.mousecaptor.stageX;
+      s.mousecaptor.stageY = stageY != undefined ?
+        stageY :
+        s.mousecaptor.stageY;
+      s.mousecaptor.ratio = ratio != undefined ?
+        ratio :
+        s.mousecaptor.ratio;
+
+      return self;
+    }
+  };
+
+  this.goTo = function(stageX, stageY, ratio) {
+    s.mousecaptor.interpolate(stageX, stageY, ratio);
+    return self;
+  };
+
+  this.zoomTo = function(x, y, ratio) {
+    ratio = Math.min(
+              Math.max(s.mousecaptor.config('minRatio'), ratio),
+              s.mousecaptor.config('maxRatio')
+            );
+    if (ratio == s.mousecaptor.ratio) {
+      s.mousecaptor.interpolate(
+        x - s.width / 2 + s.mousecaptor.stageX,
+        y - s.height / 2 + s.mousecaptor.stageY
+      );
+    }else {
+      s.mousecaptor.interpolate(
+        (ratio * x - s.mousecaptor.ratio * s.width/2) /
+        (ratio - s.mousecaptor.ratio),
+        (ratio * y - s.mousecaptor.ratio * s.height/2) /
+        (ratio - s.mousecaptor.ratio),
+        ratio
+      );
+    }
+    return self;
+  };
+
+  this.resize = function(w, h) {
+    s.resize(w, h);
+    return self;
+  };
+
+  this.draw = function(nodes, edges, labels, safe) {
+    s.draw(nodes, edges, labels, safe);
+    return self;
+  };
+
+  this.refresh = function() {
+    s.refresh();
+    return self;
+  };
+
+  // Tasks methods:
+  this.addGenerator = function(id, task, condition) {
+    sigma.chronos.addGenerator(id + '_ext_' + s.id, task, condition);
+    return self;
+  };
+
+  this.removeGenerator = function(id) {
+    sigma.chronos.removeGenerator(id + '_ext_' + s.id);
+    return self;
+  };
+
+  // Graph methods:
+  this.addNode = function(id, params) {
+    s.graph.addNode(id, params);
+    return self;
+  };
+
+  this.addEdge = function(id, source, target, params) {
+    s.graph.addEdge(id, source, target, params);
+    return self;
+  }
+
+  this.dropNode = function(v) {
+    s.graph.dropNode(v);
+    return self;
+  };
+
+  this.dropEdge = function(v) {
+    s.graph.dropEdge(v);
+    return self;
+  };
+
+  this.pushGraph = function(object, safe) {
+    object.nodes && object.nodes.forEach(function(node) {
+      node['id'] && (!safe || !s.graph.nodesIndex[node['id']]) &&
+                    self.addNode(node['id'], node);
+    });
+
+    var isEdgeValid;
+    object.edges && object.edges.forEach(function(edge) {
+      validID = edge['source'] && edge['target'] && edge['id'];
+      validID &&
+        (!safe || !s.graph.edgesIndex[edge['id']]) &&
+        self.addEdge(
+          edge['id'],
+          edge['source'],
+          edge['target'],
+          edge
+        );
+    });
+
+    return self;
+  };
+
+  this.emptyGraph = function() {
+    s.graph.empty();
+    return self;
+  };
+
+  this.getNodesCount = function() {
+    return s.graph.nodes.length;
+  };
+
+  this.getEdgesCount = function() {
+    return s.graph.edges.length;
+  };
+
+  this.iterNodes = function(fun, ids) {
+    s.graph.iterNodes(fun, ids);
+    return self;
+  };
+
+  this.iterEdges = function(fun, ids) {
+    s.graph.iterEdges(fun, ids);
+    return self;
+  };
+
+  this.getNodes = function(ids) {
+    return s.graph.getNodes(ids);
+  };
+
+  this.getEdges = function(ids) {
+    return s.graph.getEdges(ids);
+  };
+
+  // Monitoring
+  this.activateMonitoring = function() {
+    return s.monitor.activate();
+  };
+
+  this.desactivateMonitoring = function() {
+    return s.monitor.desactivate();
+  };
+
+  // Events
+  s.bind('downnodes upnodes downgraph upgraph', function(e) {
+    self.dispatch(e.type, e.content);
+  });
+
+  s.graph.bind('overnodes outnodes', function(e) {
+    self.dispatch(e.type, e.content);
+  });
+}
+
+/**
+ * This class listen to all the different mouse events, to normalize them and
+ * dispatch action events instead (from "startinterpolate" to "isdragging",
+ * etc).
+ * @constructor
+ * @extends sigma.classes.Cascade
+ * @extends sigma.classes.EventDispatcher
+ * @param {element} dom The DOM element to bind the handlers on.
+ * @this {MouseCaptor}
+ */
+function MouseCaptor(dom) {
+  sigma.classes.Cascade.call(this);
+  sigma.classes.EventDispatcher.call(this);
+
+  /**
+   * Represents "this", without the well-known scope issue.
+   * @private
+   * @type {MouseCaptor}
+   */
+  var self = this;
+
+  /**
+   * The DOM element to bind the handlers on.
+   * @type {element}
+   */
+  var dom = dom;
+
+  /**
+   * The different parameters that define how this instance should work.
+   * @see sigma.classes.Cascade
+   * @type {Object}
+   */
+  this.p = {
+    minRatio: 1,
+    maxRatio: 32,
+    marginRatio: 1,
+    zoomDelta: 0.1,
+    dragDelta: 0.3,
+    zoomMultiply: 2,
+    directZooming: false,
+    blockScroll: true,
+    inertia: 1.1,
+    mouseEnabled: true
+  };
+
+  var oldMouseX = 0;
+  var oldMouseY = 0;
+  var startX = 0;
+  var startY = 0;
+
+  var oldStageX = 0;
+  var oldStageY = 0;
+  var oldRatio = 1;
+
+  var targetRatio = 1;
+  var targetStageX = 0;
+  var targetStageY = 0;
+
+  var lastStageX = 0;
+  var lastStageX2 = 0;
+  var lastStageY = 0;
+  var lastStageY2 = 0;
+
+  var progress = 0;
+  var isZooming = false;
+
+  this.stageX = 0;
+  this.stageY = 0;
+  this.ratio = 1;
+
+  this.mouseX = 0;
+  this.mouseY = 0;
+
+  this.isMouseDown = false;
+
+  /**
+   * Extract the local X position from a mouse event.
+   * @private
+   * @param  {event} e A mouse event.
+   * @return {number} The local X value of the mouse.
+   */
+  function getX(e) {
+    return e.offsetX != undefined && e.offsetX ||
+           e.layerX != undefined && e.layerX ||
+           e.clientX != undefined && e.clientX;
+  };
+
+  /**
+   * Extract the local Y position from a mouse event.
+   * @private
+   * @param  {event} e A mouse event.
+   * @return {number} The local Y value of the mouse.
+   */
+  function getY(e) {
+    return e.offsetY != undefined && e.offsetY ||
+           e.layerY != undefined && e.layerY ||
+           e.clientY != undefined && e.clientY;
+  };
+
+  /**
+   * Extract the wheel delta from a mouse event.
+   * @private
+   * @param  {event} e A mouse event.
+   * @return {number} The wheel delta of the mouse.
+   */
+  function getDelta(e) {
+    return e.wheelDelta != undefined && e.wheelDelta ||
+           e.detail != undefined && -e.detail;
+  };
+
+  /**
+   * The handler listening to the 'move' mouse event. It will set the mouseX
+   * and mouseY values as the mouse position values, prevent the default event,
+   * and dispatch a 'move' event.
+   * @private
+   * @param  {event} event A 'move' mouse event.
+   */
+  function moveHandler(event) {
+    oldMouseX = self.mouseX;
+    oldMouseY = self.mouseY;
+
+    self.mouseX = getX(event);
+    self.mouseY = getY(event);
+
+    self.isMouseDown && drag(event);
+    self.dispatch('move');
+
+    if (event.preventDefault) {
+      event.preventDefault();
+    } else {
+      event.returnValue = false;
+    }
+  };
+
+  /**
+   * The handler listening to the 'up' mouse event. It will set the isMouseDown
+   * value as false, dispatch a 'mouseup' event, and trigger stopDrag().
+   * @private
+   * @param  {event} event A 'up' mouse event.
+   */
+  function upHandler(event) {
+    if (self.p.mouseEnabled && self.isMouseDown) {
+      self.isMouseDown = false;
+      self.dispatch('mouseup');
+      stopDrag();
+
+      if (event.preventDefault) {
+        event.preventDefault();
+      } else {
+        event.returnValue = false;
+      }
+    }
+  };
+
+  /**
+   * The handler listening to the 'down' mouse event. It will set the
+   * isMouseDown value as true, dispatch a 'mousedown' event, and trigger
+   * startDrag().
+   * @private
+   * @param  {event} event A 'down' mouse event.
+   */
+  function downHandler(event) {
+    if (self.p.mouseEnabled) {
+      self.isMouseDown = true;
+      oldMouseX = self.mouseX;
+      oldMouseY = self.mouseY;
+
+      self.dispatch('mousedown');
+
+      startDrag();
+
+      if (event.preventDefault) {
+        event.preventDefault();
+      } else {
+        event.returnValue = false;
+      }
+    }
+  };
+
+  /**
+   * The handler listening to the 'wheel' mouse event. It will trigger
+   * {@link startInterpolate} with the event delta as parameter.
+   * @private
+   * @param  {event} event A 'wheel' mouse event.
+   */
+  function wheelHandler(event) {
+    if (self.p.mouseEnabled) {
+      startInterpolate(
+        self.mouseX,
+        self.mouseY,
+        self.ratio * (getDelta(event) > 0 ?
+          self.p.zoomMultiply :
+          1 / self.p.zoomMultiply)
+      );
+
+      if (self.p['blockScroll']) {
+        if (event.preventDefault) {
+          event.preventDefault();
+        } else {
+          event.returnValue = false;
+        }
+      }
+    }
+  };
+
+  /**
+   * Will start computing the scene X and Y, until {@link stopDrag} is
+   * triggered.
+   */
+  function startDrag() {
+    oldStageX = self.stageX;
+    oldStageY = self.stageY;
+    startX = self.mouseX;
+    startY = self.mouseY;
+
+    lastStageX = self.stageX;
+    lastStageX2 = self.stageX;
+    lastStageY = self.stageY;
+    lastStageY2 = self.stageY;
+
+    self.dispatch('startdrag');
+  };
+
+  /**
+   * Stops computing the scene position.
+   */
+  function stopDrag() {
+    if (oldStageX != self.stageX || oldStageY != self.stageY) {
+      startInterpolate(
+        self.stageX + self.p.inertia * (self.stageX - lastStageX2),
+        self.stageY + self.p.inertia * (self.stageY - lastStageY2)
+      );
+    }
+  };
+
+  /**
+   * Computes the position of the scene, relatively to the mouse position, and
+   * dispatches a "drag" event.
+   */
+  function drag() {
+    var newStageX = self.mouseX - startX + oldStageX;
+    var newStageY = self.mouseY - startY + oldStageY;
+
+    if (newStageX != self.stageX || newStageY != self.stageY) {
+      lastStageX2 = lastStageX;
+      lastStageY2 = lastStageY;
+
+      lastStageX = newStageX;
+      lastStageY = newStageY;
+
+      self.stageX = newStageX;
+      self.stageY = newStageY;
+      self.dispatch('drag');
+    }
+  };
+
+  /**
+   * Will start computing the scene zoom ratio, until {@link stopInterpolate} is
+   * triggered.
+   * @param {number} x     The new stage X.
+   * @param {number} y     The new stage Y.
+   * @param {number} ratio The new zoom ratio.
+   */
+  function startInterpolate(x, y, ratio) {
+    if (self.isMouseDown) {
+      return;
+    }
+
+    window.clearInterval(self.interpolationID);
+    isZooming = ratio != undefined;
+
+    oldStageX = self.stageX;
+    targetStageX = x;
+
+    oldStageY = self.stageY;
+    targetStageY = y;
+
+    oldRatio = self.ratio;
+    targetRatio = ratio || self.ratio;
+    targetRatio = Math.min(
+      Math.max(targetRatio, self.p.minRatio),
+      self.p.maxRatio
+    );
+
+    progress =
+      self.p.directZooming ?
+      1 - (isZooming ? self.p.zoomDelta : self.p.dragDelta) :
+      0;
+
+    if (
+      self.ratio != targetRatio ||
+      self.stageX != targetStageX ||
+      self.stageY != targetStageY
+    ) {
+      interpolate();
+      self.interpolationID = window.setInterval(interpolate, 50);
+      self.dispatch('startinterpolate');
+    }
+  };
+
+  /**
+   * Stops the move interpolation.
+   */
+  function stopInterpolate() {
+    var oldRatio = self.ratio;
+
+    if (isZooming) {
+      self.ratio = targetRatio;
+      self.stageX = targetStageX +
+                    (self.stageX - targetStageX) *
+                    self.ratio /
+                    oldRatio;
+      self.stageY = targetStageY +
+                    (self.stageY - targetStageY) *
+                    self.ratio /
+                    oldRatio;
+    }else {
+      self.stageX = targetStageX;
+      self.stageY = targetStageY;
+    }
+
+    self.dispatch('stopinterpolate');
+  };
+
+  /**
+   * Computes the interpolate ratio and the position of the scene, relatively
+   * to the last mouse event delta received, and dispatches a "interpolate"
+   * event.
+   */
+  function interpolate() {
+    progress += (isZooming ? self.p.zoomDelta : self.p.dragDelta);
+    progress = Math.min(progress, 1);
+
+    var k = sigma.easing.quadratic.easeout(progress);
+    var oldRatio = self.ratio;
+
+    self.ratio = oldRatio * (1 - k) + targetRatio * k;
+
+    if (isZooming) {
+      self.stageX = targetStageX +
+                    (self.stageX - targetStageX) *
+                    self.ratio /
+                    oldRatio;
+
+      self.stageY = targetStageY +
+                    (self.stageY - targetStageY) *
+                    self.ratio /
+                    oldRatio;
+    } else {
+      self.stageX = oldStageX * (1 - k) + targetStageX * k;
+      self.stageY = oldStageY * (1 - k) + targetStageY * k;
+    }
+
+    self.dispatch('interpolate');
+    if (progress >= 1) {
+      window.clearInterval(self.interpolationID);
+      stopInterpolate();
+    }
+  };
+
+  /**
+   * Checks that there is always a part of the graph that is displayed, to
+   * avoid the user to drag the graph out of the stage.
+   * @param  {Object} b      An object containing the borders of the graph.
+   * @param  {number} width  The width of the stage.
+   * @param  {number} height The height of the stage.
+   * @return {MouseCaptor} Returns itself.
+   */
+  function checkBorders(b, width, height) {
+    // TODO : Find the good formula
+    /*if (!isNaN(b.minX) && !isNaN(b.maxX)) {
+      self.stageX = Math.min(
+        self.stageX = Math.max(
+          self.stageX,
+          (b.minX - width) * self.ratio +
+            self.p.marginRatio*(b.maxX - b.minX)
+        ),
+        (b.maxX - width) * self.ratio +
+          width -
+          self.p.marginRatio*(b.maxX - b.minX)
+      );
+    }
+
+    if (!isNaN(b.minY) && !isNaN(b.maxY)) {
+      self.stageY = Math.min(
+        self.stageY = Math.max(
+          self.stageY,
+          (b.minY - height) * self.ratio +
+            self.p.marginRatio*(b.maxY - b.minY)
+        ),
+        (b.maxY - height) * self.ratio +
+          height -
+          self.p.marginRatio*(b.maxY - b.minY)
+      );
+    }*/
+
+    return self;
+  };
+
+  // ADD CALLBACKS
+  dom.addEventListener('DOMMouseScroll', wheelHandler, true);
+  dom.addEventListener('mousewheel', wheelHandler, true);
+  dom.addEventListener('mousemove', moveHandler, true);
+  dom.addEventListener('mousedown', downHandler, true);
+  document.addEventListener('mouseup', upHandler, true);
+
+  this.checkBorders = checkBorders;
+  this.interpolate = startInterpolate;
+}
+
+/**
+ * This class draws the graph on the different canvas DOM elements. It just
+ * contains all the different methods to draw the graph, synchronously or
+ * pseudo-asynchronously.
+ * @constructor
+ * @param {CanvasRenderingContext2D} nodesCtx  Context dedicated to draw nodes.
+ * @param {CanvasRenderingContext2D} edgesCtx  Context dedicated to draw edges.
+ * @param {CanvasRenderingContext2D} labelsCtx Context dedicated to draw
+ *                                             labels.
+ * @param {CanvasRenderingContext2D} hoverCtx  Context dedicated to draw hover
+ *                                             nodes labels.
+ * @param {Graph} graph                        A reference to the graph to
+ *                                             draw.
+ * @param {number} w                           The width of the DOM root
+ *                                             element.
+ * @param {number} h                           The width of the DOM root
+ *                                             element.
+ * @extends sigma.classes.Cascade
+ * @this {Plotter}
+ */
+function Plotter(nodesCtx, edgesCtx, labelsCtx, hoverCtx, graph, w, h) {
+  sigma.classes.Cascade.call(this);
+
+  /**
+   * Represents "this", without the well-known scope issue.
+   * @private
+   * @type {Plotter}
+   */
+  var self = this;
+
+  /**
+   * The different parameters that define how this instance should work.
+   * @see sigma.classes.Cascade
+   * @type {Object}
+   */
+  this.p = {
+    // -------
+    // LABELS:
+    // -------
+    //   Label color:
+    //   - 'node'
+    //   - default (then defaultLabelColor
+    //              will be used instead)
+    labelColor: 'default',
+    defaultLabelColor: '#000',
+    //   Label hover background color:
+    //   - 'node'
+    //   - default (then defaultHoverLabelBGColor
+    //              will be used instead)
+    labelHoverBGColor: 'default',
+    defaultHoverLabelBGColor: '#fff',
+    //   Label hover shadow:
+    labelHoverShadow: true,
+    labelHoverShadowColor: '#000',
+    //   Label hover color:
+    //   - 'node'
+    //   - default (then defaultLabelHoverColor
+    //              will be used instead)
+    labelHoverColor: 'default',
+    defaultLabelHoverColor: '#000',
+    //   Label active background color:
+    //   - 'node'
+    //   - default (then defaultActiveLabelBGColor
+    //              will be used instead)
+    labelActiveBGColor: 'default',
+    defaultActiveLabelBGColor: '#fff',
+    //   Label active shadow:
+    labelActiveShadow: true,
+    labelActiveShadowColor: '#000',
+    //   Label active color:
+    //   - 'node'
+    //   - default (then defaultLabelActiveColor
+    //              will be used instead)
+    labelActiveColor: 'default',
+    defaultLabelActiveColor: '#000',
+    //   Label size:
+    //   - 'fixed'
+    //   - 'proportional'
+    //   Label size:
+    //   - 'fixed'
+    //   - 'proportional'
+    labelSize: 'fixed',
+    defaultLabelSize: 12, // for fixed display only
+    labelSizeRatio: 2,    // for proportional display only
+    labelThreshold: 6,
+    font: 'Arial',
+    hoverFont: '',
+    activeFont: '',
+    fontStyle: '',
+    hoverFontStyle: '',
+    activeFontStyle: '',
+    // ------
+    // EDGES:
+    // ------
+    //   Edge color:
+    //   - 'source'
+    //   - 'target'
+    //   - default (then defaultEdgeColor or edge['color']
+    //              will be used instead)
+    edgeColor: 'source',
+    defaultEdgeColor: '#aaa',
+    defaultEdgeType: 'line',
+    // ------
+    // NODES:
+    // ------
+    defaultNodeColor: '#aaa',
+    // HOVER:
+    //   Node hover color:
+    //   - 'node'
+    //   - default (then defaultNodeHoverColor
+    //              will be used instead)
+    nodeHoverColor: 'node',
+    defaultNodeHoverColor: '#fff',
+    // ACTIVE:
+    //   Node active color:
+    //   - 'node'
+    //   - default (then defaultNodeActiveColor
+    //              will be used instead)
+    nodeActiveColor: 'node',
+    defaultNodeActiveColor: '#fff',
+    //   Node border color:
+    //   - 'node'
+    //   - default (then defaultNodeBorderColor
+    //              will be used instead)
+    borderSize: 0,
+    nodeBorderColor: 'node',
+    defaultNodeBorderColor: '#fff',
+    // --------
+    // PROCESS:
+    // --------
+    edgesSpeed: 200,
+    nodesSpeed: 200,
+    labelsSpeed: 200
+  };
+
+  /**
+   * The canvas context dedicated to draw the nodes.
+   * @type {CanvasRenderingContext2D}
+   */
+  var nodesCtx = nodesCtx;
+
+  /**
+   * The canvas context dedicated to draw the edges.
+   * @type {CanvasRenderingContext2D}
+   */
+  var edgesCtx = edgesCtx;
+
+  /**
+   * The canvas context dedicated to draw the labels.
+   * @type {CanvasRenderingContext2D}
+   */
+  var labelsCtx = labelsCtx;
+
+  /**
+   * The canvas context dedicated to draw the hover nodes.
+   * @type {CanvasRenderingContext2D}
+   */
+  var hoverCtx = hoverCtx;
+
+  /**
+   * A reference to the graph to draw.
+   * @type {Graph}
+   */
+  var graph = graph;
+
+  /**
+   * The width of the stage to draw on.
+   * @type {number}
+   */
+  var width = w;
+
+  /**
+   * The height of the stage to draw on.
+   * @type {number}
+   */
+  var height = h;
+
+  /**
+   * The index of the next edge to draw.
+   * @type {number}
+   */
+  this.currentEdgeIndex = 0;
+
+  /**
+   * The index of the next node to draw.
+   * @type {number}
+   */
+  this.currentNodeIndex = 0;
+
+  /**
+   * The index of the next label to draw.
+   * @type {number}
+   */
+  this.currentLabelIndex = 0;
+
+  /**
+   * An atomic function to drawn the N next edges, with N as edgesSpeed.
+   * The counter is {@link this.currentEdgeIndex}.
+   * This function has been designed to work with {@link sigma.chronos}, that
+   * will insert frames at the middle of the calls, to make the edges drawing
+   * process fluid for the user.
+   * @see sigma.chronos
+   * @return {boolean} Returns true if all the edges are drawn and false else.
+   */
+  function task_drawEdge() {
+    var c = graph.edges.length;
+    var s, t, i = 0;
+
+    while (i++< self.p.edgesSpeed && self.currentEdgeIndex < c) {
+      e = graph.edges[self.currentEdgeIndex];
+      s = e['source'];
+      t = e['target'];
+      if (e['hidden'] ||
+          s['hidden'] ||
+          t['hidden'] ||
+          (!self.isOnScreen(s) && !self.isOnScreen(t))) {
+        self.currentEdgeIndex++;
+      }else {
+        drawEdge(graph.edges[self.currentEdgeIndex++]);
+      }
+    }
+
+    return self.currentEdgeIndex < c;
+  };
+
+  /**
+   * An atomic function to drawn the N next nodes, with N as nodesSpeed.
+   * The counter is {@link this.currentEdgeIndex}.
+   * This function has been designed to work with {@link sigma.chronos}, that
+   * will insert frames at the middle of the calls, to make the nodes drawing
+   * process fluid for the user.
+   * @see sigma.chronos
+   * @return {boolean} Returns true if all the nodes are drawn and false else.
+   */
+  function task_drawNode() {
+    var c = graph.nodes.length;
+    var i = 0;
+
+    while (i++< self.p.nodesSpeed && self.currentNodeIndex < c) {
+      if (!self.isOnScreen(graph.nodes[self.currentNodeIndex])) {
+        self.currentNodeIndex++;
+      }else {
+        drawNode(graph.nodes[self.currentNodeIndex++]);
+      }
+    }
+
+    return self.currentNodeIndex < c;
+  };
+
+  /**
+   * An atomic function to drawn the N next labels, with N as labelsSpeed.
+   * The counter is {@link this.currentEdgeIndex}.
+   * This function has been designed to work with {@link sigma.chronos}, that
+   * will insert frames at the middle of the calls, to make the labels drawing
+   * process fluid for the user.
+   * @see sigma.chronos
+   * @return {boolean} Returns true if all the labels are drawn and false else.
+   */
+  function task_drawLabel() {
+    var c = graph.nodes.length;
+    var i = 0;
+
+    while (i++< self.p.labelsSpeed && self.currentLabelIndex < c) {
+      if (!self.isOnScreen(graph.nodes[self.currentLabelIndex])) {
+        self.currentLabelIndex++;
+      }else {
+        drawLabel(graph.nodes[self.currentLabelIndex++]);
+      }
+    }
+
+    return self.currentLabelIndex < c;
+  };
+
+  /**
+   * Draws one node to the corresponding canvas.
+   * @param  {Object} node The node to draw.
+   * @return {Plotter} Returns itself.
+   */
+  function drawNode(node) {
+    var size = Math.round(node['displaySize'] * 10) / 10;
+    var ctx = nodesCtx;
+
+    ctx.fillStyle = node['color'];
+    ctx.beginPath();
+    ctx.arc(node['displayX'],
+            node['displayY'],
+            size,
+            0,
+            Math.PI * 2,
+            true);
+
+    ctx.closePath();
+    ctx.fill();
+
+    node['hover'] && drawHoverNode(node);
+    return self;
+  };
+
+  /**
+   * Draws one edge to the corresponding canvas.
+   * @param  {Object} edge The edge to draw.
+   * @return {Plotter} Returns itself.
+   */
+  function drawEdge(edge) {
+    var x1 = edge['source']['displayX'];
+    var y1 = edge['source']['displayY'];
+    var x2 = edge['target']['displayX'];
+    var y2 = edge['target']['displayY'];
+    var color = edge['color'];
+
+    if (!color) {
+      switch (self.p.edgeColor) {
+        case 'source':
+          color = edge['source']['color'] ||
+                  self.p.defaultNodeColor;
+          break;
+        case 'target':
+          color = edge['target']['color'] ||
+                  self.p.defaultNodeColor;
+          break;
+        default:
+          color = self.p.defaultEdgeColor;
+          break;
+      }
+    }
+
+    var ctx = edgesCtx;
+
+    switch (edge['type'] || self.p.defaultEdgeType) {
+      case 'curve':
+        ctx.strokeStyle = color;
+        ctx.lineWidth = edge['displaySize'] / 3;
+        ctx.beginPath();
+        ctx.moveTo(x1, y1);
+        ctx.quadraticCurveTo((x1 + x2) / 2 + (y2 - y1) / 4,
+                             (y1 + y2) / 2 + (x1 - x2) / 4,
+                             x2,
+                             y2);
+        ctx.stroke();
+        break;
+      case 'line':
+      default:
+        ctx.strokeStyle = color;
+        ctx.lineWidth = edge['displaySize'] / 3;
+        ctx.beginPath();
+        ctx.moveTo(x1, y1);
+        ctx.lineTo(x2, y2);
+
+        ctx.stroke();
+        break;
+    }
+
+    return self;
+  };
+
+  /**
+   * Draws one label to the corresponding canvas.
+   * @param  {Object} node The label to draw.
+   * @return {Plotter} Returns itself.
+   */
+  function drawLabel(node) {
+    var ctx = labelsCtx;
+
+    if (node['displaySize'] >= self.p.labelThreshold || node['forceLabel']) {
+      var fontSize = self.p.labelSize == 'fixed' ?
+                     self.p.defaultLabelSize :
+                     self.p.labelSizeRatio * node['displaySize'];
+
+      ctx.font = self.p.fontStyle + fontSize + 'px ' + self.p.font;
+
+      ctx.fillStyle = self.p.labelColor == 'node' ?
+                      (node['color'] || self.p.defaultNodeColor) :
+                      self.p.defaultLabelColor;
+      ctx.fillText(
+        node['label'],
+        Math.round(node['displayX'] + node['displaySize'] * 1.5),
+        Math.round(node['displayY'] + fontSize / 2 - 3)
+      );
+    }
+
+    return self;
+  };
+
+  /**
+   * Draws one hover node to the corresponding canvas.
+   * @param  {Object} node The hover node to draw.
+   * @return {Plotter} Returns itself.
+   */
+  function drawHoverNode(node) {
+    var ctx = hoverCtx;
+
+    var fontSize = self.p.labelSize == 'fixed' ?
+                   self.p.defaultLabelSize :
+                   self.p.labelSizeRatio * node['displaySize'];
+
+    ctx.font = (self.p.hoverFontStyle || self.p.fontStyle || '') + ' ' +
+               fontSize + 'px ' +
+               (self.p.hoverFont || self.p.font || '');
+
+    ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultHoverLabelBGColor;
+
+    // Label background:
+    ctx.beginPath();
+
+    if (self.p.labelHoverShadow) {
+      ctx.shadowOffsetX = 0;
+      ctx.shadowOffsetY = 0;
+      ctx.shadowBlur = 4;
+      ctx.shadowColor = self.p.labelHoverShadowColor;
+    }
+
+    sigma.tools.drawRoundRect(
+      ctx,
+      Math.round(node['displayX'] - fontSize / 2 - 2),
+      Math.round(node['displayY'] - fontSize / 2 - 2),
+      Math.round(ctx.measureText(node['label']).width +
+        node['displaySize'] * 1.5 +
+        fontSize / 2 + 4),
+      Math.round(fontSize + 4),
+      Math.round(fontSize / 2 + 2),
+      'left'
+    );
+    ctx.closePath();
+    ctx.fill();
+
+    ctx.shadowOffsetX = 0;
+    ctx.shadowOffsetY = 0;
+    ctx.shadowBlur = 0;
+
+    // Node border:
+    ctx.beginPath();
+    ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultNodeBorderColor;
+    ctx.arc(Math.round(node['displayX']),
+            Math.round(node['displayY']),
+            node['displaySize'] + self.p.borderSize,
+            0,
+            Math.PI * 2,
+            true);
+    ctx.closePath();
+    ctx.fill();
+
+    // Node:
+    ctx.beginPath();
+    ctx.fillStyle = self.p.nodeHoverColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultNodeHoverColor;
+    ctx.arc(Math.round(node['displayX']),
+            Math.round(node['displayY']),
+            node['displaySize'],
+            0,
+            Math.PI * 2,
+            true);
+
+    ctx.closePath();
+    ctx.fill();
+
+    // Label:
+    ctx.fillStyle = self.p.labelHoverColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultLabelHoverColor;
+    ctx.fillText(
+      node['label'],
+      Math.round(node['displayX'] + node['displaySize'] * 1.5),
+      Math.round(node['displayY'] + fontSize / 2 - 3)
+    );
+
+    return self;
+  };
+
+  /**
+   * Draws one active node to the corresponding canvas.
+   * @param  {Object} node The active node to draw.
+   * @return {Plotter} Returns itself.
+   */
+  function drawActiveNode(node) {
+    var ctx = hoverCtx;
+
+    if (!isOnScreen(node)) {
+      return self;
+    }
+
+    var fontSize = self.p.labelSize == 'fixed' ?
+                   self.p.defaultLabelSize :
+                   self.p.labelSizeRatio * node['displaySize'];
+
+    ctx.font = (self.p.activeFontStyle || self.p.fontStyle || '') + ' ' +
+               fontSize + 'px ' +
+               (self.p.activeFont || self.p.font || '');
+
+    ctx.fillStyle = self.p.labelHoverBGColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultActiveLabelBGColor;
+
+    // Label background:
+    ctx.beginPath();
+
+    if (self.p.labelActiveShadow) {
+      ctx.shadowOffsetX = 0;
+      ctx.shadowOffsetY = 0;
+      ctx.shadowBlur = 4;
+      ctx.shadowColor = self.p.labelActiveShadowColor;
+    }
+
+    sigma.tools.drawRoundRect(
+      ctx,
+      Math.round(node['displayX'] - fontSize / 2 - 2),
+      Math.round(node['displayY'] - fontSize / 2 - 2),
+      Math.round(ctx.measureText(node['label']).width +
+        node['displaySize'] * 1.5 +
+        fontSize / 2 + 4),
+      Math.round(fontSize + 4),
+      Math.round(fontSize / 2 + 2),
+      'left'
+    );
+    ctx.closePath();
+    ctx.fill();
+
+    ctx.shadowOffsetX = 0;
+    ctx.shadowOffsetY = 0;
+    ctx.shadowBlur = 0;
+
+    // Node border:
+    ctx.beginPath();
+    ctx.fillStyle = self.p.nodeBorderColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultNodeBorderColor;
+    ctx.arc(Math.round(node['displayX']),
+            Math.round(node['displayY']),
+            node['displaySize'] + self.p.borderSize,
+            0,
+            Math.PI * 2,
+            true);
+    ctx.closePath();
+    ctx.fill();
+
+    // Node:
+    ctx.beginPath();
+    ctx.fillStyle = self.p.nodeActiveColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultNodeActiveColor;
+    ctx.arc(Math.round(node['displayX']),
+            Math.round(node['displayY']),
+            node['displaySize'],
+            0,
+            Math.PI * 2,
+            true);
+
+    ctx.closePath();
+    ctx.fill();
+
+    // Label:
+    ctx.fillStyle = self.p.labelActiveColor == 'node' ?
+                    (node['color'] || self.p.defaultNodeColor) :
+                    self.p.defaultLabelActiveColor;
+    ctx.fillText(
+      node['label'],
+      Math.round(node['displayX'] + node['displaySize'] * 1.5),
+      Math.round(node['displayY'] + fontSize / 2 - 3)
+    );
+
+    return self;
+  };
+
+  /**
+   * Determines if a node is on the screen or not. The limits here are
+   * bigger than the actual screen, to avoid seeing labels disappear during
+   * the graph manipulation.
+   * @param  {Object}  node The node to check if it is on or out the screen.
+   * @return {boolean} Returns false if the node is hidden or not on the screen
+   *                   or true else.
+   */
+  function isOnScreen(node) {
+    if (isNaN(node['x']) || isNaN(node['y'])) {
+      throw (new Error('A node\'s coordinate is not a ' +
+                       'number (id: ' + node['id'] + ')')
+      );
+    }
+
+    return !node['hidden'] &&
+           (node['displayX'] + node['displaySize'] > -width / 3) &&
+           (node['displayX'] - node['displaySize'] < width * 4 / 3) &&
+           (node['displayY'] + node['displaySize'] > -height / 3) &&
+           (node['displayY'] - node['displaySize'] < height * 4 / 3);
+  };
+
+  /**
+   * Resizes this instance.
+   * @param  {number} w The new width.
+   * @param  {number} h The new height.
+   * @return {Plotter} Returns itself.
+   */
+  function resize(w, h) {
+    width = w;
+    height = h;
+
+    return self;
+  }
+
+  this.task_drawLabel = task_drawLabel;
+  this.task_drawEdge = task_drawEdge;
+  this.task_drawNode = task_drawNode;
+  this.drawActiveNode = drawActiveNode;
+  this.drawHoverNode = drawHoverNode;
+  this.isOnScreen = isOnScreen;
+  this.resize = resize;
+}
+
+/**
+ * A class to monitor some local / global probes directly on an instance,
+ * inside a div DOM element.
+ * It executes different methods (called "probes") regularly, and displays
+ * the results on the element.
+ * @constructor
+ * @extends sigma.classes.Cascade
+ * @param {Sigma} instance The instance to monitor.
+ * @param {element} dom    The div DOM element to draw write on.
+ * @this {Monitor}
+ */
+function Monitor(instance, dom) {
+  sigma.classes.Cascade.call(this);
+
+  /**
+   * Represents "this", without the well-known scope issue.
+   * @private
+   * @type {Monitor}
+   */
+  var self = this;
+
+  /**
+   * {@link Sigma} instance owning this Monitor instance.
+   * @type {Sigma}
+   */
+  this.instance = instance;
+
+  /**
+   * Determines if the monitoring is activated or not.
+   * @type {Boolean}
+   */
+  this.monitoring = false;
+
+  /**
+   * The different parameters that define how this instance should work. It
+   * also contains the different probes.
+   * @see sigma.classes.Cascade
+   * @type {Object}
+   */
+  this.p = {
+    fps: 40,
+    dom: dom,
+    globalProbes: {
+      'Time (ms)': sigma.chronos.getExecutionTime,
+      'Queue': sigma.chronos.getQueuedTasksCount,
+      'Tasks': sigma.chronos.getTasksCount,
+      'FPS': sigma.chronos.getFPS
+    },
+    localProbes: {
+      'Nodes count': function() { return self.instance.graph.nodes.length; },
+      'Edges count': function() { return self.instance.graph.edges.length; }
+    }
+  };
+
+  /**
+   * Activates the monitoring: Some texts describing some values about sigma.js
+   * or the owning {@link Sigma} instance will appear over the graph, but
+   * beneath the mouse sensible DOM element.
+   * @return {Monitor} Returns itself.
+   */
+  function activate() {
+    if (!self.monitoring) {
+      self.monitoring = window.setInterval(routine, 1000 / self.p.fps);
+    }
+
+    return self;
+  }
+
+  /**
+   * Desactivates the monitoring: Will disappear, and stop computing the
+   * different probes.
+   * @return {Monitor} Returns itself.
+   */
+  function desactivate() {
+    if (self.monitoring) {
+      window.clearInterval(self.monitoring);
+      self.monitoring = null;
+
+      self.p.dom.innerHTML = '';
+    }
+
+    return self;
+  }
+
+  /**
+   * The private method dedicated to compute the different values to observe.
+   * @private
+   * @return {Monitor} Returns itself.
+   */
+  function routine() {
+    var s = '';
+
+    s += '<p>GLOBAL :</p>';
+    for (var k in self.p.globalProbes) {
+      s += '<p>' + k + ' : ' + self.p.globalProbes[k]() + '</p>';
+    }
+
+    s += '<br><p>LOCAL :</p>';
+    for (var k in self.p.localProbes) {
+      s += '<p>' + k + ' : ' + self.p.localProbes[k]() + '</p>';
+    }
+
+    self.p.dom.innerHTML = s;
+
+    return self;
+  }
+
+  this.activate = activate;
+  this.desactivate = desactivate;
+}
+
+/**
+ * Add a function to the prototype of SigmaPublic, but with access to the
+ * Sigma class properties.
+ * @param {string} pluginName        [description].
+ * @param {function} caller          [description].
+ * @param {function(Sigma)} launcher [description].
+ */
+sigma.addPlugin = function(pluginName, caller, launcher) {
+  SigmaPublic.prototype[pluginName] = caller;
+  local.plugins.push(launcher);
+};
+sigma.tools.drawRoundRect = function(ctx, x, y, w, h, ellipse, corners) {
+  var e = ellipse ? ellipse : 0;
+  var c = corners ? corners : [];
+  c = ((typeof c) == 'string') ? c.split(' ') : c;
+
+  var tl = e && (c.indexOf('topleft') >= 0 ||
+                 c.indexOf('top') >= 0 ||
+                 c.indexOf('left') >= 0);
+  var tr = e && (c.indexOf('topright') >= 0 ||
+                 c.indexOf('top') >= 0 ||
+                 c.indexOf('right') >= 0);
+  var bl = e && (c.indexOf('bottomleft') >= 0 ||
+                 c.indexOf('bottom') >= 0 ||
+                 c.indexOf('left') >= 0);
+  var br = e && (c.indexOf('bottomright') >= 0 ||
+                 c.indexOf('bottom') >= 0 ||
+                 c.indexOf('right') >= 0);
+
+  ctx.moveTo(x, y + e);
+
+  if (tl) {
+    ctx.arcTo(x, y, x + e, y, e);
+  }else {
+    ctx.lineTo(x, y);
+  }
+
+  if (tr) {
+    ctx.lineTo(x + w - e, y);
+    ctx.arcTo(x + w, y, x + w, y + e, e);
+  }else {
+    ctx.lineTo(x + w, y);
+  }
+
+  if (br) {
+    ctx.lineTo(x + w, y + h - e);
+    ctx.arcTo(x + w, y + h, x + w - e, y + h, e);
+  }else {
+    ctx.lineTo(x + w, y + h);
+  }
+
+  if (bl) {
+    ctx.lineTo(x + e, y + h);
+    ctx.arcTo(x, y + h, x, y + h - e, e);
+  }else {
+    ctx.lineTo(x, y + h);
+  }
+
+  ctx.lineTo(x, y + e);
+};
+
+sigma.tools.getRGB = function(s, asArray) {
+  s = s.toString();
+  var res = {
+    'r': 0,
+    'g': 0,
+    'b': 0
+  };
+
+  if (s.length >= 3) {
+    if (s.charAt(0) == '#') {
+      var l = s.length - 1;
+      if (l == 6) {
+        res = {
+          'r': parseInt(s.charAt(1) + s.charAt(2), 16),
+          'g': parseInt(s.charAt(3) + s.charAt(4), 16),
+          'b': parseInt(s.charAt(5) + s.charAt(5), 16)
+        };
+      }else if (l == 3) {
+        res = {
+          'r': parseInt(s.charAt(1) + s.charAt(1), 16),
+          'g': parseInt(s.charAt(2) + s.charAt(2), 16),
+          'b': parseInt(s.charAt(3) + s.charAt(3), 16)
+        };
+      }
+    }
+  }
+
+  if (asArray) {
+    res = [
+      res['r'],
+      res['g'],
+      res['b']
+    ];
+  }
+
+  return res;
+};
+
+sigma.tools.rgbToHex = function(R, G, B) {
+  return sigma.tools.toHex(R) + sigma.tools.toHex(G) + sigma.tools.toHex(B);
+};
+
+sigma.tools.toHex = function(n) {
+  n = parseInt(n, 10);
+
+  if (isNaN(n)) {
+    return '00';
+  }
+  n = Math.max(0, Math.min(n, 255));
+  return '0123456789ABCDEF'.charAt((n - n % 16) / 16) +
+         '0123456789ABCDEF'.charAt(n % 16);
+};
+
+sigma.easing = {
+  linear: {},
+  quadratic: {}
+};
+
+sigma.easing.linear.easenone = function(k) {
+  return k;
+};
+
+sigma.easing.quadratic.easein = function(k) {
+  return k * k;
+};
+
+sigma.easing.quadratic.easeout = function(k) {
+  return - k * (k - 2);
+};
+
+sigma.easing.quadratic.easeinout = function(k) {
+  if ((k *= 2) < 1) return 0.5 * k * k;
+  return - 0.5 * (--k * (k - 2) - 1);
+};
+
+/**
+ * sigma.chronos manages frames insertion to simulate asynchronous computing.
+ * It has been designed to make possible to execute heavy computing tasks
+ * for the browser, without freezing it.
+ * @constructor
+ * @extends sigma.classes.Cascade
+ * @extends sigma.classes.EventDispatcher
+ * @this {sigma.chronos}
+ */
+sigma.chronos = new (function() {
+  sigma.classes.EventDispatcher.call(this);
+
+  /**
+   * Represents "this", without the well-known scope issue.
+   * @private
+   * @type {sigma.chronos}
+   */
+  var self = this;
+
+  /**
+   * Indicates whether any task is actively running or not.
+   * @private
+   * @type {boolean}
+   */
+  var isRunning = false;
+
+  /**
+   * Indicates the FPS "goal", that will define the theoretical
+   * frame length.
+   * @private
+   * @type {number}
+   */
+  var fpsReq = 80;
+
+  /**
+   * Stores the last computed FPS value (FPS is computed only when any
+   * task is running).
+   * @private
+   * @type {number}
+   */
+  var lastFPS = 0;
+
+  /**
+   * The number of frames inserted since the last start.
+   * @private
+   * @type {number}
+   */
+  var framesCount = 0;
+
+  /**
+   * The theoretical frame time.
+   * @private
+   * @type {number}
+   */
+  var frameTime = 1000 / fpsReq;
+
+  /**
+   * The theoretical frame length, minus the last measured delay.
+   * @private
+   * @type {number}
+   */
+  var correctedFrameTime = frameTime;
+
+  /**
+   * The measured length of the last frame.
+   * @private
+   * @type {number}
+   */
+  var effectiveTime = 0;
+
+  /**
+   * The time passed since the last runTasks action.
+   * @private
+   * @type {number}
+   */
+  var currentTime = 0;
+
+  /**
+   * The time when the last frame was inserted.
+   * @private
+   * @type {number}
+   */
+  var startTime = 0;
+
+  /**
+   * The difference between the theoretical frame length and the
+   * last measured frame length.
+   * @private
+   * @type {number}
+   */
+  var delay = 0;
+
+  /**
+   * The container of all active generators.
+   * @private
+   * @type {Object.<string, Object>}
+   */
+  var generators = {};
+
+  /**
+   * The array of all the referenced and active tasks.
+   * @private
+   * @type {Array.<Object>}
+   */
+  var tasks = [];
+
+  /**
+   * The array of all the referenced and queued tasks.
+   * @private
+   * @type {Array.<Object>}
+   */
+  var queuedTasks = [];
+
+  /**
+   * The index of the next task to execute.
+   * @private
+   * @type {number}
+   */
+  var taskIndex = 0;
+
+
+  /**
+   * Inserts a frame before executing the callback.
+   * @param  {function()} callback The callback to execute after having
+   *                               inserted the frame.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function insertFrame(callback) {
+    window.setTimeout(callback, 0);
+    return self;
+  }
+
+  /**
+   * The local method that executes routine, and inserts frames when needed.
+   * It dispatches a "frameinserted" event after having inserted any frame,
+   * and an "insertframe" event before.
+   * @private
+   */
+  function frameInserter() {
+    self.dispatch('frameinserted');
+    while (isRunning && tasks.length && routine()) {}
+
+    if (!isRunning || !tasks.length) {
+      stopTasks();
+    } else {
+      startTime = (new Date()).getTime();
+      framesCount++;
+      delay = effectiveTime - frameTime;
+      correctedFrameTime = frameTime - delay;
+
+      self.dispatch('insertframe');
+      insertFrame(frameInserter);
+    }
+  };
+
+  /**
+   * The local method that executes the tasks, and compares the current frame
+   * length to the ideal frame length.
+   * @private
+   * @return {boolean} Returns false if the current frame should be ended,
+   *                   and true else.
+   */
+  function routine() {
+    taskIndex = taskIndex % tasks.length;
+
+    if (!tasks[taskIndex].task()) {
+      var n = tasks[taskIndex].taskName;
+
+      queuedTasks = queuedTasks.filter(function(e) {
+        (e.taskParent == n) && tasks.push({
+          taskName: e.taskName,
+          task: e.task
+        });
+        return e.taskParent != n;
+      });
+
+      self.dispatch('killed', tasks.splice(taskIndex--, 1)[0]);
+    }
+
+    taskIndex++;
+    effectiveTime = (new Date()).getTime() - startTime;
+    return effectiveTime <= correctedFrameTime;
+  };
+
+  /**
+   * Starts tasks execution.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function runTasks() {
+    isRunning = true;
+    taskIndex = 0;
+    framesCount = 0;
+
+    startTime = (new Date()).getTime();
+    currentTime = startTime;
+
+    self.dispatch('start');
+    self.dispatch('insertframe');
+    insertFrame(frameInserter);
+    return self;
+  };
+
+  /**
+   * Stops tasks execution, and dispatch a "stop" event.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function stopTasks() {
+    self.dispatch('stop');
+    isRunning = false;
+    return self;
+  };
+
+  /**
+   * A task is a function that will be executed continuously while it returns
+   * true. As soon as it return false, the task will be removed.
+   * If several tasks are present, they will be executed in parallele.
+   * This method will add the task to this execution process.
+   * @param {function(): boolean} task     The task to add.
+   * @param {string} name                  The name of the worker, used for
+   *                                       managing the different tasks.
+   * @param {boolean} autostart            If true, sigma.chronos will start
+   *                                       automatically if it is not working
+   *                                       yet.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function addTask(task, name, autostart) {
+    if (typeof task != 'function') {
+      throw new Error('Task "' + name + '" is not a function');
+    }
+
+    tasks.push({
+      taskName: name,
+      task: task
+    });
+
+    isRunning = !!(isRunning || (autostart && runTasks()) || true);
+    return self;
+  };
+
+  /**
+   * Will add a task that will be start to be executed as soon as a task
+   * named as the parent will be removed.
+   * @param {function(): boolean} task     The task to add.
+   * @param {string} name                  The name of the worker, used for
+   *                                       managing the different tasks.
+   * @param {string} parent                The name of the parent task.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function queueTask(task, name, parent) {
+    if (typeof task != 'function') {
+      throw new Error('Task "' + name + '" is not a function');
+    }
+
+    if (!tasks.concat(queuedTasks).some(function(e) {
+      return e.taskName == parent;
+    })) {
+      throw new Error(
+        'Parent task "' + parent + '" of "' + name + '" is not attached.'
+      );
+    }
+
+    queuedTasks.push({
+      taskParent: parent,
+      taskName: name,
+      task: task
+    });
+
+    return self;
+  };
+
+  /**
+   * Removes a task.
+   * @param  {string} v           If v is undefined, then every tasks will
+   *                              be removed. If not, each task named v will
+   *                              be removed.
+   * @param  {number} queueStatus Determines the queued tasks behaviour. If 0,
+   *                              then nothing will happen. If 1, the tasks
+   *                              queued to any removed task will be triggered.
+   *                              If 2, the tasks queued to any removed task
+   *                              will be removed as well.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function removeTask(v, queueStatus) {
+    if (v == undefined) {
+      tasks = [];
+      if (queueStatus == 1) {
+        queuedTasks = [];
+      }else if (queueStatus == 2) {
+        tasks = queuedTasks;
+        queuedTasks = [];
+      }
+      stopTasks();
+    } else {
+      var n = (typeof v == 'string') ? v : '';
+      tasks = tasks.filter(function(e) {
+        if ((typeof v == 'string') ? e.taskName == v : e.task == v) {
+          n = e.taskName;
+          return false;
+        }
+        return true;
+      });
+
+      if (queueStatus > 0) {
+        queuedTasks = queuedTasks.filter(function(e) {
+          if (queueStatus == 1 && e.taskParent == n) {
+            tasks.push(e);
+          }
+          return e.taskParent != n;
+        });
+      }
+    }
+
+    isRunning = !!(!tasks.length || (stopTasks() && false));
+    return self;
+  };
+
+  /**
+   * A generator is a pair task/condition. The task will be executed
+   * while it returns true.
+   * When it returns false, the condition will be tested. If
+   * the condition returns true, the task will be executed
+   * again at the next process iteration. If not, the generator
+   * is removed.
+   * If several generators are present, they will be executed one
+   * by one: When the first stops, the second will start, etc. When
+   * they are all ended, then the conditions will be tested to know
+   * which generators have to be started again.
+   * @param {string} id                     The generators ID.
+   * @param {function(): boolean} task      The generator's task.
+   * @param {function(): boolean} condition The generator's condition.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function addGenerator(id, task, condition) {
+    if (generators[id] != undefined) {
+      return self;
+    }
+
+    generators[id] = {
+      task: task,
+      condition: condition
+    };
+
+    getGeneratorsCount(true) == 0 && startGenerators();
+    return self;
+  };
+
+  /**
+   * Removes a generator. It means that the task will continue being eecuted
+   * until it returns false, but then the
+   * condition will not be tested.
+   * @param  {string} id The generator's ID.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function removeGenerator(id) {
+    if (generators[id]) {
+      generators[id].on = false;
+      generators[id].del = true;
+    }
+    return self;
+  };
+
+  /**
+   * Returns the number of generators.
+   * @private
+   * @param  {boolean} running If true, returns the number of active
+   *                          generators instead.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function getGeneratorsCount(running) {
+    return running ?
+      Object.keys(generators).filter(function(id) {
+        return !!generators[id].on;
+      }).length :
+      Object.keys(generators).length;
+  };
+
+  /**
+   * Returns the array of the generators IDs.
+   * @return {array.<string>} The array of IDs.
+   */
+  function getGeneratorsIDs() {
+    return Object.keys(generators);
+  }
+
+  /**
+   * startGenerators is the method that manages which generator
+   * is the next to start when another one stops. It will dispatch
+   * a "stopgenerators" event if there is no more generator to start,
+   * and a "startgenerators" event else.
+   * @return {sigma.chronos} Returns itself.
+   */
+  function startGenerators() {
+    if (!Object.keys(generators).length) {
+      self.dispatch('stopgenerators');
+    }else {
+      self.dispatch('startgenerators');
+
+      self.unbind('killed', onTaskEnded);
+      insertFrame(function() {
+        for (var k in generators) {
+          generators[k].on = true;
+          addTask(
+            generators[k].task,
+            k,
+            false
+          );
+        }
+      });
+
+      self.bind('killed', onTaskEnded).runTasks();
+    }
+
+    return self;
+  };
+
+  /**
+   * A callback triggered everytime the task of a generator stops, that will
+   * test the related generator's condition, and see if there is still any
+   * generator to start.
+   * @private
+   * @param  {Object} e The sigma.chronos "killed" event.
+   */
+  function onTaskEnded(e) {
+    if (generators[e['content'].taskName] != undefined) {
+      if (generators[e['content'].taskName].del ||
+          !generators[e['content'].taskName].condition()) {
+        delete generators[e['content'].taskName];
+      }else {
+        generators[e['content'].taskName].on = false;
+      }
+
+      if (getGeneratorsCount(true) == 0) {
+        startGenerators();
+      }
+    }
+  };
+
+  /**
+   * Either set or returns the fpsReq property. This property determines
+   * the number of frames that should be inserted per second.
+   * @param  {?number} v The frequency asked.
+   * @return {(Chronos|number)} Returns the frequency if v is undefined, and
+   *                          itself else.
+   */
+  function frequency(v) {
+    if (v != undefined) {
+      fpsReq = Math.abs(1 * v);
+      frameTime = 1000 / fpsReq;
+      framesCount = 0;
+      return self;
+    } else {
+      return fpsReq;
+    }
+  };
+
+  /**
+   * Returns the actual average number of frames that are inserted per
+   * second.
+   * @return {number} The actual average FPS.
+   */
+  function getFPS() {
+    if (isRunning) {
+      lastFPS =
+        Math.round(
+          framesCount /
+          ((new Date()).getTime() - currentTime) *
+          10000
+        ) / 10;
+    }
+
+    return lastFPS;
+  };
+
+  /**
+   * Returns the number of tasks.
+   * @return {number} The number of tasks.
+   */
+  function getTasksCount() {
+    return tasks.length;
+  }
+
+  /**
+   * Returns the number of queued tasks.
+   * @return {number} The number of queued tasks.
+   */
+  function getQueuedTasksCount() {
+    return queuedTasks.length;
+  }
+
+  /**
+   * Returns how long sigma.chronos has active tasks running
+   * without interuption for, in ms.
+   * @return {number} The time chronos is running without interuption for.
+   */
+  function getExecutionTime() {
+    return startTime - currentTime;
+  }
+
+  this.frequency = frequency;
+
+  this.runTasks = runTasks;
+  this.stopTasks = stopTasks;
+  this.insertFrame = insertFrame;
+
+  this.addTask = addTask;
+  this.queueTask = queueTask;
+  this.removeTask = removeTask;
+
+  this.addGenerator = addGenerator;
+  this.removeGenerator = removeGenerator;
+  this.startGenerators = startGenerators;
+  this.getGeneratorsIDs = getGeneratorsIDs;
+
+  this.getFPS = getFPS;
+  this.getTasksCount = getTasksCount;
+  this.getQueuedTasksCount = getQueuedTasksCount;
+  this.getExecutionTime = getExecutionTime;
+
+  return this;
+})();
+
+sigma.debugMode = 0;
+
+sigma.log = function() {
+  if (sigma.debugMode == 1) {
+    for (var k in arguments) {
+      console.log(arguments[k]);
+    }
+  }else if (sigma.debugMode > 1) {
+    for (var k in arguments) {
+      throw new Error(arguments[k]);
+    }
+  }
+
+  return sigma;
+};
+
+sigma.publicPrototype = SigmaPublic.prototype;
+})();
+