108 lines
3.1 KiB
JavaScript
108 lines
3.1 KiB
JavaScript
import constant from "./constant";
|
|
import jiggle from "./jiggle";
|
|
|
|
/**
|
|
* Modified link force algorithm
|
|
* - simplify calculations for parameters locked for spring model
|
|
* - replace the use of links {} with loop. greatly reduce memory usage
|
|
* - removed other unused functions
|
|
* Alpha should be constant 1 for accurate simulation
|
|
*/
|
|
export default function() {
|
|
var dataSizeFactor,
|
|
distance = constant(30),
|
|
distances = [],
|
|
nodes,
|
|
stableVelocity = 0,
|
|
stableVeloHandler = null,
|
|
latestVelocityDiff = 0,
|
|
iterations = 1;
|
|
|
|
function force(alpha) {
|
|
let n = nodes.length;
|
|
// Cache old velocity for comparison later
|
|
if (stableVeloHandler!==null && stableVelocity>=0) {
|
|
for (let i = n-1, node; i>=0; i--) {
|
|
node = nodes[i];
|
|
node.oldvx = node.vx;
|
|
node.oldvy = node.vy;
|
|
}
|
|
}
|
|
// Each iteration in a tick
|
|
for (var k = 0, source, target, i, j, x, y, l; k < iterations; ++k) {
|
|
// For each link
|
|
for (i = 1; i < n; i++) for (j = 0; j < i; j++) {
|
|
// jiggle so l won't be zero and divide by zero error after this
|
|
source = nodes[i];
|
|
target = nodes[j];
|
|
x = target.x + target.vx - source.x - source.vx || jiggle();
|
|
y = target.y + target.vy - source.y - source.vy || jiggle();
|
|
l = Math.sqrt(x * x + y * y);
|
|
l = (l - distances[i*(i-1)/2+j]) / l * dataSizeFactor * alpha;
|
|
x *= l, y *= l;
|
|
target.vx -= x;
|
|
target.vy -= y;
|
|
source.vx += x;
|
|
source.vy += y;
|
|
}
|
|
}
|
|
|
|
// Calculate velocity changes, aka force applied.
|
|
if (stableVeloHandler!==null && stableVelocity>=0) {
|
|
let velocityDiff = 0;
|
|
for (let i = n-1, node; i>=0; i--) {
|
|
node = nodes[i];
|
|
velocityDiff += Math.abs(Math.hypot(node.vx-node.oldvx, node.vy-node.oldvy));
|
|
}
|
|
velocityDiff /= n;
|
|
latestVelocityDiff = velocityDiff;
|
|
|
|
if(velocityDiff<stableVelocity){
|
|
stableVeloHandler();
|
|
}
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
if (!nodes) return;
|
|
// 0.5 to divide the force to two part for source and target node
|
|
dataSizeFactor = 0.5/(nodes.length-1);
|
|
initializeDistance();
|
|
}
|
|
|
|
function initializeDistance() {
|
|
if (!nodes) return;
|
|
for (let i = 1, n = nodes.length; i < n; i++) {
|
|
for (let j = 0; j < i; j++) {
|
|
distances.push(distance(nodes[i], nodes[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
force.initialize = function(_) {
|
|
nodes = _;
|
|
initialize();
|
|
};
|
|
|
|
force.iterations = function(_) {
|
|
return arguments.length ? (iterations = +_, force) : iterations;
|
|
};
|
|
|
|
force.distance = function(_) {
|
|
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance;
|
|
};
|
|
|
|
force.latestAccel = function () {
|
|
return latestVelocityDiff;
|
|
};
|
|
|
|
force.onStableVelo = function (_) {
|
|
return arguments.length ? (stableVeloHandler = _, force) : stableVeloHandler;
|
|
};
|
|
|
|
force.stableVelocity = function (_) {
|
|
return arguments.length ? (stableVelocity = _, force) : stableVelocity;
|
|
};
|
|
return force;
|
|
}
|