graphology-layout-forceatlas2
Version:
ForceAtlas 2 layout algorithm for graphology.
831 lines (713 loc) • 27.5 kB
JavaScript
/**
* Graphology ForceAtlas2 Layout Webworker
* ========================================
*
* Web worker able to run the layout in a separate thread.
*/
module.exports = function worker() {
var NODES, EDGES;
var moduleShim = {};
(function () {
/* eslint no-constant-condition: 0 */
/**
* Graphology ForceAtlas2 Iteration
* =================================
*
* Function used to perform a single iteration of the algorithm.
*/
/**
* Matrices properties accessors.
*/
var NODE_X = 0;
var NODE_Y = 1;
var NODE_DX = 2;
var NODE_DY = 3;
var NODE_OLD_DX = 4;
var NODE_OLD_DY = 5;
var NODE_MASS = 6;
var NODE_CONVERGENCE = 7;
var NODE_SIZE = 8;
var NODE_FIXED = 9;
var EDGE_SOURCE = 0;
var EDGE_TARGET = 1;
var EDGE_WEIGHT = 2;
var REGION_NODE = 0;
var REGION_CENTER_X = 1;
var REGION_CENTER_Y = 2;
var REGION_SIZE = 3;
var REGION_NEXT_SIBLING = 4;
var REGION_FIRST_CHILD = 5;
var REGION_MASS = 6;
var REGION_MASS_CENTER_X = 7;
var REGION_MASS_CENTER_Y = 8;
var SUBDIVISION_ATTEMPTS = 3;
/**
* Constants.
*/
var PPN = 10;
var PPE = 3;
var PPR = 9;
var MAX_FORCE = 10;
/**
* Function used to perform a single interation of the algorithm.
*
* @param {object} options - Layout options.
* @param {Float32Array} NodeMatrix - Node data.
* @param {Float32Array} EdgeMatrix - Edge data.
* @return {object} - Some metadata.
*/
moduleShim.exports = function iterate(options, NodeMatrix, EdgeMatrix) {
// Initializing variables
var l, r, n, n1, n2, rn, e, w, g, s;
var order = NodeMatrix.length,
size = EdgeMatrix.length;
var adjustSizes = options.adjustSizes;
var thetaSquared = options.barnesHutTheta * options.barnesHutTheta;
var outboundAttCompensation, coefficient, xDist, yDist, ewc, distance, factor;
var RegionMatrix = [];
// 1) Initializing layout data
//-----------------------------
// Resetting positions & computing max values
for (n = 0; n < order; n += PPN) {
NodeMatrix[n + NODE_OLD_DX] = NodeMatrix[n + NODE_DX];
NodeMatrix[n + NODE_OLD_DY] = NodeMatrix[n + NODE_DY];
NodeMatrix[n + NODE_DX] = 0;
NodeMatrix[n + NODE_DY] = 0;
}
// If outbound attraction distribution, compensate
if (options.outboundAttractionDistribution) {
outboundAttCompensation = 0;
for (n = 0; n < order; n += PPN) {
outboundAttCompensation += NodeMatrix[n + NODE_MASS];
}
outboundAttCompensation /= order / PPN;
}
// 1.bis) Barnes-Hut computation
//------------------------------
if (options.barnesHutOptimize) {
// Setting up
var minX = Infinity,
maxX = -Infinity,
minY = Infinity,
maxY = -Infinity,
q,
q2,
subdivisionAttempts;
// Computing min and max values
for (n = 0; n < order; n += PPN) {
minX = Math.min(minX, NodeMatrix[n + NODE_X]);
maxX = Math.max(maxX, NodeMatrix[n + NODE_X]);
minY = Math.min(minY, NodeMatrix[n + NODE_Y]);
maxY = Math.max(maxY, NodeMatrix[n + NODE_Y]);
}
// squarify bounds, it's a quadtree
var dx = maxX - minX,
dy = maxY - minY;
if (dx > dy) {
minY -= (dx - dy) / 2;
maxY = minY + dx;
} else {
minX -= (dy - dx) / 2;
maxX = minX + dy;
}
// Build the Barnes Hut root region
RegionMatrix[0 + REGION_NODE] = -1;
RegionMatrix[0 + REGION_CENTER_X] = (minX + maxX) / 2;
RegionMatrix[0 + REGION_CENTER_Y] = (minY + maxY) / 2;
RegionMatrix[0 + REGION_SIZE] = Math.max(maxX - minX, maxY - minY);
RegionMatrix[0 + REGION_NEXT_SIBLING] = -1;
RegionMatrix[0 + REGION_FIRST_CHILD] = -1;
RegionMatrix[0 + REGION_MASS] = 0;
RegionMatrix[0 + REGION_MASS_CENTER_X] = 0;
RegionMatrix[0 + REGION_MASS_CENTER_Y] = 0;
// Add each node in the tree
l = 1;
for (n = 0; n < order; n += PPN) {
// Current region, starting with root
r = 0;
subdivisionAttempts = SUBDIVISION_ATTEMPTS;
while (true) {
// Are there sub-regions?
// We look at first child index
if (RegionMatrix[r + REGION_FIRST_CHILD] >= 0) {
// There are sub-regions
// We just iterate to find a "leaf" 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[n + NODE_X] < RegionMatrix[r + REGION_CENTER_X]) {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD];
} else {
// Bottom Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
}
} else {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
} else {
// Bottom Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
}
}
// Update center of mass and mass (we only do it for non-leave regions)
RegionMatrix[r + REGION_MASS_CENTER_X] =
(RegionMatrix[r + REGION_MASS_CENTER_X] *
RegionMatrix[r + REGION_MASS] +
NodeMatrix[n + NODE_X] * NodeMatrix[n + NODE_MASS]) /
(RegionMatrix[r + REGION_MASS] + NodeMatrix[n + NODE_MASS]);
RegionMatrix[r + REGION_MASS_CENTER_Y] =
(RegionMatrix[r + REGION_MASS_CENTER_Y] *
RegionMatrix[r + REGION_MASS] +
NodeMatrix[n + NODE_Y] * NodeMatrix[n + NODE_MASS]) /
(RegionMatrix[r + REGION_MASS] + NodeMatrix[n + NODE_MASS]);
RegionMatrix[r + REGION_MASS] += NodeMatrix[n + NODE_MASS];
// Iterate on the right quadrant
r = q;
continue;
} else {
// There are no sub-regions: we are in a "leaf"
// Is there a node in this leave?
if (RegionMatrix[r + REGION_NODE] < 0) {
// There is no node in region:
// we record node n and go on
RegionMatrix[r + REGION_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[r + REGION_FIRST_CHILD] = l * PPR;
w = RegionMatrix[r + REGION_SIZE] / 2; // new size (half)
// NOTE: we use screen coordinates
// from Top Left to Bottom Right
// Top Left sub-region
g = RegionMatrix[r + REGION_FIRST_CHILD];
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] =
RegionMatrix[r + REGION_CENTER_X] - w;
RegionMatrix[g + REGION_CENTER_Y] =
RegionMatrix[r + REGION_CENTER_Y] - w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
// Bottom Left sub-region
g += PPR;
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] =
RegionMatrix[r + REGION_CENTER_X] - w;
RegionMatrix[g + REGION_CENTER_Y] =
RegionMatrix[r + REGION_CENTER_Y] + w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
// Top Right sub-region
g += PPR;
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] =
RegionMatrix[r + REGION_CENTER_X] + w;
RegionMatrix[g + REGION_CENTER_Y] =
RegionMatrix[r + REGION_CENTER_Y] - w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
// Bottom Right sub-region
g += PPR;
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] =
RegionMatrix[r + REGION_CENTER_X] + w;
RegionMatrix[g + REGION_CENTER_Y] =
RegionMatrix[r + REGION_CENTER_Y] + w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] =
RegionMatrix[r + REGION_NEXT_SIBLING];
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 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[RegionMatrix[r + REGION_NODE] + NODE_X] <
RegionMatrix[r + REGION_CENTER_X]
) {
if (
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y] <
RegionMatrix[r + REGION_CENTER_Y]
) {
// Top Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD];
} else {
// Bottom Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
}
} else {
if (
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y] <
RegionMatrix[r + REGION_CENTER_Y]
) {
// Top Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
} else {
// Bottom Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
}
}
// We remove r[0] from the region r, add its mass to r and record it in q
RegionMatrix[r + REGION_MASS] =
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_MASS];
RegionMatrix[r + REGION_MASS_CENTER_X] =
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_X];
RegionMatrix[r + REGION_MASS_CENTER_Y] =
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y];
RegionMatrix[q + REGION_NODE] = RegionMatrix[r + REGION_NODE];
RegionMatrix[r + REGION_NODE] = -1;
// Find the quadrant of n
if (NodeMatrix[n + NODE_X] < RegionMatrix[r + REGION_CENTER_X]) {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Left quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD];
} else {
// Bottom Left quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
}
} else {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Right quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
} else {
// Bottom Right quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
}
}
if (q === q2) {
// If both nodes are in the same quadrant,
// we have to try it again on this quadrant
if (subdivisionAttempts--) {
r = q;
continue; // while
} else {
// we are out of precision here, and we cannot subdivide anymore
// but we have to break the loop anyway
subdivisionAttempts = SUBDIVISION_ATTEMPTS;
break; // while
}
}
// If both quadrants are different, we record n
// in its quadrant
RegionMatrix[q2 + REGION_NODE] = n;
break;
}
}
}
}
}
// 2) Repulsion
//--------------
// NOTES: adjustSizes = antiCollision & scalingRatio = coefficient
if (options.barnesHutOptimize) {
coefficient = options.scalingRatio;
// Applying repulsion through regions
for (n = 0; n < order; n += PPN) {
// Computing leaf quad nodes iteration
r = 0; // Starting with root region
while (true) {
if (RegionMatrix[r + REGION_FIRST_CHILD] >= 0) {
// The region has sub-regions
// We run the Barnes Hut test to see if we are at the right distance
distance =
Math.pow(
NodeMatrix[n + NODE_X] - RegionMatrix[r + REGION_MASS_CENTER_X],
2
) +
Math.pow(
NodeMatrix[n + NODE_Y] - RegionMatrix[r + REGION_MASS_CENTER_Y],
2
);
s = RegionMatrix[r + REGION_SIZE];
if ((4 * s * s) / distance < thetaSquared) {
// We treat the region as a single body, and we repulse
xDist =
NodeMatrix[n + NODE_X] - RegionMatrix[r + REGION_MASS_CENTER_X];
yDist =
NodeMatrix[n + NODE_Y] - RegionMatrix[r + REGION_MASS_CENTER_Y];
if (adjustSizes === true) {
//-- Linear Anti-collision Repulsion
if (distance > 0) {
factor =
(coefficient *
NodeMatrix[n + NODE_MASS] *
RegionMatrix[r + REGION_MASS]) /
distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
} else if (distance < 0) {
factor =
(-coefficient *
NodeMatrix[n + NODE_MASS] *
RegionMatrix[r + REGION_MASS]) /
Math.sqrt(distance);
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
} else {
//-- Linear Repulsion
if (distance > 0) {
factor =
(coefficient *
NodeMatrix[n + NODE_MASS] *
RegionMatrix[r + REGION_MASS]) /
distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
}
// When this is done, we iterate. We have to look at the next sibling.
r = RegionMatrix[r + REGION_NEXT_SIBLING];
if (r < 0) break; // No next sibling: we have finished the tree
continue;
} else {
// The region is too close and we have to look at sub-regions
r = RegionMatrix[r + REGION_FIRST_CHILD];
continue;
}
} else {
// The region has no sub-region
// If there is a node r[0] and it is not n, then repulse
rn = RegionMatrix[r + REGION_NODE];
if (rn >= 0 && rn !== n) {
xDist = NodeMatrix[n + NODE_X] - NodeMatrix[rn + NODE_X];
yDist = NodeMatrix[n + NODE_Y] - NodeMatrix[rn + NODE_Y];
distance = xDist * xDist + yDist * yDist;
if (adjustSizes === true) {
//-- Linear Anti-collision Repulsion
if (distance > 0) {
factor =
(coefficient *
NodeMatrix[n + NODE_MASS] *
NodeMatrix[rn + NODE_MASS]) /
distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
} else if (distance < 0) {
factor =
(-coefficient *
NodeMatrix[n + NODE_MASS] *
NodeMatrix[rn + NODE_MASS]) /
Math.sqrt(distance);
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
} else {
//-- Linear Repulsion
if (distance > 0) {
factor =
(coefficient *
NodeMatrix[n + NODE_MASS] *
NodeMatrix[rn + NODE_MASS]) /
distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
}
}
// When this is done, we iterate. We have to look at the next sibling.
r = RegionMatrix[r + REGION_NEXT_SIBLING];
if (r < 0) break; // No next sibling: we have finished the tree
continue;
}
}
}
} else {
coefficient = options.scalingRatio;
// Square iteration
for (n1 = 0; n1 < order; n1 += PPN) {
for (n2 = 0; n2 < n1; n2 += PPN) {
// Common to both methods
xDist = NodeMatrix[n1 + NODE_X] - NodeMatrix[n2 + NODE_X];
yDist = NodeMatrix[n1 + NODE_Y] - NodeMatrix[n2 + NODE_Y];
if (adjustSizes === true) {
//-- Anticollision Linear Repulsion
distance =
Math.sqrt(xDist * xDist + yDist * yDist) -
NodeMatrix[n1 + NODE_SIZE] -
NodeMatrix[n2 + NODE_SIZE];
if (distance > 0) {
factor =
(coefficient *
NodeMatrix[n1 + NODE_MASS] *
NodeMatrix[n2 + NODE_MASS]) /
distance /
distance;
// Updating nodes' dx and dy
NodeMatrix[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
} else if (distance < 0) {
factor =
100 *
coefficient *
NodeMatrix[n1 + NODE_MASS] *
NodeMatrix[n2 + NODE_MASS];
// Updating nodes' dx and dy
NodeMatrix[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
}
} else {
//-- Linear Repulsion
distance = Math.sqrt(xDist * xDist + yDist * yDist);
if (distance > 0) {
factor =
(coefficient *
NodeMatrix[n1 + NODE_MASS] *
NodeMatrix[n2 + NODE_MASS]) /
distance /
distance;
// Updating nodes' dx and dy
NodeMatrix[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
}
}
}
}
}
// 3) Gravity
//------------
g = options.gravity / options.scalingRatio;
coefficient = options.scalingRatio;
for (n = 0; n < order; n += PPN) {
factor = 0;
// Common to both methods
xDist = NodeMatrix[n + NODE_X];
yDist = NodeMatrix[n + NODE_Y];
distance = Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
if (options.strongGravityMode) {
//-- Strong gravity
if (distance > 0) factor = coefficient * NodeMatrix[n + NODE_MASS] * g;
} else {
//-- Linear Anti-collision Repulsion n
if (distance > 0)
factor = (coefficient * NodeMatrix[n + NODE_MASS] * g) / distance;
}
// Updating node's dx and dy
NodeMatrix[n + NODE_DX] -= xDist * factor;
NodeMatrix[n + NODE_DY] -= yDist * factor;
}
// 4) Attraction
//---------------
coefficient =
1 * (options.outboundAttractionDistribution ? outboundAttCompensation : 1);
// TODO: simplify distance
// TODO: coefficient is always used as -c --> optimize?
for (e = 0; e < size; e += PPE) {
n1 = EdgeMatrix[e + EDGE_SOURCE];
n2 = EdgeMatrix[e + EDGE_TARGET];
w = EdgeMatrix[e + EDGE_WEIGHT];
// Edge weight influence
ewc = Math.pow(w, options.edgeWeightInfluence);
// Common measures
xDist = NodeMatrix[n1 + NODE_X] - NodeMatrix[n2 + NODE_X];
yDist = NodeMatrix[n1 + NODE_Y] - NodeMatrix[n2 + NODE_Y];
// Applying attraction to nodes
if (adjustSizes === true) {
distance =
Math.sqrt(xDist * xDist + yDist * yDist) -
NodeMatrix[n1 + NODE_SIZE] -
NodeMatrix[n2 + NODE_SIZE];
if (options.linLogMode) {
if (options.outboundAttractionDistribution) {
//-- LinLog Degree Distributed Anti-collision Attraction
if (distance > 0) {
factor =
(-coefficient * ewc * Math.log(1 + distance)) /
distance /
NodeMatrix[n1 + NODE_MASS];
}
} else {
//-- LinLog Anti-collision Attraction
if (distance > 0) {
factor = (-coefficient * ewc * Math.log(1 + distance)) / distance;
}
}
} else {
if (options.outboundAttractionDistribution) {
//-- Linear Degree Distributed Anti-collision Attraction
if (distance > 0) {
factor = (-coefficient * ewc) / NodeMatrix[n1 + NODE_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 (options.linLogMode) {
if (options.outboundAttractionDistribution) {
//-- LinLog Degree Distributed Attraction
if (distance > 0) {
factor =
(-coefficient * ewc * Math.log(1 + distance)) /
distance /
NodeMatrix[n1 + NODE_MASS];
}
} else {
//-- LinLog Attraction
if (distance > 0)
factor = (-coefficient * ewc * Math.log(1 + distance)) / distance;
}
} else {
if (options.outboundAttractionDistribution) {
//-- Linear Attraction Mass Distributed
// NOTE: Distance is set to 1 to override next condition
distance = 1;
factor = (-coefficient * ewc) / NodeMatrix[n1 + NODE_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[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
}
}
// 5) Apply Forces
//-----------------
var force, swinging, traction, nodespeed, newX, newY;
// MATH: sqrt and square distances
if (adjustSizes === true) {
for (n = 0; n < order; n += PPN) {
if (NodeMatrix[n + NODE_FIXED] !== 1) {
force = Math.sqrt(
Math.pow(NodeMatrix[n + NODE_DX], 2) +
Math.pow(NodeMatrix[n + NODE_DY], 2)
);
if (force > MAX_FORCE) {
NodeMatrix[n + NODE_DX] =
(NodeMatrix[n + NODE_DX] * MAX_FORCE) / force;
NodeMatrix[n + NODE_DY] =
(NodeMatrix[n + NODE_DY] * MAX_FORCE) / force;
}
swinging =
NodeMatrix[n + NODE_MASS] *
Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY])
);
traction =
Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY])
) / 2;
nodespeed = (0.1 * Math.log(1 + traction)) / (1 + Math.sqrt(swinging));
// Updating node's positon
newX =
NodeMatrix[n + NODE_X] +
NodeMatrix[n + NODE_DX] * (nodespeed / options.slowDown);
NodeMatrix[n + NODE_X] = newX;
newY =
NodeMatrix[n + NODE_Y] +
NodeMatrix[n + NODE_DY] * (nodespeed / options.slowDown);
NodeMatrix[n + NODE_Y] = newY;
}
}
} else {
for (n = 0; n < order; n += PPN) {
if (NodeMatrix[n + NODE_FIXED] !== 1) {
swinging =
NodeMatrix[n + NODE_MASS] *
Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY])
);
traction =
Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY])
) / 2;
nodespeed =
(NodeMatrix[n + NODE_CONVERGENCE] * Math.log(1 + traction)) /
(1 + Math.sqrt(swinging));
// Updating node convergence
NodeMatrix[n + NODE_CONVERGENCE] = Math.min(
1,
Math.sqrt(
(nodespeed *
(Math.pow(NodeMatrix[n + NODE_DX], 2) +
Math.pow(NodeMatrix[n + NODE_DY], 2))) /
(1 + Math.sqrt(swinging))
)
);
// Updating node's positon
newX =
NodeMatrix[n + NODE_X] +
NodeMatrix[n + NODE_DX] * (nodespeed / options.slowDown);
NodeMatrix[n + NODE_X] = newX;
newY =
NodeMatrix[n + NODE_Y] +
NodeMatrix[n + NODE_DY] * (nodespeed / options.slowDown);
NodeMatrix[n + NODE_Y] = newY;
}
}
}
// We return the information about the layout (no need to return the matrices)
return {};
};
})();
var iterate = moduleShim.exports;
self.addEventListener('message', function (event) {
var data = event.data;
NODES = new Float32Array(data.nodes);
if (data.edges) EDGES = new Float32Array(data.edges);
// Running the iteration
iterate(data.settings, NODES, EDGES);
// Sending result to supervisor
self.postMessage(
{
nodes: NODES.buffer
},
[NODES.buffer]
);
});
};