onSimulationCreated
Your escape hatch to raw D3. The callback fires once per simulation instance, right after
it's created, and hands you the underlying
d3-force Simulation — everything the props don't
cover is yours to configure directly.
import type { Simulation } from "d3-force";
<Graph
graph={data}
onSimulationCreated={(simulation) => {
console.log("alpha:", simulation.alpha());
}}
/>;
Live — the buttons below call methods on the captured simulation, and the bar tracks its alpha in real time:
1.000Recipes
Keep a handle for later
const simulationRef = useRef<Simulation<any, any> | null>(null);
<Graph
graph={data}
onSimulationCreated={(simulation) => {
simulationRef.current = simulation;
}}
/>;
// anywhere else in your component:
const reheat = () => simulationRef.current?.alpha(1).restart();
const freeze = () => simulationRef.current?.stop();
Add forces the props don't expose
The props cover link, charge and gravity forces — for anything else, register it yourself. A collision force that prevents node overlap:
import { forceCollide } from "d3-force";
<Graph
graph={data}
onSimulationCreated={(simulation) => {
simulation.force("collide", forceCollide(60));
}}
/>;
Listen to simulation events
Use namespaced listeners (tick.myFeature) so you don't overwrite the library's own
internal tick handler:
<Graph
graph={data}
onSimulationCreated={(simulation) => {
simulation.on("end.analytics", () => console.log("layout settled"));
simulation.on("tick.minimap", () => updateMinimap(simulation.nodes()));
}}
/>;
simulation.on("tick", ...) (without a namespace) would detach the handler that re-renders the
React nodes, freezing the graph. Always use a namespace suffix as shown above.
The simulation is re-created whenever the graph data changes, and the callback
fires again with the new instance — so refs you store stay fresh automatically.