Loading...
The D3Force layout is a force-directed layout based on d3-force. It simulates physical forces (such as attraction, repulsion, collision, etc.) to make the graph reach a stable state with minimal energy.
The main features of this layout are:
The D3Force layout simulates five different forces to achieve automatic layout. Imagine a physical world where these forces act simultaneously and eventually reach equilibrium:
Note: The arrows of different colors in the diagram represent different types of forces. In the actual layout, these forces are invisible and also affected by other forces.
Layout calculation is an iterative process with two key concepts:
Like the "energy" of the layout, it determines how vigorously nodes move:
Controls the number of times forces are applied in each calculation:
Tip: Iterations and alpha value work together. Increasing iterations makes each step more precise, while alpha controls the overall progress.
Property | Description | Type | Default | Required |
---|---|---|---|---|
type | Layout type | string | 'd3-force' | ✓ |
nodeSize | Node size (diameter), for collision detection | number | ((node, index, nodes) => number) | - | |
iterations | Number of force iterations, higher is more precise | number | - | |
onTick | Callback for each iteration, for real-time results | (data: LayoutMapping) => void | - | |
forceSimulation | Custom force simulation, defaults to d3.js method | Simulation<NodeDatum, EdgeDatum> | - | |
randomSource | Function to generate random numbers | () => number | - |
Property | Description | Type | Default | Required |
---|---|---|---|---|
alpha | Current convergence threshold, controls activity | number | 1 | |
alphaMin | Minimum threshold to stop, when alpha < this, stop | number | 0.001 | |
alphaDecay | Decay rate of alpha, [0, 1], 0.028 ≈ 300 iterations | number | 0.028 | |
alphaTarget | Target alpha, system tries to converge to this value | number | 0 | |
velocityDecay | Velocity decay factor, higher means slower movement | number | 0.4 |
Property | Description | Type | Default | Required |
---|---|---|---|---|
link.id | Function to generate edge id | (edge, index, edges) => string | (e) => e.id | |
link.distance | Ideal edge length | number | ((edge, index, edges) => number) | 30 | |
link.strength | Force strength, higher means closer to ideal | number | ((edge, index, edges) => number) | 1 | |
link.iterations | Number of link force iterations | number | 1 |
Property | Description | Type | Default | Required |
---|---|---|---|---|
manyBody.strength | Force strength, negative for repulsion, positive for attraction | number | ((node, index, nodes) => number) | -30 | |
manyBody.theta | Barnes-Hut accuracy, smaller is more accurate | number | 0.9 | |
manyBody.distanceMin | Minimum distance, prevents excessive force | number | 1 | |
manyBody.distanceMax | Maximum distance, beyond which no force is applied | number | Infinity |
Property | Description | Type | Default | Required |
---|---|---|---|---|
center.x | Center x coordinate | number | 0 | |
center.y | Center y coordinate | number | 0 | |
center.strength | Force strength, higher means closer to center | number | 1 |
Property | Description | Type | Default | Required |
---|---|---|---|---|
collide.radius | Collision radius, nodes repel if closer | number | ((node, index, nodes) => number) | 10 | |
collide.strength | Force strength, higher means stronger repulsion | number | 1 | |
collide.iterations | Number of collision iterations | number | 1 |
Property | Description | Type | Default | Required |
---|---|---|---|---|
radial.strength | Force strength, higher means closer to radius | number | ((node, index, nodes) => number) | 0.1 | |
radial.radius | Target radius, nodes are attracted to circle | number | ((node, index, nodes) => number) | 100 | |
radial.x | Center x coordinate | number | 0 | |
radial.y | Center y coordinate | number | 0 |
Property | Description | Type | Default | Required |
---|---|---|---|---|
x.strength | Force strength in x direction | number | ((node, index, nodes) => number) | - | |
x.x | Target x coordinate, nodes attracted here | number | ((node, index, nodes) => number) | - |
Property | Description | Type | Default | Required |
---|---|---|---|---|
y.strength | Force strength in y direction | number | ((node, index, nodes) => number) | - | |
y.y | Target y coordinate, nodes attracted here | number | ((node, index, nodes) => number) | - |
{layout: {type: 'd3-force',collide: {// Prevent nodes from overlapping by specifying a collision radius for each node.radius: (d) => d.size / 2,},},}
See Example - Prevent Node Overlap in Force-Directed Layout
This example shows how to use force-directed layout for team clustering, where nodes of different teams automatically cluster together.
import { Graph } from '@antv/g6';const graph = new Graph({container: 'container',width: 500,height: 250,autoFit: 'view',data: {nodes: [// Team A{ id: 'A1', team: 'A', label: 'A1', size: 30 },{ id: 'A2', team: 'A', label: 'A2', size: 20 },{ id: 'A3', team: 'A', label: 'A3', size: 20 },{ id: 'A4', team: 'A', label: 'A4', size: 20 },// Team B{ id: 'B1', team: 'B', label: 'B1', size: 30 },{ id: 'B2', team: 'B', label: 'B2', size: 20 },{ id: 'B3', team: 'B', label: 'B3', size: 20 },{ id: 'B4', team: 'B', label: 'B4', size: 20 },// Team C{ id: 'C1', team: 'C', label: 'C1', size: 30 },{ id: 'C2', team: 'C', label: 'C2', size: 20 },{ id: 'C3', team: 'C', label: 'C3', size: 20 },{ id: 'C4', team: 'C', label: 'C4', size: 20 },],edges: [// Team A internal connections{ source: 'A1', target: 'A2' },{ source: 'A1', target: 'A3' },{ source: 'A1', target: 'A4' },// Team B internal connections{ source: 'B1', target: 'B2' },{ source: 'B1', target: 'B3' },{ source: 'B1', target: 'B4' },// Team C internal connections{ source: 'C1', target: 'C2' },{ source: 'C1', target: 'C3' },{ source: 'C1', target: 'C4' },// Few connections between teams{ source: 'A1', target: 'B1' },{ source: 'B1', target: 'C1' },],},node: {style: {size: (d) => d.size,fill: (d) => {// Different colors for different teamsconst colors = {A: '#FF6B6B',B: '#4ECDC4',C: '#45B7D1',};return colors[d.team];},labelText: (d) => d.label,labelPlacement: 'center',labelFill: '#fff',},},edge: {style: {stroke: '#aaa',},},layout: {type: 'd3-force',// Configure link force - nodes within the same team are closerlink: {distance: (d) => {// Shorter distance within the same teamif (d.source.team === d.target.team) return 50;// Longer distance between teamsreturn 200;},strength: (d) => {// Stronger connection within the same teamif (d.source.team === d.target.team) return 0.7;// Weaker connection between teamsreturn 0.1;},},// Configure many-body force - control repulsion between nodesmanyBody: {strength: (d) => {// Team leader nodes (ending with 1) have stronger repulsionif (d.label.endsWith('1')) return -100;return -30;},},// Configure collision force - prevent node overlapcollide: {radius: 35,strength: 0.8,},// Configure center force - keep the graph centeredcenter: {strength: 0.05,},},behaviors: ['drag-element-force'],});graph.render();
import { Graph } from '@antv/g6';// Create mock data with nodes from different teamsconst data = {nodes: [// Team A{ id: 'A1', team: 'A', label: 'A1', size: 30 },{ id: 'A2', team: 'A', label: 'A2', size: 20 },{ id: 'A3', team: 'A', label: 'A3', size: 20 },{ id: 'A4', team: 'A', label: 'A4', size: 20 },// Team B{ id: 'B1', team: 'B', label: 'B1', size: 30 },{ id: 'B2', team: 'B', label: 'B2', size: 20 },{ id: 'B3', team: 'B', label: 'B3', size: 20 },{ id: 'B4', team: 'B', label: 'B4', size: 20 },// Team C{ id: 'C1', team: 'C', label: 'C1', size: 30 },{ id: 'C2', team: 'C', label: 'C2', size: 20 },{ id: 'C3', team: 'C', label: 'C3', size: 20 },{ id: 'C4', team: 'C', label: 'C4', size: 20 },],edges: [// Team A internal connections{ source: 'A1', target: 'A2' },{ source: 'A1', target: 'A3' },{ source: 'A1', target: 'A4' },// Team B internal connections{ source: 'B1', target: 'B2' },{ source: 'B1', target: 'B3' },{ source: 'B1', target: 'B4' },// Team C internal connections{ source: 'C1', target: 'C2' },{ source: 'C1', target: 'C3' },{ source: 'C1', target: 'C4' },// Few connections between teams{ source: 'A1', target: 'B1' },{ source: 'B1', target: 'C1' },],};const graph = new Graph({container: 'container',data,node: {style: {size: (d) => d.size,fill: (d) => {// Different colors for different teamsconst colors = {A: '#FF6B6B',B: '#4ECDC4',C: '#45B7D1',};return colors[d.team];},labelText: (d) => d.label,labelPlacement: 'center',labelFill: '#fff',},},edge: {style: {stroke: '#aaa',},},layout: {type: 'd3-force',// Configure link force - nodes within the same team are closerlink: {distance: (d) => {// Shorter distance within the same teamif (d.source.team === d.target.team) return 50;// Longer distance between teamsreturn 200;},strength: (d) => {// Stronger connection within the same teamif (d.source.team === d.target.team) return 0.7;// Weaker connection between teamsreturn 0.1;},},// Configure many-body force - control repulsion between nodesmanyBody: {strength: (d) => {// Team leader nodes (ending with 1) have stronger repulsionif (d.label.endsWith('1')) return -100;return -30;},},// Configure collision force - prevent node overlapcollide: {radius: 35,strength: 0.8,},// Configure center force - keep the graph centeredcenter: {strength: 0.05,},},behaviors: ['drag-element-force'],});graph.render();
Main configuration notes:
link.distance
: Shorter within teams, longer between teamslink.strength
: Stronger within teams, weaker between teamsmanyBody.strength
: Controls repulsion between nodescollide
: Prevents node overlapcenter
: Keeps the layout centeredSee also Customize parameters for different nodes.