Completely rebuilt the force-directed layout system from the ground up to solve cluster collapse issues.
- Pure physics: repulsion (inverse-square), springs (Hooke's law), centering
- No complex heuristics or fragile anchoring
- ~800 lines → ~1000 lines of clean, documented code
Before: All nodes started at canvas center, causing initial collapse After: Disconnected clusters start spatially separated:
- Main cluster: center of canvas
- Other clusters: orbit around periphery (55% radius)
- Each cluster internally organized in concentric rings by degree
Key fix: Nodes from different clusters repel 2.5× stronger than same-cluster pairs.
- Prevents clusters from drifting together during simulation
- No post-processing needed to keep them apart
- Extends repulsion distance cutoff for cross-cluster pairs
Progressive force adjustment across simulation:
- Early phase (0-30%): Repulsion ×1.4, Springs ×0.7 → Spread out
- Middle phase (30-70%): Balanced forces → Stable movement
- Late phase (70-100%): Repulsion ×0.8, Springs ×1.2, Center ×1.0 → Clean settling
After simulation completes, 8 passes of collision detection:
- Respects actual node dimensions + labels
- Pushes apart any remaining overlaps
- No radial relaxation or cluster anchoring needed
- ❌
computeAutoSpreadMultiplier- adaptive scaling caused unpredictability - ❌
applyClusterAnchors- post-simulation anchoring caused the collapse - ❌
radialRelaxation- unnecessary with proper initialization - ❌ Adaptive iteration boosting - simpler fixed presets work better
- ❌ Complex multi-phase cooling schedules - simple linear alpha decay
✅ Layout scale presets (Compact/Balanced/Spacious)
✅ Scale multiplier (0.5–2.5×)
✅ Iteration presets (Fast/Balanced/Deep)
✅ Integration with store autoLayoutSettings
✅ All other layout algorithms (hierarchical, radial, grid, circular)
FORCE_LAYOUT_DEFAULTS = {
// Stronger repulsion to prevent overlap
repulsionStrength: 200000, // was 5200
// Longer target distances for spaciousness
targetLinkDistance: 250, // was springLength: 720
minNodeDistance: 150, // was minLinkDistance: 60
// More iterations for better convergence
iterations: 300, // was 220
// Simpler damping
damping: 0.85, // was 0.6
alphaDecay: 0.015, // was 0.02
}No API changes - it's a drop-in replacement:
// Still works the same
applyAutoLayoutToActiveGraph();
// Force Simulation Tuner still works
// (though slider values map to new parameters)
// Auto-graph generation still works
generateGraph(data, graphId, ...);- Test with single connected graph (5-20 nodes)
- Test with multiple disconnected clusters
- Test with large graphs (30+ nodes)
- Test with layout scale slider
- Test Force Simulation Tuner
- Test repeated auto-layout (should be stable)
- Existing graphs won't change (positions are saved)
- Re-running auto-layout will use new algorithm
- May need to adjust Force Tuner slider values if you had custom presets
- Layouts should be more spacious and stable by default
AUTO_LAYOUT_GUIDE.md- Update "Adaptive Force Layout" sectionFORCE_SIMULATION_TUNER.md- Note algorithm change- Both updated to reflect simpler, cleaner design