weird cloudflare "javascripts" folder bug
[disclosr.git] / js / sigma.min.js
blob:a/js/sigma.min.js -> blob:b/js/sigma.min.js
--- a/js/sigma.min.js
+++ b/js/sigma.min.js
@@ -1,1 +1,3552 @@
-
+// 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;
+      }
+    })();
+  }
+})();
+
+/**
+ * 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;
+    }
+  };
+};
+
+/**
+ * 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;
+};
+
+(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];
+};
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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
+      );