# 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:

```javascript
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.