694 lines
18 KiB
JavaScript
694 lines
18 KiB
JavaScript
// https://d3js.org/d3-force/ v3.0.0 Copyright 2010-2021 Mike Bostock
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-quadtree'), require('d3-dispatch'), require('d3-timer')) :
|
|
typeof define === 'function' && define.amd ? define(['exports', 'd3-quadtree', 'd3-dispatch', 'd3-timer'], factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {}, global.d3, global.d3, global.d3));
|
|
}(this, (function (exports, d3Quadtree, d3Dispatch, d3Timer) { 'use strict';
|
|
|
|
function center(x, y) {
|
|
var nodes, strength = 1;
|
|
|
|
if (x == null) x = 0;
|
|
if (y == null) y = 0;
|
|
|
|
function force() {
|
|
var i,
|
|
n = nodes.length,
|
|
node,
|
|
sx = 0,
|
|
sy = 0;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
node = nodes[i], sx += node.x, sy += node.y;
|
|
}
|
|
|
|
for (sx = (sx / n - x) * strength, sy = (sy / n - y) * strength, i = 0; i < n; ++i) {
|
|
node = nodes[i], node.x -= sx, node.y -= sy;
|
|
}
|
|
}
|
|
|
|
force.initialize = function(_) {
|
|
nodes = _;
|
|
};
|
|
|
|
force.x = function(_) {
|
|
return arguments.length ? (x = +_, force) : x;
|
|
};
|
|
|
|
force.y = function(_) {
|
|
return arguments.length ? (y = +_, force) : y;
|
|
};
|
|
|
|
force.strength = function(_) {
|
|
return arguments.length ? (strength = +_, force) : strength;
|
|
};
|
|
|
|
return force;
|
|
}
|
|
|
|
function constant(x) {
|
|
return function() {
|
|
return x;
|
|
};
|
|
}
|
|
|
|
function jiggle(random) {
|
|
return (random() - 0.5) * 1e-6;
|
|
}
|
|
|
|
function x$2(d) {
|
|
return d.x + d.vx;
|
|
}
|
|
|
|
function y$2(d) {
|
|
return d.y + d.vy;
|
|
}
|
|
|
|
function collide(radius) {
|
|
var nodes,
|
|
radii,
|
|
random,
|
|
strength = 1,
|
|
iterations = 1;
|
|
|
|
if (typeof radius !== "function") radius = constant(radius == null ? 1 : +radius);
|
|
|
|
function force() {
|
|
var i, n = nodes.length,
|
|
tree,
|
|
node,
|
|
xi,
|
|
yi,
|
|
ri,
|
|
ri2;
|
|
|
|
for (var k = 0; k < iterations; ++k) {
|
|
tree = d3Quadtree.quadtree(nodes, x$2, y$2).visitAfter(prepare);
|
|
for (i = 0; i < n; ++i) {
|
|
node = nodes[i];
|
|
ri = radii[node.index], ri2 = ri * ri;
|
|
xi = node.x + node.vx;
|
|
yi = node.y + node.vy;
|
|
tree.visit(apply);
|
|
}
|
|
}
|
|
|
|
function apply(quad, x0, y0, x1, y1) {
|
|
var data = quad.data, rj = quad.r, r = ri + rj;
|
|
if (data) {
|
|
if (data.index > node.index) {
|
|
var x = xi - data.x - data.vx,
|
|
y = yi - data.y - data.vy,
|
|
l = x * x + y * y;
|
|
if (l < r * r) {
|
|
if (x === 0) x = jiggle(random), l += x * x;
|
|
if (y === 0) y = jiggle(random), l += y * y;
|
|
l = (r - (l = Math.sqrt(l))) / l * strength;
|
|
node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj));
|
|
node.vy += (y *= l) * r;
|
|
data.vx -= x * (r = 1 - r);
|
|
data.vy -= y * r;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r;
|
|
}
|
|
}
|
|
|
|
function prepare(quad) {
|
|
if (quad.data) return quad.r = radii[quad.data.index];
|
|
for (var i = quad.r = 0; i < 4; ++i) {
|
|
if (quad[i] && quad[i].r > quad.r) {
|
|
quad.r = quad[i].r;
|
|
}
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
if (!nodes) return;
|
|
var i, n = nodes.length, node;
|
|
radii = new Array(n);
|
|
for (i = 0; i < n; ++i) node = nodes[i], radii[node.index] = +radius(node, i, nodes);
|
|
}
|
|
|
|
force.initialize = function(_nodes, _random) {
|
|
nodes = _nodes;
|
|
random = _random;
|
|
initialize();
|
|
};
|
|
|
|
force.iterations = function(_) {
|
|
return arguments.length ? (iterations = +_, force) : iterations;
|
|
};
|
|
|
|
force.strength = function(_) {
|
|
return arguments.length ? (strength = +_, force) : strength;
|
|
};
|
|
|
|
force.radius = function(_) {
|
|
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius;
|
|
};
|
|
|
|
return force;
|
|
}
|
|
|
|
function index(d) {
|
|
return d.index;
|
|
}
|
|
|
|
function find(nodeById, nodeId) {
|
|
var node = nodeById.get(nodeId);
|
|
if (!node) throw new Error("node not found: " + nodeId);
|
|
return node;
|
|
}
|
|
|
|
function link(links) {
|
|
var id = index,
|
|
strength = defaultStrength,
|
|
strengths,
|
|
distance = constant(30),
|
|
distances,
|
|
nodes,
|
|
count,
|
|
bias,
|
|
random,
|
|
iterations = 1;
|
|
|
|
if (links == null) links = [];
|
|
|
|
function defaultStrength(link) {
|
|
return 1 / Math.min(count[link.source.index], count[link.target.index]);
|
|
}
|
|
|
|
function force(alpha) {
|
|
for (var k = 0, n = links.length; k < iterations; ++k) {
|
|
for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) {
|
|
link = links[i], source = link.source, target = link.target;
|
|
x = target.x + target.vx - source.x - source.vx || jiggle(random);
|
|
y = target.y + target.vy - source.y - source.vy || jiggle(random);
|
|
l = Math.sqrt(x * x + y * y);
|
|
l = (l - distances[i]) / l * alpha * strengths[i];
|
|
x *= l, y *= l;
|
|
target.vx -= x * (b = bias[i]);
|
|
target.vy -= y * b;
|
|
source.vx += x * (b = 1 - b);
|
|
source.vy += y * b;
|
|
}
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
if (!nodes) return;
|
|
|
|
var i,
|
|
n = nodes.length,
|
|
m = links.length,
|
|
nodeById = new Map(nodes.map((d, i) => [id(d, i, nodes), d])),
|
|
link;
|
|
|
|
for (i = 0, count = new Array(n); i < m; ++i) {
|
|
link = links[i], link.index = i;
|
|
if (typeof link.source !== "object") link.source = find(nodeById, link.source);
|
|
if (typeof link.target !== "object") link.target = find(nodeById, link.target);
|
|
count[link.source.index] = (count[link.source.index] || 0) + 1;
|
|
count[link.target.index] = (count[link.target.index] || 0) + 1;
|
|
}
|
|
|
|
for (i = 0, bias = new Array(m); i < m; ++i) {
|
|
link = links[i], bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]);
|
|
}
|
|
|
|
strengths = new Array(m), initializeStrength();
|
|
distances = new Array(m), initializeDistance();
|
|
}
|
|
|
|
function initializeStrength() {
|
|
if (!nodes) return;
|
|
|
|
for (var i = 0, n = links.length; i < n; ++i) {
|
|
strengths[i] = +strength(links[i], i, links);
|
|
}
|
|
}
|
|
|
|
function initializeDistance() {
|
|
if (!nodes) return;
|
|
|
|
for (var i = 0, n = links.length; i < n; ++i) {
|
|
distances[i] = +distance(links[i], i, links);
|
|
}
|
|
}
|
|
|
|
force.initialize = function(_nodes, _random) {
|
|
nodes = _nodes;
|
|
random = _random;
|
|
initialize();
|
|
};
|
|
|
|
force.links = function(_) {
|
|
return arguments.length ? (links = _, initialize(), force) : links;
|
|
};
|
|
|
|
force.id = function(_) {
|
|
return arguments.length ? (id = _, force) : id;
|
|
};
|
|
|
|
force.iterations = function(_) {
|
|
return arguments.length ? (iterations = +_, force) : iterations;
|
|
};
|
|
|
|
force.strength = function(_) {
|
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initializeStrength(), force) : strength;
|
|
};
|
|
|
|
force.distance = function(_) {
|
|
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance;
|
|
};
|
|
|
|
return force;
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use
|
|
const a = 1664525;
|
|
const c = 1013904223;
|
|
const m = 4294967296; // 2^32
|
|
|
|
function lcg() {
|
|
let s = 1;
|
|
return () => (s = (a * s + c) % m) / m;
|
|
}
|
|
|
|
function x$1(d) {
|
|
return d.x;
|
|
}
|
|
|
|
function y$1(d) {
|
|
return d.y;
|
|
}
|
|
|
|
var initialRadius = 10,
|
|
initialAngle = Math.PI * (3 - Math.sqrt(5));
|
|
|
|
function simulation(nodes) {
|
|
var simulation,
|
|
alpha = 1,
|
|
alphaMin = 0.001,
|
|
alphaDecay = 1 - Math.pow(alphaMin, 1 / 300),
|
|
alphaTarget = 0,
|
|
velocityDecay = 0.6,
|
|
forces = new Map(),
|
|
stepper = d3Timer.timer(step),
|
|
event = d3Dispatch.dispatch("tick", "end"),
|
|
random = lcg();
|
|
|
|
if (nodes == null) nodes = [];
|
|
|
|
function step() {
|
|
tick();
|
|
event.call("tick", simulation);
|
|
if (alpha < alphaMin) {
|
|
stepper.stop();
|
|
event.call("end", simulation);
|
|
}
|
|
}
|
|
|
|
function tick(iterations) {
|
|
var i, n = nodes.length, node;
|
|
|
|
if (iterations === undefined) iterations = 1;
|
|
|
|
for (var k = 0; k < iterations; ++k) {
|
|
alpha += (alphaTarget - alpha) * alphaDecay;
|
|
|
|
forces.forEach(function(force) {
|
|
force(alpha);
|
|
});
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
node = nodes[i];
|
|
if (node.fx == null) node.x += node.vx *= velocityDecay;
|
|
else node.x = node.fx, node.vx = 0;
|
|
if (node.fy == null) node.y += node.vy *= velocityDecay;
|
|
else node.y = node.fy, node.vy = 0;
|
|
}
|
|
}
|
|
|
|
return simulation;
|
|
}
|
|
|
|
function initializeNodes() {
|
|
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
node = nodes[i], node.index = i;
|
|
if (node.fx != null) node.x = node.fx;
|
|
if (node.fy != null) node.y = node.fy;
|
|
if (isNaN(node.x) || isNaN(node.y)) {
|
|
var radius = initialRadius * Math.sqrt(0.5 + i), angle = i * initialAngle;
|
|
node.x = radius * Math.cos(angle);
|
|
node.y = radius * Math.sin(angle);
|
|
}
|
|
if (isNaN(node.vx) || isNaN(node.vy)) {
|
|
node.vx = node.vy = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
function initializeForce(force) {
|
|
if (force.initialize) force.initialize(nodes, random);
|
|
return force;
|
|
}
|
|
|
|
initializeNodes();
|
|
|
|
return simulation = {
|
|
tick: tick,
|
|
|
|
restart: function() {
|
|
return stepper.restart(step), simulation;
|
|
},
|
|
|
|
stop: function() {
|
|
return stepper.stop(), simulation;
|
|
},
|
|
|
|
nodes: function(_) {
|
|
return arguments.length ? (nodes = _, initializeNodes(), forces.forEach(initializeForce), simulation) : nodes;
|
|
},
|
|
|
|
alpha: function(_) {
|
|
return arguments.length ? (alpha = +_, simulation) : alpha;
|
|
},
|
|
|
|
alphaMin: function(_) {
|
|
return arguments.length ? (alphaMin = +_, simulation) : alphaMin;
|
|
},
|
|
|
|
alphaDecay: function(_) {
|
|
return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay;
|
|
},
|
|
|
|
alphaTarget: function(_) {
|
|
return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget;
|
|
},
|
|
|
|
velocityDecay: function(_) {
|
|
return arguments.length ? (velocityDecay = 1 - _, simulation) : 1 - velocityDecay;
|
|
},
|
|
|
|
randomSource: function(_) {
|
|
return arguments.length ? (random = _, forces.forEach(initializeForce), simulation) : random;
|
|
},
|
|
|
|
force: function(name, _) {
|
|
return arguments.length > 1 ? ((_ == null ? forces.delete(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name);
|
|
},
|
|
|
|
find: function(x, y, radius) {
|
|
var i = 0,
|
|
n = nodes.length,
|
|
dx,
|
|
dy,
|
|
d2,
|
|
node,
|
|
closest;
|
|
|
|
if (radius == null) radius = Infinity;
|
|
else radius *= radius;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
node = nodes[i];
|
|
dx = x - node.x;
|
|
dy = y - node.y;
|
|
d2 = dx * dx + dy * dy;
|
|
if (d2 < radius) closest = node, radius = d2;
|
|
}
|
|
|
|
return closest;
|
|
},
|
|
|
|
on: function(name, _) {
|
|
return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name);
|
|
}
|
|
};
|
|
}
|
|
|
|
function manyBody() {
|
|
var nodes,
|
|
node,
|
|
random,
|
|
alpha,
|
|
strength = constant(-30),
|
|
strengths,
|
|
distanceMin2 = 1,
|
|
distanceMax2 = Infinity,
|
|
theta2 = 0.81;
|
|
|
|
function force(_) {
|
|
var i, n = nodes.length, tree = d3Quadtree.quadtree(nodes, x$1, y$1).visitAfter(accumulate);
|
|
for (alpha = _, i = 0; i < n; ++i) node = nodes[i], tree.visit(apply);
|
|
}
|
|
|
|
function initialize() {
|
|
if (!nodes) return;
|
|
var i, n = nodes.length, node;
|
|
strengths = new Array(n);
|
|
for (i = 0; i < n; ++i) node = nodes[i], strengths[node.index] = +strength(node, i, nodes);
|
|
}
|
|
|
|
function accumulate(quad) {
|
|
var strength = 0, q, c, weight = 0, x, y, i;
|
|
|
|
// For internal nodes, accumulate forces from child quadrants.
|
|
if (quad.length) {
|
|
for (x = y = i = 0; i < 4; ++i) {
|
|
if ((q = quad[i]) && (c = Math.abs(q.value))) {
|
|
strength += q.value, weight += c, x += c * q.x, y += c * q.y;
|
|
}
|
|
}
|
|
quad.x = x / weight;
|
|
quad.y = y / weight;
|
|
}
|
|
|
|
// For leaf nodes, accumulate forces from coincident quadrants.
|
|
else {
|
|
q = quad;
|
|
q.x = q.data.x;
|
|
q.y = q.data.y;
|
|
do strength += strengths[q.data.index];
|
|
while (q = q.next);
|
|
}
|
|
|
|
quad.value = strength;
|
|
}
|
|
|
|
function apply(quad, x1, _, x2) {
|
|
if (!quad.value) return true;
|
|
|
|
var x = quad.x - node.x,
|
|
y = quad.y - node.y,
|
|
w = x2 - x1,
|
|
l = x * x + y * y;
|
|
|
|
// Apply the Barnes-Hut approximation if possible.
|
|
// Limit forces for very close nodes; randomize direction if coincident.
|
|
if (w * w / theta2 < l) {
|
|
if (l < distanceMax2) {
|
|
if (x === 0) x = jiggle(random), l += x * x;
|
|
if (y === 0) y = jiggle(random), l += y * y;
|
|
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l);
|
|
node.vx += x * quad.value * alpha / l;
|
|
node.vy += y * quad.value * alpha / l;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, process points directly.
|
|
else if (quad.length || l >= distanceMax2) return;
|
|
|
|
// Limit forces for very close nodes; randomize direction if coincident.
|
|
if (quad.data !== node || quad.next) {
|
|
if (x === 0) x = jiggle(random), l += x * x;
|
|
if (y === 0) y = jiggle(random), l += y * y;
|
|
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l);
|
|
}
|
|
|
|
do if (quad.data !== node) {
|
|
w = strengths[quad.data.index] * alpha / l;
|
|
node.vx += x * w;
|
|
node.vy += y * w;
|
|
} while (quad = quad.next);
|
|
}
|
|
|
|
force.initialize = function(_nodes, _random) {
|
|
nodes = _nodes;
|
|
random = _random;
|
|
initialize();
|
|
};
|
|
|
|
force.strength = function(_) {
|
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
};
|
|
|
|
force.distanceMin = function(_) {
|
|
return arguments.length ? (distanceMin2 = _ * _, force) : Math.sqrt(distanceMin2);
|
|
};
|
|
|
|
force.distanceMax = function(_) {
|
|
return arguments.length ? (distanceMax2 = _ * _, force) : Math.sqrt(distanceMax2);
|
|
};
|
|
|
|
force.theta = function(_) {
|
|
return arguments.length ? (theta2 = _ * _, force) : Math.sqrt(theta2);
|
|
};
|
|
|
|
return force;
|
|
}
|
|
|
|
function radial(radius, x, y) {
|
|
var nodes,
|
|
strength = constant(0.1),
|
|
strengths,
|
|
radiuses;
|
|
|
|
if (typeof radius !== "function") radius = constant(+radius);
|
|
if (x == null) x = 0;
|
|
if (y == null) y = 0;
|
|
|
|
function force(alpha) {
|
|
for (var i = 0, n = nodes.length; i < n; ++i) {
|
|
var node = nodes[i],
|
|
dx = node.x - x || 1e-6,
|
|
dy = node.y - y || 1e-6,
|
|
r = Math.sqrt(dx * dx + dy * dy),
|
|
k = (radiuses[i] - r) * strengths[i] * alpha / r;
|
|
node.vx += dx * k;
|
|
node.vy += dy * k;
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
if (!nodes) return;
|
|
var i, n = nodes.length;
|
|
strengths = new Array(n);
|
|
radiuses = new Array(n);
|
|
for (i = 0; i < n; ++i) {
|
|
radiuses[i] = +radius(nodes[i], i, nodes);
|
|
strengths[i] = isNaN(radiuses[i]) ? 0 : +strength(nodes[i], i, nodes);
|
|
}
|
|
}
|
|
|
|
force.initialize = function(_) {
|
|
nodes = _, initialize();
|
|
};
|
|
|
|
force.strength = function(_) {
|
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
};
|
|
|
|
force.radius = function(_) {
|
|
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius;
|
|
};
|
|
|
|
force.x = function(_) {
|
|
return arguments.length ? (x = +_, force) : x;
|
|
};
|
|
|
|
force.y = function(_) {
|
|
return arguments.length ? (y = +_, force) : y;
|
|
};
|
|
|
|
return force;
|
|
}
|
|
|
|
function x(x) {
|
|
var strength = constant(0.1),
|
|
nodes,
|
|
strengths,
|
|
xz;
|
|
|
|
if (typeof x !== "function") x = constant(x == null ? 0 : +x);
|
|
|
|
function force(alpha) {
|
|
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
if (!nodes) return;
|
|
var i, n = nodes.length;
|
|
strengths = new Array(n);
|
|
xz = new Array(n);
|
|
for (i = 0; i < n; ++i) {
|
|
strengths[i] = isNaN(xz[i] = +x(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
}
|
|
}
|
|
|
|
force.initialize = function(_) {
|
|
nodes = _;
|
|
initialize();
|
|
};
|
|
|
|
force.strength = function(_) {
|
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
};
|
|
|
|
force.x = function(_) {
|
|
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), initialize(), force) : x;
|
|
};
|
|
|
|
return force;
|
|
}
|
|
|
|
function y(y) {
|
|
var strength = constant(0.1),
|
|
nodes,
|
|
strengths,
|
|
yz;
|
|
|
|
if (typeof y !== "function") y = constant(y == null ? 0 : +y);
|
|
|
|
function force(alpha) {
|
|
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
if (!nodes) return;
|
|
var i, n = nodes.length;
|
|
strengths = new Array(n);
|
|
yz = new Array(n);
|
|
for (i = 0; i < n; ++i) {
|
|
strengths[i] = isNaN(yz[i] = +y(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
}
|
|
}
|
|
|
|
force.initialize = function(_) {
|
|
nodes = _;
|
|
initialize();
|
|
};
|
|
|
|
force.strength = function(_) {
|
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
|
|
};
|
|
|
|
force.y = function(_) {
|
|
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), initialize(), force) : y;
|
|
};
|
|
|
|
return force;
|
|
}
|
|
|
|
exports.forceCenter = center;
|
|
exports.forceCollide = collide;
|
|
exports.forceLink = link;
|
|
exports.forceManyBody = manyBody;
|
|
exports.forceRadial = radial;
|
|
exports.forceSimulation = simulation;
|
|
exports.forceX = x;
|
|
exports.forceY = y;
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
})));
|