d3-force simulation methods
Force simulation methods: simulation.restart (reheat and restart timer), simulation.stop (stop timer), simulation.tick (advance one step), simulation.nodes (set nodes), simulation.alpha (set current alpha), simulation.alphaMin (set minimum alpha threshold), simulation.alphaDecay (set alpha decay rate), simulation.alphaTarget (set target alpha), simulation.velocityDecay (set velocity decay rate), simulation.force (add/remove force), simulation.find (find closest node), simulation.randomSource (set random source), simulation.on (add/remove event listener).
forceSimulation function signature and behavior
forceSimulation(nodes) creates a new simulation with the specified array of nodes and no forces. If nodes is not specified, it defaults to the empty array. The function is impure and mutates the passed-in nodes. The simulator starts automatically; use simulation.on() to listen for tick events as the simulation runs. If you wish to run the simulation manually instead, call simulation.stop(), and then call simulation.tick() as desired.
simulation.restart() method
simulation.restart() restarts the simulation's internal timer and returns the simulation. In conjunction with simulation.alphaTarget() or simulation.alpha(), this method can be used to reheat the simulation during interaction, such as when dragging a node, or to resume the simulation after temporarily pausing it with simulation.stop().
simulation.stop() method
simulation.stop() stops the simulation's internal timer, if it is running, and returns the simulation. If the timer is already stopped, this method does nothing. This method is useful for running the simulation manually; see simulation.tick().
simulation.tick() method signature and behavior
simulation.tick(iterations) manually steps the simulation by the specified number of iterations, and returns the simulation. If iterations is not specified, it defaults to 1. For each iteration, it increments the current alpha by (alphaTarget - alpha) × alphaDecay; then invokes each registered force, passing the new alpha; then decrements each node's velocity by velocity × velocityDecay; lastly increments each node's position by velocity. This method does not dispatch events; events are only dispatched by the internal timer when the simulation is started automatically upon creation or by calling simulation.restart(). The natural number of ticks when the simulation is started is ⌈log(alphaMin) / log(1 - alphaDecay)⌉; by default, this is 300.
simulation.nodes() method
simulation.nodes(nodes) sets the simulation's nodes to the specified array of objects if nodes is specified, initializing their positions and velocities if necessary, and then re-initializes any bound forces; returns the simulation. If nodes is not specified, returns the simulation's array of nodes. This function is impure; it mutates the passed-in nodes to assign index, x, y, vx, and vy properties. The simulation initializes these properties: index (zero-based index into nodes), x (current x-position), y (current y-position), vx (current x-velocity), vy (current y-velocity). If either vx or vy is NaN, the velocity is initialized to ⟨0,0⟩. If either x or y is NaN, the position is initialized in a phyllotaxis arrangement for deterministic, uniform distribution. To fix a node in a given position, specify fx (node's fixed x-position) and fy (node's fixed y-position). At the end of each tick, a node with defined node.fx has node.x reset to this value and node.vx set to zero; likewise for node.fy. To unfix a node, set node.fx and node.fy to null or delete these properties.
simulation.alpha() method
simulation.alpha(alpha) sets the current alpha to the specified number in the range [0,1] and returns the simulation, if alpha is specified. If alpha is not specified, returns the current alpha value, which defaults to 1. Alpha is roughly analogous to temperature in simulated annealing. It decreases over time as the simulation cools down. When alpha reaches alphaMin, the simulation stops.
simulation.alphaMin() method
simulation.alphaMin(min) sets the minimum alpha to the specified number in the range [0,1] and returns the simulation, if min is specified. If min is not specified, returns the current minimum alpha value, which defaults to 0.001. The simulation's internal timer stops when the current alpha is less than the minimum alpha. The default alpha decay rate of ~0.0228 corresponds to 300 iterations.
simulation.alphaDecay() method
simulation.alphaDecay(decay) sets the alpha decay rate to the specified number in the range [0,1] and returns the simulation, if decay is specified. If decay is not specified, returns the current alpha decay rate, which defaults to 0.0228… = 1 - pow(0.001, 1 / 300) where 0.001 is the default minimum alpha. The alpha decay rate determines how quickly the current alpha interpolates towards the target alpha. Higher decay rates cause the simulation to stabilize more quickly but risk getting stuck in a local minimum; lower values cause the simulation to take longer to run but typically converge on a better layout. To have the simulation run forever at the current alpha, set the decay rate to zero; alternatively, set a target alpha greater than the minimum alpha.
simulation.alphaTarget() method
simulation.alphaTarget(target) sets the current target alpha to the specified number in the range [0,1] and returns the simulation, if target is specified. If target is not specified, returns the current target alpha value, which defaults to 0.
simulation.velocityDecay() method
simulation.velocityDecay(decay) sets the velocity decay factor to the specified number in the range [0,1] and returns the simulation, if decay is specified. If decay is not specified, returns the current velocity decay factor, which defaults to 0.4. The decay factor is akin to atmospheric friction; after the application of any forces during a tick, each node's velocity is multiplied by 1 - decay. As with lowering the alpha decay rate, less velocity decay may converge on a better solution but risks numerical instabilities and oscillation.
simulation.force() method
simulation.force(name, force) assigns the force for the specified name and returns the simulation if force is specified. If force is not specified, returns the force with the specified name, or undefined if there is no such force. By default, new simulations have no forces. To remove the force with the given name, pass null as the force.
simulation.force() example with multiple forces
const simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody())
.force("link", d3.forceLink(links))
.force("center", d3.forceCenter());
simulation.force() example removing a force
simulation.force("charge", null);
simulation.find() method
simulation.find(x, y, radius) returns the node closest to the position ⟨x,y⟩ with the given search radius. If radius is not specified, it defaults to infinity. If there is no node within the search area, returns undefined.
simulation.randomSource() method
simulation.randomSource(source) sets the function used to generate random numbers if source is specified; this should be a function that returns a number between 0 (inclusive) and 1 (exclusive). If source is not specified, returns the simulation's current random source which defaults to a fixed-seed linear congruential generator.
simulation.on() method event types
simulation.on(typenames, listener) registers event listeners for the simulation. The typenames is a string containing one or more typename separated by whitespace. Each typename is a type, optionally followed by a period (.) and a name, such as tick.foo and tick.bar; the name allows multiple listeners to be registered for the same type. The type must be one of the following: tick (after each tick of the simulation's internal timer) or end (after the simulation's timer stops when alpha < alphaMin). Note that tick events are not dispatched when simulation.tick() is called manually; events are only dispatched by the internal timer and are intended for interactive rendering of the simulation.
Force function signature
A force is a function with signature force(alpha) that applies this force, optionally observing the specified alpha. Typically, the force is applied to the array of nodes previously passed to force.initialize(), however, some forces may apply to a subset of nodes, or behave differently.
force.initialize() method
force.initialize(nodes) supplies the array of nodes and random source to this force. This method is called when a force is bound to a simulation via simulation.force() and when the simulation's nodes change via simulation.nodes(). A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
Custom force example moving nodes towards origin
function force(alpha) {
for (let i = 0, n = nodes.length, node, k = alpha * 0.1; i < n; ++i) {
node = nodes[i];
node.vx -= node.x * k;
node.vy -= node.y * k;
}
}
Force simulation physics model
A force simulation implements a velocity Verlet numerical integrator for simulating physical forces on particles (nodes). The simulation assumes a constant unit time step Δt = 1 for each step and a constant unit mass m = 1 for all particles. As a result, a force F acting on a particle is equivalent to a constant acceleration a over the time interval Δt, and can be simulated simply by adding to the particle's velocity, which is then added to the particle's position.
Custom force behavior
Forces typically read the node's current position ⟨x,y⟩ and then mutate the node's velocity ⟨vx,vy⟩. Forces may also peek ahead to the anticipated next position of the node, ⟨x + vx,y + vy⟩; this is necessary for resolving geometric constraints through iterative relaxation. Forces may also modify the position directly, which is sometimes useful to avoid adding energy to the simulation, such as when recentering the simulation in the viewport.