D3.js Create Force Directed Map (V4)
_Reference for this article: https://blog.csdn.net/juzipidemimi/article/details/100787059
_Creating a force-oriented map requires three things:
- Simulation simulation system
- node
- Power
_There are many nodes and various types of forces in the simulation system. Through the force to control the movement of nodes, each node moves continuously under the action of multiple forces until the system tends to balance. Many tick events occur in the middle. Each tick, the simulation system updates the position of the nodes, and the energy (alpha) of the system decreases gradually until a certain value (alphaMin) is reached, and the entire graph stops moving.
1. Nodes_Node is an array of objects with unlimited attributes. You can add a variety of information to control the rendering of the graph (e.g., color size, etc.). Each tick updates the position (x,y) and speed (vx, vy) of the nodes.
2. ForceForce drives the movement of the whole system. You can add force to the system and control the movement of nodes.
Common forces:
- d3.forceCenter([x,
y]: Center force, which pushes all nodes toward the center of the graph (given a point) and the default coordinate is [0,0]. When applied, the relative positions of all nodes remain unchanged - d3.forceCollide([radius]):collision, collision force that makes two nodes pop up like a spring ball when they touch
- d3.forceLink([links]): Connectivity, pulling the nodes together, as if there was a spring between the nodes
- d3.forceManyBody(): Repulsion, similar to the repulsion of charged electrons, pushes all nodes away from each other
- d3.forceX,d3.forceY: Positional force, which pushes a node to the desired point (x,y), unlike forceCenter, which changes the relative position of the node
_defines the relationship between nodes, defined by the connection between nodes. links are also essential for creating a forceLink.
var nodes = [ {"id": "Alice"}, {"id": "Bob"}, {"id": "Carol"} ]; var links = [ {"source": 0, "target": 1}, // Alice → Bob {"source": 1, "target": 2} // Bob → Carol ];
_Each object must contain a source target attribute, which represents the start and end points of an edge. The value of the attribute is the id of the node, which defaults to the index of the node in the array. You can also customize the id getter:
d3.forceLink().id(d => d.id)4. Create a force map 4.1 Data
var nodes = [ { name: "0"}, { name: "1"}, { name: "2"}, { name: "3"}, { name: "4"}, { name: "5"}, { name: "6"} ]; // Sorce's subscript to the nodes array element indicates the starting node of the connection // The target's subscript to the nodes array element indicates the end node of the connection var edges = [ { source : 0 , target: 1 } , { source : 0 , target: 2 } , { source : 0 , target: 3 } , { source : 1 , target: 4 } , { source : 1 , target: 5 } , { source : 1 , target: 6 }];4.2 Create svg container
const svg = d3.select('body') .append('svg') .attr('width', width) .attr('height', height) .attr('class', 'chart')4.3 Create a simulation system
const simulation = d3.forceSimulation(nodes) .force('charge', d3.forceManyBody()) .force('link', d3.forceLink(links)) .force('x', d3.forceX(width / 2)) .force('y', d3.forceY(height / 2))4.4 Set some properties of repulsive and connective forces
simulation.alphaDecay(0.05) // Decay factor, the larger the value, the faster the chart will stabilize simulation.force('charge') .strength(-50) // Repulsion strength, positive values attract each other, negative values repel each other simulation.force('link') .id(d => d.id) // set id getter .distance(0) // Connection Distance .strength(1) // Connectivity strength 0 ~ 1 .iterations(1) // Number of iterations4.5 Draw Edges
Edge drawing needs to be done first, because there is no such attribute as z-index in svg to set the level, then drawing overrides drawing first
const simulationLinks = svg.append('g') .selectAll('line') .data(links) .enter() .append('line') .attr('stroke', d => '#c2c2c2')4.6 Draw nodes and set drag events
_At the start of each drag, set alphaTarget and restart the simulation system. The value of alpha decreases from alphaTarget to alphaMin, so if you set the value of alphaTarget smaller than alphaMin, it will get stuck and will not continue to update.
const simulationNodes = svg.append('g') .attr('fill', '#fff') .attr('stroke', '#000') .attr('stroke-width', 1.5) .selectAll('circle') .data(nodes) .enter() .append('circle') .attr('r', 3.5) .attr('fill', d => d.children ? null : '#000') //Leaf node with black bottom and white bottom and parent node with black bottom .attr('stroke', d => d.children ? null : '#fff') .call(d3.drag() .on('start', started) .on('drag', dragged) .on('end', ended) ) function started(d) { if (!d3.event.active) { simulation.alphaTarget(.2).restart() } d.fx = d.x // fx fy indicates where the next node will be fixed // Each tick ends with node.x set to node.fx and node.vx set to 0 d.fy = d.y } function dragged(d) { d.fx = d3.event.x d.fy = d3.event.y } function ended(d) { if (!d3.event.active) { // Set to 0 to stop directly, if greater than alphaMin it will stop gradually simulation.alphaTarget(0) } d.fx = null d.fy = null }4.7 Final set tick event
_Although the simulation system updates the position of the node (only sets the x y attribute of the nodes object), it does not change to the coordinate representation of the internal elements of svg, which requires our own operation
simulation.on('tick', ticked) function ticked() { simulationLinks.attr('x1', d => d.source.x ) .attr('y1', d => d.source.y ) .attr('x2', d => d.target.x ) .attr('y2', d => d.target.y ) simulationNodes.attr('cx', d => d.x ) .attr('cy', d => d.y ) }