Back to TIL
March 2026
posted on 03.28.2026

D3 Force Simulation Tuning for Knowledge Graphs

When building a force-directed knowledge graph with D3.js, the default force settings cluster everything into an unreadable blob. The key parameters to tune for readable cluster separation:

const simulation = d3.forceSimulation(nodes)
  .force("link", d3.forceLink(links).id(d => d.id).distance(60).strength(0.3))
  .force("charge", d3.forceManyBody().strength(-120).distanceMax(400))
  .force("center", d3.forceCenter(width / 2, height / 2).strength(0.05))
  .force("collision", d3.forceCollide().radius(d => nodeRadius[d.type] + 4).strength(0.7))
  .force("x", d3.forceX(width / 2).strength(0.03))
  .force("y", d3.forceY(height / 2).strength(0.03))
  .alphaDecay(0.02)

What each does:

  • Charge strength (-120): Repulsive force between all nodes. More negative = more spacing. distanceMax(400) caps the influence range so distant clusters do not push each other to infinity.
  • Link distance (60) + strength (0.3): How far apart linked nodes want to be. Lower strength makes the links springier, letting clusters breathe.
  • Center strength (0.05): Very weak pull toward center. Prevents drift without collapsing the graph.
  • Collision radius (+4 padding): Physical collision boundary. strength(0.7) means nodes push each other away firmly but not rigidly.
  • forceX/forceY (0.03): Gentle gravity toward center on each axis independently. Works with center force to keep things on screen.
  • alphaDecay (0.02): Slower cooldown. Default is 0.0228. Lower values let the simulation settle longer, producing better final positions.

The defaults (charge: -30, linkDistance: 30, no forceX/Y) are designed for small graphs. For 500+ nodes with multiple types, you need 3-4x stronger repulsion and explicit centering forces.

No reactions yet

in Naperville, IL
Last visitor from Mitaka, Japan