Skip to main content

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:

onSimulationCreated={(simulation) => ...}Live
alpha1.000

Recipes

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()));
}}
/>;
Don't replace the tick handler

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.

Lifecycle

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.