To put it simply: a D3 force simulation cannot and should not be treated as a pure function (meaning a function that consistently returns the same values for the same inputs).
The rationale behind this is that a simulation, much like a physical system, progresses in a chaotic manner, heavily influenced by the initial interactions that took place. By its very essence, a D3 force simulation is regarded as nondeterministic.
This can be easily exemplified with a basic simulation:
const nodes = d3.range(5).map(() => ({
x: 100,
y: 100
}));
const simulation = d3.forceSimulation(nodes)
.force("collide", d3.forceCollide(10))
.stop()
.tick(300)
console.log(nodes.map(d => d.x))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
If we execute that snippet multiple times, we will observe varying outcomes. For instance, the x
coordinates of the nodes may differ:
1st time: [120.1198, 110.0542, 100.2805, 89.5732, 79.972]
2nd time: [67.6898, 83.854, 99.5493, 116.7441, 132.1625]
3rd time: [133.1773, 116.5792, 100.4626, 82.8064, 66.9742]
etc...
You can verify this yourself by clicking the Run code snippet button repeatedly.
In light of this, if you anticipate a D3 force simulation to function akin to a pure function, you are employing an unsuitable approach for the task at hand.