;(function(undefined) { 'use strict'; /** * Sigma ForceAtlas2.5 Webworker * ============================== * * Author: Guillaume Plique (Yomguithereal) * Algorithm author: Mathieu Jacomy @ Sciences Po Medialab & WebAtlas * Version: 1.0.3 */ 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: { 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 } }; 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; } /** * 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 'np: non correct (' + i + ').'; if (i !== parseInt(i)) throw 'np: non int.'; if (p in nodeProperties) return i + nodeProperties[p]; else throw 'ForceAtlas2.Worker - ' + 'Inexistant node property given (' + p + ').'; } function ep(i, p) { // DEBUG: safeguards if ((i % W.ppe) !== 0) throw 'ep: non correct (' + i + ').'; if (i !== parseInt(i)) throw 'ep: non int.'; if (p in edgeProperties) return i + edgeProperties[p]; else throw 'ForceAtlas2.Worker - ' + 'Inexistant edge property given (' + p + ').'; } function rp(i, p) { // DEBUG: safeguards if ((i % W.ppr) !== 0) throw 'rp: non correct (' + i + ').'; if (i !== parseInt(i)) throw 'rp: non int.'; if (p in regionProperties) return i + regionProperties[p]; else throw 'ForceAtlas2.Worker - ' + 'Inexistant region property given (' + p + ').'; } // DEBUG function nan(v) { if (isNaN(v)) throw 'NaN alert!'; } /** * 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, ewc, mass, distance, size, factor; // 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( (Math.pow(NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')], 2)) + (Math.pow(NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')], 2)) ); 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( Math.pow(xDist, 2) + Math.pow(yDist, 2) ); 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( (Math.pow(xDist, 2) + Math.pow(yDist, 2)) - NodeMatrix[np(n1, 'size')] - NodeMatrix[np(n2, 'size')] ); 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( Math.pow(xDist, 2) + Math.pow(yDist, 2) ); 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; // 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( Math.pow(NodeMatrix[np(n, 'dx')], 2) + Math.pow(NodeMatrix[np(n, 'dy')], 2) ); 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)); // 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); } } } 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 * (Math.pow(NodeMatrix[np(n, 'dx')], 2) + Math.pow(NodeMatrix[np(n, 'dy')], 2)) / (1 + Math.sqrt(swinging)) )); // 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); } } } // Counting one more iteration W.iterations++; } /** * Message reception & sending */ // Sending data back to the supervisor var sendNewCoords; if (typeof window !== 'undefined' && window.document) { // From same document as sigma sendNewCoords = function() { 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 }; requestAnimationFrame(function() { document.dispatchEvent(e); }); }; } else { // From a WebWorker sendNewCoords = function() { self.postMessage( {nodes: NodeMatrix.buffer}, [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 'sigma is not declared'; sigma.prototype.getForceAtlas2Worker = getWorkerFn; } }).call(this);