import {dispatch} from "d3-dispatch"; import {map} from "d3-collection"; import {timer} from "d3-timer"; export function x(d) { return d.x; } export function y(d) { return d.y; } var initialRadius = 10, initialAngle = Math.PI * (3 - Math.sqrt(5)); export default function(nodes) { var simulation, alpha = 1, alphaMin = 0.001, alphaDecay = 1 - Math.pow(alphaMin, 1 / 300), alphaTarget = 0, drag = 0.6, forces = map(), fixes = {}, stepper = timer(step), event = dispatch("tick", "end"); if (nodes == null) nodes = []; function step() { tick(); event.call("tick", simulation); if (alpha < alphaMin) { stepper.stop(); event.call("end", simulation); } } function tick() { var i, n = nodes.length, node, fix; alpha += (alphaTarget - alpha) * alphaDecay; forces.each(function(force) { force(alpha); }); for (i = 0; i < n; ++i) { node = nodes[i]; node.x += node.vx *= drag; node.y += node.vy *= drag; } for (i in fixes) { fix = fixes[i], node = nodes[i]; node.x = fix.x; node.y = fix.y; node.vx = node.vy = 0; } } function initializeNodes() { for (var i = 0, n = nodes.length, node; i < n; ++i) { node = nodes[i], node.index = i; if (isNaN(node.x) || isNaN(node.y)) { var radius = initialRadius * Math.sqrt(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); 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.each(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; }, drag: function(_) { return arguments.length ? (drag = 1 - _, simulation) : 1 - drag; }, force: function(name, _) { return arguments.length > 1 ? ((_ == null ? forces.remove(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name); }, fix: function(node, x, y) { return fixes[node.index] = {x: x == null ? node.x : +x, y: y == null ? node.y : +y}, simulation; }, unfix: function(node) { return delete fixes[node.index], simulation; }, on: function(name, _) { return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name); } }; }