D3.js 的力导向图基于物理粒子模拟系统,通过以下力学参数实现节点动态平衡:
const simulation = d3.forceSimulation(nodes) .force("link", d3.forceLink(links).id(d => d.id)) .force("charge", d3.forceManyBody().strength(-50)) .force("center", d3.forceCenter(width/2, height/2)) .force("collision", d3.forceCollide().radius(20));
符合标准的 JSON 数据应包含两个核心数组:
{ "nodes": [ {"id": "A", "group": 1, "value": 10}, {"id": "B", "group": 2, "value": 20} ], "links": [ {"source": "A", "target": "B", "strength": 0.8}, {"source": "B", "target": "C", "type": "dependency"} ] }
字段解析表:
| 字段 | 节点/链接 | 说明 |
|————|———–|——————————-|
| id | 节点 | 唯一标识符(必填) |
| group | 节点 | 用于颜色分类 |
| value | 节点 | 决定节点半径大小 |
| source | 链接 | 起始节点ID(与nodes.id对应) |
| target | 链接 | 目标节点ID |
| strength | 链接 | 弹力系数(0-1) |
<div id="graph-container"></div> <script src="https://d3js.org/d3.v7.min.js"></script> <script> // 初始化画布 const width = 800, height = 600; const svg = d3.select("#graph-container") .append("svg") .attr("viewBox", [0, 0, width, height]); // 加载数据 d3.json("your-data.json").then(data => { // 创建力导向模拟 const simulation = d3.forceSimulation(data.nodes) .force("link", d3.forceLink(data.links).id(d => d.id)) .force("charge", d3.forceManyBody().strength(-100)) .force("center", d3.forceCenter(width/2, height/2)); // 绘制连线 const link = svg.append("g") .selectAll("line") .data(data.links) .join("line") .attr("stroke", "#999") .attr("stroke-width", 1.5); // 绘制节点 const node = svg.append("g") .selectAll("circle") .data(data.nodes) .join("circle") .attr("r", d => Math.sqrt(d.value) + 5) .attr("fill", d => d3.schemeCategory10[d.group % 10]) .call(drag(simulation)); // 动态更新 simulation.on("tick", () => { link .attr("x1", d => d.source.x) .attr("y1", d => d.source.y) .attr("x2", d => d.target.x) .attr("y2", d => d.target.y); node .attr("cx", d => d.x) .attr("cy", d => d.y); }); // 拖拽交互 function drag(simulation) { return d3.drag() .on("start", event => { if (!event.active) simulation.alphaTarget(0.3).restart(); event.subject.fx = event.subject.x; event.subject.fy = event.subject.y; }) .on("drag", event => { event.subject.fx = event.x; event.subject.fy = event.y; }) .on("end", event => { if (!event.active) simulation.alphaTarget(0); event.subject.fx = null; event.subject.fy = null; }); } }); </script>
数据预处理:当节点超过 500 个时,建议:
d3.forceCollide
避免节点重叠simulation.alphaDecay(0.05)
控制收敛速度视觉降噪方案:
// 动态透明度 link.attr("stroke-opacity", d => d.strength * 0.8); // 聚焦高亮 node.on("mouseover", function(event, d) { link.style("stroke", l => l.source === d || l.target === d ? "red" : "#ddd"); });
移动端适配:
circle { touch-action: none; /* 禁用浏览器默认手势 */ cursor: grab; }
Q1 连线不显示
检查 JSON 中 source/target 是否与 nodes.id 严格匹配,建议使用唯一标识符验证工具:
data.links.forEach(link => { if (!data.nodes.find(n => n.id === link.source)) console.error("Missing source node:", link.source); });
Q2 节点堆叠
按需调整力学参数:
.force("collision", d3.forceCollide() .radius(d => Math.sqrt(d.value) + 8))
Q3 数据更新策略
采用高效的重绘机制:
function updateData(newData) { // 停止原有模拟 simulation.stop(); // 合并新旧数据 const nodes = [...simulation.nodes(), ...newData.nodes]; const links = [...simulation.force("link").links(), ...newData.links]; // 重启模拟 simulation.nodes(nodes); simulation.force("link").links(links); simulation.alpha(1).restart(); }
数据来源与参考文献