text: var Sigma =
  /******/ (function(modules) { // webpackBootstrap
  /******/ 	// The module cache
  /******/ 	var installedModules = {};
  /******/
  /******/ 	// The require function
  /******/ 	function __webpack_require__(moduleId) {
  /******/
  /******/ 		// Check if module is in cache
  /******/ 		if(installedModules[moduleId]) {
  /******/ 			return installedModules[moduleId].exports;
  /******/ 		}
  /******/ 		// Create a new module (and put it into the cache)
  /******/ 		var module = installedModules[moduleId] = {
  /******/ 			i: moduleId,
  /******/ 			l: false,
  /******/ 			exports: {}
  /******/ 		};
  /******/
  /******/ 		// Execute the module function
  /******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  /******/
  /******/ 		// Flag the module as loaded
  /******/ 		module.l = true;
  /******/
  /******/ 		// Return the exports of the module
  /******/ 		return module.exports;
  /******/ 	}
  /******/
  /******/
  /******/ 	// expose the modules object (__webpack_modules__)
  /******/ 	__webpack_require__.m = modules;
  /******/
  /******/ 	// expose the module cache
  /******/ 	__webpack_require__.c = installedModules;
  /******/
  /******/ 	// identity function for calling harmony imports with the correct context
  /******/ 	__webpack_require__.i = function(value) { return value; };
  /******/
  /******/ 	// define getter function for harmony exports
  /******/ 	__webpack_require__.d = function(exports, name, getter) {
  /******/ 		if(!__webpack_require__.o(exports, name)) {
  /******/ 			Object.defineProperty(exports, name, {
  /******/ 				configurable: false,
  /******/ 				enumerable: true,
  /******/ 				get: getter
  /******/ 			});
  /******/ 		}
  /******/ 	};
  /******/
  /******/ 	// getDefaultExport function for compatibility with non-harmony modules
  /******/ 	__webpack_require__.n = function(module) {
  /******/ 		var getter = module && module.__esModule ?
  /******/ 			function getDefault() { return module['default']; } :
  /******/ 			function getModuleExports() { return module; };
  /******/ 		__webpack_require__.d(getter, 'a', getter);
  /******/ 		return getter;
  /******/ 	};
  /******/
  /******/ 	// Object.prototype.hasOwnProperty.call
  /******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  /******/
  /******/ 	// __webpack_public_path__
  /******/ 	__webpack_require__.p = "";
  /******/
  /******/ 	// Load entry module and return exports
  /******/ 	return __webpack_require__(__webpack_require__.s = 135);
  /******/ })
  /************************************************************************/
  /******/ ({
  
  /***/ 135:
  /***/ (function(module, exports, __webpack_require__) {
  
  __webpack_require__(17);
  module.exports = __webpack_require__(18);
  
  
  /***/ }),
  
  /***/ 17:
  /***/ (function(module, exports) {
  
  /*** IMPORTS FROM imports-loader ***/
  (function() {
  
  ;(function(undefined) {
    'use strict';
  
    if (typeof sigma === 'undefined')
      throw new Error('sigma is not declared');
  
    // Initialize package:
    sigma.utils.pkg('sigma.layouts');
  
    /**
     * Sigma ForceLink Supervisor
     * ===============================
     *
     * Author: Guillaume Plique (Yomguithereal)
     * Extensions author: Sébastien Heymann @ Linkurious
     * Version: 0.1
     */
    var _root = this;
  
    /**
     * Feature detection
     * ------------------
     */
    var webWorkers = 'Worker' in _root;
  
    /**
     * Event emitter Object
     * ------------------
     */
    var eventEmitter = {};
    sigma.classes.dispatcher.extend(eventEmitter);
  
    /**
     * Supervisor Object
     * ------------------
     */
    function Supervisor(sigInst, options) {
      // Window URL Polyfill
      _root.URL = _root.URL || _root.webkitURL;
  
      options = options || {};
  
      // Properties
      this.sigInst = sigInst;
      this.graph = this.sigInst.graph;
      this.ppn = 10;
      this.ppe = 3;
      this.config = {};
      this.worker = null;
      this.shouldUseWorker = null;
      this.workerUrl = null;
      this.runOnBackground = null;
      this.easing = null;
      this.randomize = null;
  
      this.configure(options);
  
      // State
      this.started = false;
      this.running = false;
  
      this.initWorker();
    }
  
    Supervisor.prototype.makeBlob = function(workerFn) {
      var blob;
  
      try {
        blob = new Blob([workerFn], {type: 'application/javascript'});
      }
      catch (e) {
        _root.BlobBuilder = _root.BlobBuilder ||
                            _root.WebKitBlobBuilder ||
                            _root.MozBlobBuilder;
  
        blob = new BlobBuilder();
        blob.append(workerFn);
        blob = blob.getBlob();
      }
  
      return blob;
    };
  
    Supervisor.prototype.graphToByteArrays = function() {
      var nodes = this.graph.nodes(),
          edges = this.graph.edges(),
          nbytes = nodes.length * this.ppn,
          ebytes = edges.length * this.ppe,
          nIndex = {},
          i,
          j,
          l;
  
      // Allocating Byte arrays with correct nb of bytes
      this.nodesByteArray = new Float32Array(nbytes);
      this.edgesByteArray = new Float32Array(ebytes);
  
      // Iterate through nodes
      for (i = j = 0, l = nodes.length; i < l; i++) {
  
        // Populating index
        nIndex[nodes[i].id] = j;
  
        // Populating byte array
        this.nodesByteArray[j] = this.randomize(nodes[i].x);
        this.nodesByteArray[j + 1] = this.randomize(nodes[i].y);
        this.nodesByteArray[j + 2] = 0;
        this.nodesByteArray[j + 3] = 0;
        this.nodesByteArray[j + 4] = 0;
        this.nodesByteArray[j + 5] = 0;
        this.nodesByteArray[j + 6] = 1 + this.graph.degree(nodes[i].id);
        this.nodesByteArray[j + 7] = 1;
        this.nodesByteArray[j + 8] = nodes[i].size;
        this.nodesByteArray[j + 9] = nodes[i].fixed || 0;
        j += this.ppn;
      }
  
      // Iterate through edges
      for (i = j = 0, l = edges.length; i < l; i++) {
        this.edgesByteArray[j] = nIndex[edges[i].source];
        this.edgesByteArray[j + 1] = nIndex[edges[i].target];
        this.edgesByteArray[j + 2] = edges[i].weight || 0;
        j += this.ppe;
      }
    };
  
    // TODO: make a better send function
    Supervisor.prototype.applyLayoutChanges = function(prefixed) {
      var nodes = this.graph.nodes(),
          j = 0,
          realIndex;
  
      // Moving nodes
      for (var i = 0, l = this.nodesByteArray.length; i < l; i += this.ppn) {
        if (!nodes[j].fixed) {
          if (prefixed) {
            nodes[j].fa2_x = this.nodesByteArray[i];
            nodes[j].fa2_y = this.nodesByteArray[i + 1];
  
          } else {
            nodes[j].x = this.nodesByteArray[i];
            nodes[j].y = this.nodesByteArray[i + 1];
          }
        }
        j++;
      }
    };
  
    Supervisor.prototype.sendByteArrayToWorker = function(action) {
      var content = {
        action: action || 'loop',
        nodes: this.nodesByteArray.buffer
      };
  
      var buffers = [this.nodesByteArray.buffer];
  
      if (action === 'start') {
        content.config = this.config || {};
        content.edges = this.edgesByteArray.buffer;
        buffers.push(this.edgesByteArray.buffer);
      }
  
      if (this.shouldUseWorker)
        this.worker.postMessage(content, buffers);
      else
        _root.postMessage(content, '*');
    };
  
    Supervisor.prototype.start = function() {
      if (this.running)
        return;
  
      this.running = true;
  
      if (!this.started) {
        // Sending init message to worker
        this.sendByteArrayToWorker('start');
        this.started = true;
        eventEmitter.dispatchEvent('start');
      }
      else {
        this.sendByteArrayToWorker();
      }
    };
  
    Supervisor.prototype.stop = function() {
      if (!this.running)
        return;
  
      this.running = false;
      eventEmitter.dispatchEvent('stop');
    };
  
    Supervisor.prototype.initWorker = function() {
      var _this = this,
          workerFn = sigma.layouts.getForceLinkWorker();
  
      // Web worker or classic DOM events?
      if (this.shouldUseWorker) {
        if (!this.workerUrl) {
          var blob = this.makeBlob(workerFn);
          this.worker = new Worker(URL.createObjectURL(blob));
        }
        else {
          this.worker = new Worker(this.workerUrl);
        }
  
        // Post Message Polyfill
        this.worker.postMessage =
          this.worker.webkitPostMessage || this.worker.postMessage;
      }
      else {
  
        // TODO: do we crush?
        eval(workerFn);
      }
  
      // Worker message receiver
      this.msgName = (this.worker) ? 'message' : 'newCoords';
      this.listener = function(e) {
  
        // Retrieving data
        _this.nodesByteArray = new Float32Array(e.data.nodes);
  
        // If ForceLink is running, we act accordingly
        if (_this.running) {
  
          // Applying layout
          _this.applyLayoutChanges(_this.runOnBackground);
  
          // Send data back to worker and loop
          _this.sendByteArrayToWorker();
  
          // Rendering graph
          if (!_this.runOnBackground)
            _this.sigInst.refresh({skipIndexation: true});
        }
  
        // Stop ForceLink if it has converged
        if (e.data.converged) {
          _this.running = false;
        }
  
        if (!_this.running) {
          _this.killWorker();
          if (_this.runOnBackground && _this.easing) {
            _this.applyLayoutChanges(true);
            eventEmitter.dispatchEvent('interpolate');
  
            // reset fa_x/y in case a pinned node was previously layed out
            _this.graph.nodes()
              .filter(function(node) { return node.fixed; })
              .forEach(function(node) {
                node.fa2_x = node.x;
                node.fa2_y = node.y;
              });
  
            sigma.plugins.animate(
              _this.sigInst,
              {
                x: 'fa2_x',
                y: 'fa2_y'
              },
              {
                easing: _this.easing,
                onComplete: function() {
                  _this.sigInst.refresh();
                  eventEmitter.dispatchEvent('stop');
                }
              }
            );
          }
          else {
            _this.applyLayoutChanges(false);
            _this.sigInst.refresh();
            eventEmitter.dispatchEvent('stop');
          }
        }
      };
  
      (this.worker || document).addEventListener(this.msgName, this.listener);
  
      // Filling byteArrays
      this.graphToByteArrays();
  
      // Binding on kill to properly terminate layout when parent is killed
      _this.sigInst.bind('kill', function() {
        sigma.layouts.killForceLink();
      });
    };
  
    Supervisor.prototype.killWorker = function() {
      if (this.worker) {
        this.worker.terminate();
      }
      else {
        _root.postMessage({action: 'kill'}, '*');
        document.removeEventListener(this.msgName, this.listener);
      }
    };
  
    Supervisor.prototype.configure = function(config) {
  
      // Setting configuration
      this.config = config;
  
      this.shouldUseWorker =
        config.worker === false ? false : true && webWorkers;
      this.workerUrl = config.workerUrl;
      this.runOnBackground = (config.background) ? true : false;
      this.easing = config.easing;
  
      switch (config.randomize) {
        case 'globally':
          this.randomize = function(x) { return Math.random() * (config.randomizeFactor || 1) };
        break;
        case 'locally':
          this.randomize = function(x) { return x + (Math.random() * (config.randomizeFactor || 1)) };
        break;
        default:
          this.randomize = function(x) { return x };
      }
  
      if (!this.started)
        return;
  
      var data = {action: 'config', config: this.config};
  
      if (this.shouldUseWorker)
        this.worker.postMessage(data);
      else
        _root.postMessage(data, '*');
    };
  
    /**
     * Interface
     * ----------
     */
    var supervisor = null;
  
    sigma.layouts.startForceLink = function(sigInst, config) {
  
      // Create supervisor if undefined
      if (!supervisor) {
        supervisor = new Supervisor(sigInst, config);
      }
      else if (!supervisor.running) {
        supervisor.killWorker();
        supervisor.initWorker();
        supervisor.started = false;
      }
  
      // Configuration provided?
      if (config)
        supervisor.configure(config);
  
      // Start algorithm
      supervisor.start();
  
      return eventEmitter;
    };
  
    sigma.layouts.stopForceLink = function() {
      if (!supervisor)
        return;
  
      // Stop algorithm
      supervisor.stop();
  
      return supervisor;
    };
  
    sigma.layouts.killForceLink = function() {
      if (!supervisor)
        return;
  
      // Stop Algorithm
      supervisor.stop();
  
      // Kill Worker
      supervisor.killWorker();
  
      // Kill supervisor
      supervisor = null;
  
      eventEmitter = {};
      sigma.classes.dispatcher.extend(eventEmitter);
    };
  
    sigma.layouts.configForceLink = function(sigInst, config) {
      if (!supervisor) {
        supervisor = new Supervisor(sigInst, config);
      }
      else if (!supervisor.running) {
        supervisor.killWorker();
        supervisor.initWorker();
        supervisor.started = false;
      }
  
      supervisor.configure(config);
  
      return eventEmitter;
    };
  
    sigma.layouts.isForceLinkRunning = function() {
      return !!supervisor && supervisor.running;
    };
  }).call(this);
  
  }.call(window));
  
  /***/ }),
  
  /***/ 18:
  /***/ (function(module, exports) {
  
  /*** IMPORTS FROM imports-loader ***/
  (function() {
  
  ;(function(undefined) {
    'use strict';
  
    if (typeof sigma === 'undefined')
      throw new Error('sigma is not declared');
  
    // Initialize package:
    sigma.utils.pkg('sigma.layouts');
  
    /**
     * Sigma ForceLink Webworker
     * ==============================
     *
     * Author: Guillaume Plique (Yomguithereal)
     * Algorithm author: Mathieu Jacomy @ Sciences Po Medialab & WebAtlas
     * Extensions author: Sébastien Heymann @ Linkurious
     * Version: 1.0.0
     */
  
    var _root = this,
        inWebWorker = !('document' in _root);
  
    /**
     * Worker Function Wrapper
     * ------------------------
     *
     * The worker has to be wrapped into a single stringified function
     * to be passed afterwards as a BLOB object to the supervisor.
     */
    var Worker = function(undefined) {
      'use strict';
  
      /**
       * Worker settings and properties
       */
      var W = {
  
        // Properties
        ppn: 10,
        ppe: 3,
        ppr: 9,
        maxForce: 10,
        iterations: 0,
        converged: false,
  
        // Possible to change through config
        settings: {
          // force atlas 2:
          linLogMode: false,
          outboundAttractionDistribution: false,
          adjustSizes: false,
          edgeWeightInfluence: 0,
          scalingRatio: 1,
          strongGravityMode: false,
          gravity: 1,
          slowDown: 1,
          barnesHutOptimize: false,
          barnesHutTheta: 0.5,
          startingIterations: 1,
          iterationsPerRender: 1,
          // stopping condition:
          maxIterations: 1000,
          avgDistanceThreshold: 0.01,
          autoStop: false,
          // node siblings:
          alignNodeSiblings: false,
          nodeSiblingsScale: 1,
          nodeSiblingsAngleMin: 0,
          minNodeDistance: 0
        }
      };
  
      var NodeMatrix,
          EdgeMatrix,
          RegionMatrix;
  
      /**
       * Helpers
       */
      function extend() {
        var i,
            k,
            res = {},
            l = arguments.length;
  
        for (i = l - 1; i >= 0; i--)
          for (k in arguments[i])
            res[k] = arguments[i][k];
        return res;
      }
  
      function __emptyObject(obj) {
        var k;
  
        for (k in obj)
          if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k))
            delete obj[k];
  
        return obj;
      }
  
      /**
       * Return the euclidian distance between two points of a plane
       * with an orthonormal basis.
       *
       * @param  {number} x0  The X coordinate of the first point.
       * @param  {number} y0  The Y coordinate of the first point.
       * @param  {number} x1  The X coordinate of the second point.
       * @param  {number} y1  The Y coordinate of the second point.
       * @return {number}     The euclidian distance.
       */
      function getDistance(x0, y0, x1, y1) {
        return Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
      }
  
      /**
       * Scale a value from the range [baseMin, baseMax] to the range
       * [limitMin, limitMax].
       *
       * @param  {number} value    The value to rescale.
       * @param  {number} baseMin  The min value of the range of origin.
       * @param  {number} baseMax  The max value of the range of origin.
       * @param  {number} limitMin The min value of the range of destination.
       * @param  {number} limitMax The max value of the range of destination.
       * @return {number}          The scaled value.
       */
      function scaleRange(value, baseMin, baseMax, limitMin, limitMax) {
        return ((limitMax - limitMin) * (value - baseMin) / (baseMax - baseMin)) + limitMin;
      }
  
      /**
       * Get the angle of the vector (in radian).
       *
       * @param  {object} v  The 2d vector with x,y coordinates.
       * @return {number}    The angle of the vector  (in radian).
       */
      function getVectorAngle(v) {
        return Math.acos( v.x / Math.sqrt(v.x * v.x + v.y * v.y) );
      }
  
      /**
       * Get the normal vector of the line segment, i.e. the vector
       * orthogonal to the line.
       * http://stackoverflow.com/a/1243614/
       *
       * @param  {number} aX The x coorinates of the start point.
       * @param  {number} aY The y coorinates of the start point.
       * @param  {number} bX The x coorinates of the end point.
       * @param  {number} bY The y coorinates of the end point.
       * @return {object}    The 2d vector with (xi,yi), (xi_prime,yi_prime) coordinates.
       */
      function getNormalVector(aX, aY, bX, bY) {
        return {
          xi:       -(bY - aY),
          yi:         bX - aX,
          xi_prime:   bY - aY,
          yi_prime: -(bX - aX)
        };
      }
  
      /**
       * Get the normalized vector.
       *
       * @param  {object} v      The 2d vector with (xi,yi), (xi_prime,yi_prime) coordinates.
       * @param  {number} length The vector length.
       * @return {object}        The normalized vector
       */
      function getNormalizedVector(v, length) {
        return {
          x: (v.xi_prime - v.xi) / length,
          y: (v.yi_prime - v.yi) / length
        };
      }
  
      /**
       * Get the a point the line segment [A,B] at a specified distance percentage
       * from the start point.
       *
       * @param  {number} aX The x coorinates of the start point.
       * @param  {number} aY The y coorinates of the start point.
       * @param  {number} bX The x coorinates of the end point.
       * @param  {number} bY The y coorinates of the end point.
       * @param  {number} t  The distance percentage from the start point.
       * @return {object}    The (x,y) coordinates of the point.
       */
      function getPointOnLineSegment(aX, aY, bX, bY, t) {
        return {
          x: aX + (bX - aX) * t,
          y: aY + (bY - aY) * t
        };
      }
  
      /**
       * Matrices properties accessors
       */
      var nodeProperties = {
        x: 0,
        y: 1,
        dx: 2,
        dy: 3,
        old_dx: 4,
        old_dy: 5,
        mass: 6,
        convergence: 7,
        size: 8,
        fixed: 9
      };
  
      var edgeProperties = {
        source: 0,
        target: 1,
        weight: 2
      };
  
      var regionProperties = {
        node: 0,
        centerX: 1,
        centerY: 2,
        size: 3,
        nextSibling: 4,
        firstChild: 5,
        mass: 6,
        massCenterX: 7,
        massCenterY: 8
      };
  
      function np(i, p) {
  
        // DEBUG: safeguards
        if ((i % W.ppn) !== 0)
          throw new Error('Invalid argument in np: "i" is not correct (' + i + ').');
        if (i !== parseInt(i))
          throw new TypeError('Invalid argument in np: "i" is not an integer.');
  
        if (p in nodeProperties)
          return i + nodeProperties[p];
        else
          throw new Error('ForceLink.Worker - ' +
                'Inexistant node property given (' + p + ').');
      }
  
      function ep(i, p) {
  
        // DEBUG: safeguards
        if ((i % W.ppe) !== 0)
          throw new Error('Invalid argument in ep: "i" is not correct (' + i + ').');
        if (i !== parseInt(i))
          throw new TypeError('Invalid argument in ep: "i" is not an integer.');
  
        if (p in edgeProperties)
          return i + edgeProperties[p];
        else
          throw new Error('ForceLink.Worker - ' +
                'Inexistant edge property given (' + p + ').');
      }
  
      function rp(i, p) {
  
        // DEBUG: safeguards
        if ((i % W.ppr) !== 0)
          throw new Error('Invalid argument in rp: "i" is not correct (' + i + ').');
        if (i !== parseInt(i))
          throw new TypeError('Invalid argument in rp: "i" is not an integer.');
  
        if (p in regionProperties)
          return i + regionProperties[p];
        else
          throw new Error('ForceLink.Worker - ' +
                'Inexistant region property given (' + p + ').');
      }
  
      /**
       * Algorithm initialization
       */
  
      function init(nodes, edges, config) {
        config = config || {};
        var i, l;
  
        // Matrices
        NodeMatrix = nodes;
        EdgeMatrix = edges;
  
        // Length
        W.nodesLength = NodeMatrix.length;
        W.edgesLength = EdgeMatrix.length;
  
        // Merging configuration
        configure(config);
      }
  
      function configure(o) {
        W.settings = extend(o, W.settings);
      }
  
      /**
       * Algorithm pass
       */
  
      // MATH: get distances stuff and power 2 issues
      function pass() {
        var a, i, j, l, r, n, n1, n2, e, w, g, k, m;
  
        var outboundAttCompensation,
            coefficient,
            xDist,
            yDist,
            oldxDist,
            oldyDist,
            ewc,
            mass,
            distance,
            size,
            factor,
            minNodeDistance = W.settings.minNodeDistance;
  
        // 1) Initializing layout data
        //-----------------------------
  
        // Resetting positions & computing max values
        for (n = 0; n < W.nodesLength; n += W.ppn) {
          NodeMatrix[np(n, 'old_dx')] = NodeMatrix[np(n, 'dx')];
          NodeMatrix[np(n, 'old_dy')] = NodeMatrix[np(n, 'dy')];
          NodeMatrix[np(n, 'dx')] = 0;
          NodeMatrix[np(n, 'dy')] = 0;
        }
  
        // If outbound attraction distribution, compensate
        if (W.settings.outboundAttractionDistribution) {
          outboundAttCompensation = 0;
          for (n = 0; n < W.nodesLength; n += W.ppn) {
            outboundAttCompensation += NodeMatrix[np(n, 'mass')];
          }
  
          outboundAttCompensation /= W.nodesLength;
        }
  
  
        // 1.bis) Barnes-Hut computation
        //------------------------------
  
        if (W.settings.barnesHutOptimize) {
  
          var minX = Infinity,
              maxX = -Infinity,
              minY = Infinity,
              maxY = -Infinity,
              q, q0, q1, q2, q3;
  
          // Setting up
          // RegionMatrix = new Float32Array(W.nodesLength / W.ppn * 4 * W.ppr);
          RegionMatrix = [];
  
          // Computing min and max values
          for (n = 0; n < W.nodesLength; n += W.ppn) {
            minX = Math.min(minX, NodeMatrix[np(n, 'x')]);
            maxX = Math.max(maxX, NodeMatrix[np(n, 'x')]);
            minY = Math.min(minY, NodeMatrix[np(n, 'y')]);
            maxY = Math.max(maxY, NodeMatrix[np(n, 'y')]);
          }
  
          // Build the Barnes Hut root region
          RegionMatrix[rp(0, 'node')] = -1;
          RegionMatrix[rp(0, 'centerX')] = (minX + maxX) / 2;
          RegionMatrix[rp(0, 'centerY')] = (minY + maxY) / 2;
          RegionMatrix[rp(0, 'size')] = Math.max(maxX - minX, maxY - minY);
          RegionMatrix[rp(0, 'nextSibling')] = -1;
          RegionMatrix[rp(0, 'firstChild')] = -1;
          RegionMatrix[rp(0, 'mass')] = 0;
          RegionMatrix[rp(0, 'massCenterX')] = 0;
          RegionMatrix[rp(0, 'massCenterY')] = 0;
  
          // Add each node in the tree
          l = 1;
          for (n = 0; n < W.nodesLength; n += W.ppn) {
  
            // Current region, starting with root
            r = 0;
  
            while (true) {
              // Are there sub-regions?
  
              // We look at first child index
              if (RegionMatrix[rp(r, 'firstChild')] >= 0) {
  
                // There are sub-regions
  
                // We just iterate to find a "leave" of the tree
                // that is an empty region or a region with a single node
                // (see next case)
  
                // Find the quadrant of n
                if (NodeMatrix[np(n, 'x')] < RegionMatrix[rp(r, 'centerX')]) {
  
                  if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  
                    // Top Left quarter
                    q = RegionMatrix[rp(r, 'firstChild')];
                  }
                  else {
  
                    // Bottom Left quarter
                    q = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
                  }
                }
                else {
                  if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  
                    // Top Right quarter
                    q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
                  }
                  else {
  
                    // Bottom Right quarter
                    q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
                  }
                }
  
                // Update center of mass and mass (we only do it for non-leave regions)
                RegionMatrix[rp(r, 'massCenterX')] =
                  (RegionMatrix[rp(r, 'massCenterX')] * RegionMatrix[rp(r, 'mass')] +
                   NodeMatrix[np(n, 'x')] * NodeMatrix[np(n, 'mass')]) /
                  (RegionMatrix[rp(r, 'mass')] + NodeMatrix[np(n, 'mass')]);
  
                RegionMatrix[rp(r, 'massCenterY')] =
                  (RegionMatrix[rp(r, 'massCenterY')] * RegionMatrix[rp(r, 'mass')] +
                   NodeMatrix[np(n, 'y')] * NodeMatrix[np(n, 'mass')]) /
                  (RegionMatrix[rp(r, 'mass')] + NodeMatrix[np(n, 'mass')]);
  
                RegionMatrix[rp(r, 'mass')] += NodeMatrix[np(n, 'mass')];
  
                // Iterate on the right quadrant
                r = q;
                continue;
              }
              else {
  
                // There are no sub-regions: we are in a "leave"
  
                // Is there a node in this leave?
                if (RegionMatrix[rp(r, 'node')] < 0) {
  
                  // There is no node in region:
                  // we record node n and go on
                  RegionMatrix[rp(r, 'node')] = n;
                  break;
                }
                else {
  
                  // There is a node in this region
  
                  // We will need to create sub-regions, stick the two
                  // nodes (the old one r[0] and the new one n) in two
                  // subregions. If they fall in the same quadrant,
                  // we will iterate.
  
                  // Create sub-regions
                  RegionMatrix[rp(r, 'firstChild')] = l * W.ppr;
                  w = RegionMatrix[rp(r, 'size')] / 2;  // new size (half)
  
                  // NOTE: we use screen coordinates
                  // from Top Left to Bottom Right
  
                  // Top Left sub-region
                  g = RegionMatrix[rp(r, 'firstChild')];
  
                  RegionMatrix[rp(g, 'node')] = -1;
                  RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] - w;
                  RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] - w;
                  RegionMatrix[rp(g, 'size')] = w;
                  RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
                  RegionMatrix[rp(g, 'firstChild')] = -1;
                  RegionMatrix[rp(g, 'mass')] = 0;
                  RegionMatrix[rp(g, 'massCenterX')] = 0;
                  RegionMatrix[rp(g, 'massCenterY')] = 0;
  
                  // Bottom Left sub-region
                  g += W.ppr;
                  RegionMatrix[rp(g, 'node')] = -1;
                  RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] - w;
                  RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] + w;
                  RegionMatrix[rp(g, 'size')] = w;
                  RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
                  RegionMatrix[rp(g, 'firstChild')] = -1;
                  RegionMatrix[rp(g, 'mass')] = 0;
                  RegionMatrix[rp(g, 'massCenterX')] = 0;
                  RegionMatrix[rp(g, 'massCenterY')] = 0;
  
                  // Top Right sub-region
                  g += W.ppr;
                  RegionMatrix[rp(g, 'node')] = -1;
                  RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] + w;
                  RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] - w;
                  RegionMatrix[rp(g, 'size')] = w;
                  RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
                  RegionMatrix[rp(g, 'firstChild')] = -1;
                  RegionMatrix[rp(g, 'mass')] = 0;
                  RegionMatrix[rp(g, 'massCenterX')] = 0;
                  RegionMatrix[rp(g, 'massCenterY')] = 0;
  
                  // Bottom Right sub-region
                  g += W.ppr;
                  RegionMatrix[rp(g, 'node')] = -1;
                  RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] + w;
                  RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] + w;
                  RegionMatrix[rp(g, 'size')] = w;
                  RegionMatrix[rp(g, 'nextSibling')] = RegionMatrix[rp(r, 'nextSibling')];
                  RegionMatrix[rp(g, 'firstChild')] = -1;
                  RegionMatrix[rp(g, 'mass')] = 0;
                  RegionMatrix[rp(g, 'massCenterX')] = 0;
                  RegionMatrix[rp(g, 'massCenterY')] = 0;
  
                  l += 4;
  
                  // Now the goal is to find two different sub-regions
                  // for the two nodes: the one previously recorded (r[0])
                  // and the one we want to add (n)
  
                  // Find the quadrant of the old node
                  if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')] < RegionMatrix[rp(r, 'centerX')]) {
                    if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  
                      // Top Left quarter
                      q = RegionMatrix[rp(r, 'firstChild')];
                    }
                    else {
  
                      // Bottom Left quarter
                      q = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
                    }
                  }
                  else {
                    if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  
                      // Top Right quarter
                      q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
                    }
                    else {
  
                      // Bottom Right quarter
                      q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
                    }
                  }
  
                  // We remove r[0] from the region r, add its mass to r and record it in q
                  RegionMatrix[rp(r, 'mass')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')];
                  RegionMatrix[rp(r, 'massCenterX')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')];
                  RegionMatrix[rp(r, 'massCenterY')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')];
  
                  RegionMatrix[rp(q, 'node')] = RegionMatrix[rp(r, 'node')];
                  RegionMatrix[rp(r, 'node')] = -1;
  
                  // Find the quadrant of n
                  if (NodeMatrix[np(n, 'x')] < RegionMatrix[rp(r, 'centerX')]) {
                    if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  
                      // Top Left quarter
                      q2 = RegionMatrix[rp(r, 'firstChild')];
                    }
                    else {
                      // Bottom Left quarter
                      q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
                    }
                  }
                  else {
                    if(NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  
                      // Top Right quarter
                      q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
                    }
                    else {
  
                      // Bottom Right quarter
                      q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
                    }
                  }
  
                  if (q === q2) {
  
                    // If both nodes are in the same quadrant,
                    // we have to try it again on this quadrant
                    r = q;
                    continue;
                  }
  
                  // If both quadrants are different, we record n
                  // in its quadrant
                  RegionMatrix[rp(q2, 'node')] = n;
                  break;
                }
              }
            }
          }
        }
  
  
        // 2) Repulsion
        //--------------
        // NOTES: adjustSizes = antiCollision & scalingRatio = coefficient
  
        if (W.settings.barnesHutOptimize) {
          coefficient = W.settings.scalingRatio;
  
          // Applying repulsion through regions
          for (n = 0; n < W.nodesLength; n += W.ppn) {
  
            // Computing leaf quad nodes iteration
  
            r = 0; // Starting with root region
            while (true) {
  
              if (RegionMatrix[rp(r, 'firstChild')] >= 0) {
  
                // The region has sub-regions
  
                // We run the Barnes Hut test to see if we are at the right distance
                distance = Math.sqrt(
                  (NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')]) *
                  (NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')]) +
                  (NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')]) *
                  (NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')])
                );
  
                if (2 * RegionMatrix[rp(r, 'size')] / distance < W.settings.barnesHutTheta) {
  
                  // We treat the region as a single body, and we repulse
  
                  xDist = NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')];
                  yDist = NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')];
  
                  if (W.settings.adjustSizes) {
  
                    //-- Linear Anti-collision Repulsion
                    if (distance > 0) {
                      factor = coefficient * NodeMatrix[np(n, 'mass')] *
                        RegionMatrix[rp(r, 'mass')] / distance / distance;
  
                      NodeMatrix[np(n, 'dx')] += xDist * factor;
                      NodeMatrix[np(n, 'dy')] += yDist * factor;
                    }
                    else if (distance < 0) {
                      factor = -coefficient * NodeMatrix[np(n, 'mass')] *
                        RegionMatrix[rp(r, 'mass')] / distance;
  
                      NodeMatrix[np(n, 'dx')] += xDist * factor;
                      NodeMatrix[np(n, 'dy')] += yDist * factor;
                    }
                  }
                  else {
  
                    //-- Linear Repulsion
                    if (distance > 0) {
                      factor = coefficient * NodeMatrix[np(n, 'mass')] *
                        RegionMatrix[rp(r, 'mass')] / distance / distance;
  
                      NodeMatrix[np(n, 'dx')] += xDist * factor;
                      NodeMatrix[np(n, 'dy')] += yDist * factor;
                    }
                  }
  
                  // When this is done, we iterate. We have to look at the next sibling.
                  if (RegionMatrix[rp(r, 'nextSibling')] < 0)
                    break;  // No next sibling: we have finished the tree
                  r = RegionMatrix[rp(r, 'nextSibling')];
                  continue;
  
                }
                else {
  
                  // The region is too close and we have to look at sub-regions
                  r = RegionMatrix[rp(r, 'firstChild')];
                  continue;
                }
  
              }
              else {
  
                // The region has no sub-region
                // If there is a node r[0] and it is not n, then repulse
  
                if (RegionMatrix[rp(r, 'node')] >= 0 && RegionMatrix[rp(r, 'node')] !== n) {
                  xDist = NodeMatrix[np(n, 'x')] - NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')];
                  yDist = NodeMatrix[np(n, 'y')] - NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')];
  
                  distance = Math.sqrt(xDist * xDist + yDist * yDist);
  
                  if (W.settings.adjustSizes) {
  
                    //-- Linear Anti-collision Repulsion
                    if (distance > 0) {
                      factor = coefficient * NodeMatrix[np(n, 'mass')] *
                        NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance / distance;
  
                      NodeMatrix[np(n, 'dx')] += xDist * factor;
                      NodeMatrix[np(n, 'dy')] += yDist * factor;
                    }
                    else if (distance < 0) {
                      factor = -coefficient * NodeMatrix[np(n, 'mass')] *
                        NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance;
  
                      NodeMatrix[np(n, 'dx')] += xDist * factor;
                      NodeMatrix[np(n, 'dy')] += yDist * factor;
                    }
                  }
                  else {
  
                    //-- Linear Repulsion
                    if (distance > 0) {
                      factor = coefficient * NodeMatrix[np(n, 'mass')] *
                        NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance / distance;
  
                      NodeMatrix[np(n, 'dx')] += xDist * factor;
                      NodeMatrix[np(n, 'dy')] += yDist * factor;
                    }
                  }
  
                }
  
                // When this is done, we iterate. We have to look at the next sibling.
                if (RegionMatrix[rp(r, 'nextSibling')] < 0)
                  break;  // No next sibling: we have finished the tree
                r = RegionMatrix[rp(r, 'nextSibling')];
                continue;
              }
            }
          }
        }
        else {
          coefficient = W.settings.scalingRatio;
  
          // Square iteration
          for (n1 = 0; n1 < W.nodesLength; n1 += W.ppn) {
            for (n2 = 0; n2 < n1; n2 += W.ppn) {
  
              // Common to both methods
              xDist = NodeMatrix[np(n1, 'x')] - NodeMatrix[np(n2, 'x')];
              yDist = NodeMatrix[np(n1, 'y')] - NodeMatrix[np(n2, 'y')];
  
              if (W.settings.adjustSizes) {
  
                //-- Anticollision Linear Repulsion
                distance = Math.sqrt(xDist * xDist + yDist * yDist) -
                  NodeMatrix[np(n1, 'size')] -
                  NodeMatrix[np(n2, 'size')];
  
                if (distance > 0) {
                  factor = coefficient *
                    NodeMatrix[np(n1, 'mass')] *
                    NodeMatrix[np(n2, 'mass')] /
                    distance / distance;
  
                  // Updating nodes' dx and dy
                  NodeMatrix[np(n1, 'dx')] += xDist * factor;
                  NodeMatrix[np(n1, 'dy')] += yDist * factor;
  
                  NodeMatrix[np(n2, 'dx')] += xDist * factor;
                  NodeMatrix[np(n2, 'dy')] += yDist * factor;
                }
                else if (distance < 0) {
                  factor = 100 * coefficient *
                    NodeMatrix[np(n1, 'mass')] *
                    NodeMatrix[np(n2, 'mass')];
  
                  // Updating nodes' dx and dy
                  NodeMatrix[np(n1, 'dx')] += xDist * factor;
                  NodeMatrix[np(n1, 'dy')] += yDist * factor;
  
                  NodeMatrix[np(n2, 'dx')] -= xDist * factor;
                  NodeMatrix[np(n2, 'dy')] -= yDist * factor;
                }
              }
              else {
  
                //-- Linear Repulsion
                distance = Math.sqrt(xDist * xDist + yDist * yDist);
  
                if (distance > 0) {
                  factor = coefficient *
                    NodeMatrix[np(n1, 'mass')] *
                    NodeMatrix[np(n2, 'mass')] /
                    distance / distance;
  
                  // Updating nodes' dx and dy
                  NodeMatrix[np(n1, 'dx')] += xDist * factor;
                  NodeMatrix[np(n1, 'dy')] += yDist * factor;
  
                  NodeMatrix[np(n2, 'dx')] -= xDist * factor;
                  NodeMatrix[np(n2, 'dy')] -= yDist * factor;
                }
              }
            }
          }
        }
  
  
        // 3) Gravity
        //------------
        g = W.settings.gravity / W.settings.scalingRatio;
        coefficient = W.settings.scalingRatio;
        for (n = 0; n < W.nodesLength; n += W.ppn) {
          factor = 0;
  
          // Common to both methods
          xDist = NodeMatrix[np(n, 'x')];
          yDist = NodeMatrix[np(n, 'y')];
          distance = Math.sqrt(xDist * xDist + yDist * yDist);
  
          if (W.settings.strongGravityMode) {
  
            //-- Strong gravity
            if (distance > 0)
              factor = coefficient * NodeMatrix[np(n, 'mass')] * g;
          }
          else {
  
            //-- Linear Anti-collision Repulsion n
            if (distance > 0)
              factor = coefficient * NodeMatrix[np(n, 'mass')] * g / distance;
          }
  
          // Updating node's dx and dy
          NodeMatrix[np(n, 'dx')] -= xDist * factor;
          NodeMatrix[np(n, 'dy')] -= yDist * factor;
        }
  
  
  
        // 4) Attraction
        //---------------
        coefficient = 1 * (W.settings.outboundAttractionDistribution ? outboundAttCompensation : 1);
  
        // TODO: simplify distance
        // TODO: coefficient is always used as -c --> optimize?
        for (e = 0; e < W.edgesLength; e += W.ppe) {
          n1 = EdgeMatrix[ep(e, 'source')];
          n2 = EdgeMatrix[ep(e, 'target')];
          w = EdgeMatrix[ep(e, 'weight')];
  
          // Edge weight influence
          ewc = Math.pow(w, W.settings.edgeWeightInfluence);
  
          // Common measures
          xDist = NodeMatrix[np(n1, 'x')] - NodeMatrix[np(n2, 'x')];
          yDist = NodeMatrix[np(n1, 'y')] - NodeMatrix[np(n2, 'y')];
  
          // Applying attraction to nodes
          if (W.settings.adjustSizes) {
  
            distance = Math.sqrt(
              (xDist * xDist + yDist * yDist) -
              NodeMatrix[np(n1, 'size')] -
              NodeMatrix[np(n2, 'size')]
            ) - minNodeDistance;
  
            if (W.settings.linLogMode) {
              if (W.settings.outboundAttractionDistribution) {
  
                //-- LinLog Degree Distributed Anti-collision Attraction
                if (distance > 0) {
                  factor = -coefficient * ewc * Math.log(1 + distance) /
                  distance /
                  NodeMatrix[np(n1, 'mass')];
                }
              }
              else {
  
                //-- LinLog Anti-collision Attraction
                if (distance > 0) {
                  factor = -coefficient * ewc * Math.log(1 + distance) / distance;
                }
              }
            }
            else {
              if (W.settings.outboundAttractionDistribution) {
  
                //-- Linear Degree Distributed Anti-collision Attraction
                if (distance > 0) {
                  factor = -coefficient * ewc / NodeMatrix[np(n1, 'mass')];
                }
              }
              else {
  
                //-- Linear Anti-collision Attraction
                if (distance > 0) {
                  factor = -coefficient * ewc;
                }
              }
            }
          }
          else {
  
            distance = Math.sqrt(xDist * xDist + yDist * yDist) - minNodeDistance;
  
            if (W.settings.linLogMode) {
              if (W.settings.outboundAttractionDistribution) {
  
                //-- LinLog Degree Distributed Attraction
                if (distance > 0) {
                  factor = -coefficient * ewc * Math.log(1 + distance) /
                    distance /
                    NodeMatrix[np(n1, 'mass')];
                }
              }
              else {
  
                //-- LinLog Attraction
                if (distance > 0)
                  factor = -coefficient * ewc * Math.log(1 + distance) / distance;
              }
            }
            else {
              if (W.settings.outboundAttractionDistribution) {
  
                //-- Linear Attraction Mass Distributed
                // NOTE: Distance is set to 1 to override next condition
                distance = 1;
                factor = -coefficient * ewc / NodeMatrix[np(n1, 'mass')];
              }
              else {
  
                //-- Linear Attraction
                // NOTE: Distance is set to 1 to override next condition
                distance = 1;
                factor = -coefficient * ewc;
              }
            }
          }
  
          // Updating nodes' dx and dy
          // TODO: if condition or factor = 1?
          if (distance > 0) {
  
            // Updating nodes' dx and dy
            NodeMatrix[np(n1, 'dx')] += xDist * factor;
            NodeMatrix[np(n1, 'dy')] += yDist * factor;
  
            NodeMatrix[np(n2, 'dx')] -= xDist * factor;
            NodeMatrix[np(n2, 'dy')] -= yDist * factor;
          }
        }
  
  
        // 5) Apply Forces
        //-----------------
        var force,
            swinging,
            traction,
            nodespeed,
            alldistance = 0;
  
        // MATH: sqrt and square distances
        if (W.settings.adjustSizes) {
  
          for (n = 0; n < W.nodesLength; n += W.ppn) {
            if (!NodeMatrix[np(n, 'fixed')]) {
              force = Math.sqrt(
                NodeMatrix[np(n, 'dx')] * NodeMatrix[np(n, 'dx')] +
                NodeMatrix[np(n, 'dy')] * NodeMatrix[np(n, 'dy')]
              );
  
              if (force > W.maxForce) {
                NodeMatrix[np(n, 'dx')] =
                  NodeMatrix[np(n, 'dx')] * W.maxForce / force;
                NodeMatrix[np(n, 'dy')] =
                  NodeMatrix[np(n, 'dy')] * W.maxForce / force;
              }
  
              swinging = NodeMatrix[np(n, 'mass')] *
                Math.sqrt(
                  (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) *
                  (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) +
                  (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')]) *
                  (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')])
                );
  
              traction = Math.sqrt(
                (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) *
                (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) +
                (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')]) *
                (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')])
              ) / 2;
  
              nodespeed =
                0.1 * Math.log(1 + traction) / (1 + Math.sqrt(swinging));
  
              oldxDist = NodeMatrix[np(n, 'x')];
              oldyDist = NodeMatrix[np(n, 'y')];
  
              // Updating node's positon
              NodeMatrix[np(n, 'x')] =
                NodeMatrix[np(n, 'x')] + NodeMatrix[np(n, 'dx')] *
                (nodespeed / W.settings.slowDown);
              NodeMatrix[np(n, 'y')] =
                NodeMatrix[np(n, 'y')] + NodeMatrix[np(n, 'dy')] *
                (nodespeed / W.settings.slowDown);
  
              xDist = NodeMatrix[np(n, 'x')];
              yDist = NodeMatrix[np(n, 'y')];
              distance = Math.sqrt(
                (xDist - oldxDist) * (xDist - oldxDist) +
                (yDist - oldyDist) * (yDist - oldyDist)
              );
              alldistance += distance;
            }
          }
        }
        else {
  
          for (n = 0; n < W.nodesLength; n += W.ppn) {
            if (!NodeMatrix[np(n, 'fixed')]) {
  
              swinging = NodeMatrix[np(n, 'mass')] *
                Math.sqrt(
                  (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) *
                  (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) +
                  (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')]) *
                  (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')])
                );
  
              traction = Math.sqrt(
                (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) *
                (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) +
                (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')]) *
                (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')])
              ) / 2;
  
              nodespeed = NodeMatrix[np(n, 'convergence')] *
                Math.log(1 + traction) / (1 + Math.sqrt(swinging));
  
              // Updating node convergence
              NodeMatrix[np(n, 'convergence')] =
                Math.min(1, Math.sqrt(
                  nodespeed *
                  (NodeMatrix[np(n, 'dx')] * NodeMatrix[np(n, 'dx')] +
                   NodeMatrix[np(n, 'dy')] * NodeMatrix[np(n, 'dy')]) /
                  (1 + Math.sqrt(swinging))
                ));
  
              oldxDist = NodeMatrix[np(n, 'x')];
              oldyDist = NodeMatrix[np(n, 'y')];
  
              // Updating node's positon
              NodeMatrix[np(n, 'x')] =
                NodeMatrix[np(n, 'x')] + NodeMatrix[np(n, 'dx')] *
                (nodespeed / W.settings.slowDown);
              NodeMatrix[np(n, 'y')] =
                NodeMatrix[np(n, 'y')] + NodeMatrix[np(n, 'dy')] *
                (nodespeed / W.settings.slowDown);
  
              xDist = NodeMatrix[np(n, 'x')];
              yDist = NodeMatrix[np(n, 'y')];
              distance = Math.sqrt(
                (xDist - oldxDist) * (xDist - oldxDist) +
                (yDist - oldyDist) * (yDist - oldyDist)
              );
              alldistance += distance;
            }
          }
        }
  
        // Counting one more iteration
        W.iterations++;
  
        // Auto stop.
        // The greater the ratio nb nodes / nb edges,
        // the greater the number of iterations needed to converge.
        if (W.settings.autoStop) {
          W.converged = (
            W.iterations > W.settings.maxIterations ||
            alldistance / W.nodesLength < W.settings.avgDistanceThreshold
          );
  
          // align nodes that are linked to the same two nodes only:
          if (W.converged && W.settings.alignNodeSiblings) {
            // console.time("alignment");
  
            var
              neighbors = {}, // index of neighbors
              parallelNodes = {}, // set of parallel nodes indexed by same <source;target>
              setKey, // tmp
              keysN;  // tmp
  
            // build index of neighbors:
            for (e = 0; e < W.edgesLength; e += W.ppe) {
              n1 = EdgeMatrix[ep(e, 'source')];
              n2 = EdgeMatrix[ep(e, 'target')];
  
              if (n1 === n2) continue;
  
              neighbors[n1] = neighbors[n1] || {};
              neighbors[n2] = neighbors[n2] || {};
              neighbors[n1][n2] = true;
              neighbors[n2][n1] = true;
            }
  
            // group triplets by same <source, target> (resp. target, source):
            Object.keys(neighbors).forEach(function(n) {
              n = ~~n;  // string to int
              keysN = Object.keys(neighbors[n]);
              if (keysN.length == 2) {
                setKey = keysN[0] + ';' + keysN[1];
                if (setKey in parallelNodes) {
                  parallelNodes[setKey].push(n);
                }
                else {
                  setKey = keysN[1] + ';' + keysN[0];
                  if (!parallelNodes[setKey]) {
                    parallelNodes[setKey] = [ ~~keysN[1], ~~keysN[0] ];
                  }
                  parallelNodes[setKey].push(n);
                }
              }
            });
  
            var
              setNodes,
              setSource,
              setTarget,
              degSource,
              degTarget,
              sX,
              sY,
              tX,
              tY,
              t,
              distSourceTarget,
              intersectionPoint,
              normalVector,
              nNormaleVector,
              angle,
              angleMin = W.settings.nodeSiblingsAngleMin;
  
            Object.keys(parallelNodes).forEach(function(key) {
              setSource = parallelNodes[key].shift();
              setTarget = parallelNodes[key].shift();
              setNodes = parallelNodes[key].filter(function(setNode) {
                return !NodeMatrix[np(setNode, 'fixed')];
              });
  
              if (setNodes.length == 1) return;
  
              sX = NodeMatrix[np(setSource, 'x')];
              sY = NodeMatrix[np(setSource, 'y')];
              tX = NodeMatrix[np(setTarget, 'x')];
              tY = NodeMatrix[np(setTarget, 'y')];
  
              // the extremity of lowest degree attracts the nodes
              // up to 1/4 of the distance:
              degSource = Object.keys(neighbors[setSource]).length;
              degTarget = Object.keys(neighbors[setTarget]).length;
              t = scaleRange(degSource / (degSource + degTarget), 0, 1, 1/4, 3/4);
              intersectionPoint = getPointOnLineSegment(sX, sY, tX, tY, t);
  
              // vector normal to the segment [source, target]:
              normalVector = getNormalVector(sX, sY, tX, tY);
  
              distSourceTarget = getDistance(sX, sY, tX, tY);
  
              // normalized normal vector:
              nNormaleVector = getNormalizedVector(normalVector, distSourceTarget);
  
              angle = getVectorAngle(nNormaleVector);
  
              // avoid horizontal vector because node labels overlap:
              if (2 * angleMin > Math.PI)
                throw new Error('ForceLink.Worker - Invalid parameter: angleMin must be smaller than 2 PI.');
  
              if (angleMin > 0) {
                // TODO layout parameter
                if (angle < angleMin ||
                  (angle > Math.PI - angleMin) && angle <= Math.PI) {
  
                  // New vector of angle PI - angleMin
                  nNormaleVector = {
                    x: Math.cos(Math.PI - angleMin) * 2,
                    y: Math.sin(Math.PI - angleMin) * 2
                  };
                }
                else if ((angle > 2 * Math.PI - angleMin) ||
                  angle >= Math.PI && (angle < Math.PI + angleMin)) {
  
                  // New vector of angle angleMin
                  nNormaleVector = {
                    x: Math.cos(angleMin) * 2,
                    y: Math.sin(angleMin) * 2
                  };
                }
              }
  
              // evenly distribute nodes along the perpendicular line to
              // [source, target] at the computed intersection point:
              var
                start = 0,
                sign = 1,
                steps = 1;
  
              if (setNodes.length % 2 == 1) {
                steps = 0;
                start = 1;
              }
  
              for(var i = 0; i < setNodes.length; i++) {
                NodeMatrix[np(setNodes[i], 'x')] =
                  intersectionPoint.x + (sign * nNormaleVector.x * steps) *
                  ((start || i >= 2) ? W.settings.nodeSiblingsScale : W.settings.nodeSiblingsScale * 2/3);
  
                NodeMatrix[np(setNodes[i], 'y')] =
                  intersectionPoint.y + (sign * nNormaleVector.y * steps) *
                  ((start || i >= 2) ? W.settings.nodeSiblingsScale : W.settings.nodeSiblingsScale * 2/3);
  
                sign = -sign;
                steps += (i + start) % 2;
              }
            });
  
            // console.timeEnd("alignment");
          }
        }
      }
  
      /**
       * Message reception & sending
       */
  
      // Sending data back to the supervisor
      var sendNewCoords;
  
      if (typeof window !== 'undefined' && window.document) {
  
        // From same document as sigma
        sendNewCoords = function() {
          if (!W.autoStop || W.converged) {
            var e;
  
            if (document.createEvent) {
              e = document.createEvent('Event');
              e.initEvent('newCoords', true, false);
            }
            else {
              e = document.createEventObject();
              e.eventType = 'newCoords';
            }
  
            e.eventName = 'newCoords';
            e.data = {
              nodes: NodeMatrix.buffer,
              converged: W.converged
            };
            requestAnimationFrame(function() {
              document.dispatchEvent(e);
            });
          }
        };
      }
      else {
  
        // From a WebWorker
        sendNewCoords = function() {
          if (!W.autoStop || W.converged) {
            self.postMessage(
              {
                nodes: NodeMatrix.buffer,
                converged: W.converged
              },
              [NodeMatrix.buffer]
            );
          }
        };
      }
  
      // Algorithm run
      function run(n) {
        for (var i = 0; i < n; i++)
          pass();
        sendNewCoords();
      }
  
      // On supervisor message
      var listener = function(e) {
        switch (e.data.action) {
          case 'start':
            init(
              new Float32Array(e.data.nodes),
              new Float32Array(e.data.edges),
              e.data.config
            );
  
            // First iteration(s)
            run(W.settings.startingIterations);
            break;
  
          case 'loop':
            NodeMatrix = new Float32Array(e.data.nodes);
            run(W.settings.iterationsPerRender);
            break;
  
          case 'config':
  
            // Merging new settings
            configure(e.data.config);
            break;
  
          case 'kill':
  
            // Deleting context for garbage collection
            __emptyObject(W);
            NodeMatrix = null;
            EdgeMatrix = null;
            RegionMatrix = null;
            self.removeEventListener('message', listener);
            break;
  
          default:
        }
      };
  
      // Adding event listener
      self.addEventListener('message', listener);
    };
  
  
    /**
     * Exporting
     * ----------
     *
     * Crush the worker function and make it accessible by sigma's instances so
     * the supervisor can call it.
     */
    function crush(fnString) {
      var pattern,
          i,
          l;
  
      var np = [
        'x',
        'y',
        'dx',
        'dy',
        'old_dx',
        'old_dy',
        'mass',
        'convergence',
        'size',
        'fixed'
      ];
  
      var ep = [
        'source',
        'target',
        'weight'
      ];
  
      var rp = [
        'node',
        'centerX',
        'centerY',
        'size',
        'nextSibling',
        'firstChild',
        'mass',
        'massCenterX',
        'massCenterY'
      ];
  
      // rp
      // NOTE: Must go first
      for (i = 0, l = rp.length; i < l; i++) {
        pattern = new RegExp('rp\\(([^,]*), \'' + rp[i] + '\'\\)', 'g');
        fnString = fnString.replace(
          pattern,
          (i === 0) ? '$1' : '$1 + ' + i
        );
      }
  
      // np
      for (i = 0, l = np.length; i < l; i++) {
        pattern = new RegExp('np\\(([^,]*), \'' + np[i] + '\'\\)', 'g');
        fnString = fnString.replace(
          pattern,
          (i === 0) ? '$1' : '$1 + ' + i
        );
      }
  
      // ep
      for (i = 0, l = ep.length; i < l; i++) {
        pattern = new RegExp('ep\\(([^,]*), \'' + ep[i] + '\'\\)', 'g');
        fnString = fnString.replace(
          pattern,
          (i === 0) ? '$1' : '$1 + ' + i
        );
      }
  
      return fnString;
    }
  
    // Exporting
    function getWorkerFn() {
      var fnString = crush ? crush(Worker.toString()) : Worker.toString();
      return ';(' + fnString + ').call(this);';
    }
  
    if (inWebWorker) {
      // We are in a webworker, so we launch the Worker function
      eval(getWorkerFn());
    }
    else {
      // We are requesting the worker from sigma, we retrieve it therefore
      if (typeof sigma === 'undefined')
        throw new Error('sigma is not declared');
  
      sigma.layouts.getForceLinkWorker = getWorkerFn;
    }
  }).call(this);
  
  }.call(window));
  
  /***/ })
  
  /******/ });