如何在D3.js中添加z坐标实现3D力导向图?
如何用D3.js实现3D力导向图
核心问题分析
- D3 v3的
d3.layout.force()是纯2D力导向布局,仅支持x、y轴的物理模拟,不会处理z轴的力计算,你设置的z坐标不会被布局逻辑更新。 - SVG是2D绘图标准,不存在
cz(节点z坐标)、z1/z2(连线z坐标)这类属性,你添加的这些属性对视觉渲染没有任何作用。
解决方案
要实现3D力导向图,有两种可行路径:
路径1:使用支持3D的力导向布局库
D3原生没有3D力布局,但可以使用第三方扩展(需升级到D3 v4及以上版本),这类扩展在原生2D力布局基础上扩展了z轴的力模拟,支持完整的3D物理计算。
路径2:在2D SVG中模拟3D视觉效果(伪3D)
如果不想升级D3版本,可以通过透视投影将3D坐标转换为2D屏幕坐标,同时把z值映射到节点的大小、透明度等视觉属性,营造3D效果。以下是针对你现有代码的修改示例:
修改后的完整代码
<!DOCTYPE html> <meta charset="utf-8"> <style> .node { fill: blue; stroke: black; stroke-width: 2px; } .node.visited { fill: red; } .link { stroke-width: 2px; } </style> <body> <script src="https://d3js.org/d3.v3.min.js"></script> <script> var width = 640; var height = 480; // 透视参数:视距,控制透视强度 var focalLength = 300; // 透视中心 var centerX = width / 2; var centerY = height / 2; var links = [ { source: 'Rohit', target: 'Deep' }, { source: 'Deep', target: 'Deepa' }, { source: 'Deepa', target: 'Rohit' }, ]; var nodes = [ { "id": 1, "desc": "Rohit", "x": 121.028, "y": 116.316, "z": 59.367 }, { "id": 2, "desc": "Deep", "x": 12.102, "y": 116.316, "z": 5.936 }, { "id": 3, "desc": "Deepa", "x": 12.102, "y": 11.631, "z": 5.936 } ]; // 修正节点关联逻辑:按desc属性匹配节点 links.forEach(function(link) { link.source = nodes.find(n => n.desc === link.source); link.target = nodes.find(n => n.desc === link.target); }); var svg = d3.select('body').append('svg') .attr('width', width) .attr('height', height); var defs = svg.append('defs'); var gradient = defs.append('linearGradient') .attr('id', 'svgGradient') .attr('x1', '0%') .attr('x2', '10%') .attr('y1', '0%') .attr('y2', '10%'); gradient.append('stop') .attr('class', 'start') .attr('offset', '0%') .attr('stop-color', 'red') .attr('stop-opacity', 1); gradient.append('stop') .attr('class', 'end') .attr('offset', '100%') .attr('stop-color', 'blue') .attr('stop-opacity', 1); // 初始化力布局,手动扩展z轴力模拟 var force = d3.layout.force() .size([width, height]) .nodes(nodes) .links(links) .on("tick", tick) .linkDistance(300) .start(); // 为节点添加z轴速度属性 nodes.forEach(d => { d.vx = 0; d.vy = 0; d.vz = 0; }); var link = svg.selectAll('.link') .data(links) .enter().append('line') .attr('class', 'link') .attr('stroke', 'url(#svgGradient)'); var node = svg.selectAll('.node') .data(nodes) .enter().append('circle') .attr('class', 'node') .on("click", clicked) .call(force.drag); // 透视投影函数:3D坐标转2D屏幕坐标 function project(x, y, z) { var scale = focalLength / (focalLength + z); return { x: centerX + x * scale, y: centerY + y * scale, scale: scale }; } function clicked(event, d) { if (event.defaultPrevented) return; d3.select(this).transition() .style("fill", "black") .attr("r", width * 0.2) .transition() .attr("r", width * 0.03) .transition() .style("fill", "blue") } // 重写tick函数,添加z轴力模拟与透视投影 function tick(e) { var k = 0.1 * e.alpha; // 力衰减系数 // 1. 计算节点间3D斥力 nodes.forEach(function(o, i) { nodes.slice(i + 1).forEach(function(p) { var dx = o.x - p.x, dy = o.y - p.y, dz = o.z - p.z, dist = Math.sqrt(dx * dx + dy * dy + dz * dz); if (dist < 10) dist = 10; // 避免节点重叠 var force = k / dist; o.vx += dx * force; o.vy += dy * force; o.vz += dz * force; p.vx -= dx * force; p.vy -= dy * force; p.vz -= dz * force; }); }); // 2. 计算连线的3D拉力 links.forEach(function(d) { var dx = d.source.x - d.target.x, dy = d.source.y - d.target.y, dz = d.source.z - d.target.z, dist = Math.sqrt(dx * dx + dy * dy + dz * dz), diff = d.linkDistance - dist; var force = diff * k / dist; d.source.vx += dx * force; d.source.vy += dy * force; d.source.vz += dz * force; d.target.vx -= dx * force; d.target.vy -= dy * force; d.target.vz -= dz * force; }); // 3. 更新节点3D坐标 nodes.forEach(function(d) { d.x += d.vx; d.y += d.vy; d.z += d.vz; // 限制z轴范围,避免透视异常 d.z = Math.max(-focalLength + 10, Math.min(focalLength * 2, d.z)); // 速度衰减 d.vx *= 0.9; d.vy *= 0.9; d.vz *= 0.9; }); // 4. 应用透视投影,更新2D视觉元素 node.each(function(d) { var p = project(d.x, d.y, d.z); d.screenX = p.x; d.screenY = p.y; d.screenScale = p.scale; }) .attr('cx', d => d.screenX) .attr('cy', d => d.screenY) .attr('r', d => width * 0.03 * d.screenScale) // z值越大,节点越大 .style('opacity', d => Math.max(0.3, d.screenScale)); // z值越小,透明度越低 link.attr('x1', d => d.source.screenX) .attr('y1', d => d.source.screenY) .attr('x2', d => d.target.screenX) .attr('y2', d => d.target.screenY) .style('stroke-width', d => 2 * Math.min(d.source.screenScale, d.target.screenScale)); // 连线粗细随z值变化 } </script> </body>
修改说明
- 修正节点关联逻辑:原代码用字符串索引匹配节点导致错误,改为通过
desc属性精准匹配。 - 添加z轴力模拟:手动实现3D空间内的节点斥力和连线拉力,让z坐标参与力导向计算。
- 透视投影转换:将3D坐标转为2D屏幕坐标,同时把z值映射到节点大小、透明度和连线粗细,实现近大远小的3D视觉效果。
- 移除无效属性:删除SVG不支持的
cz、z1/z2属性。
关键注意事项
- 如果需要真正的3D交互(如视角旋转),SVG伪3D方案有局限,建议结合WebGL渲染库(如Three.js)使用D3的力布局数据。
- D3 v3已停止维护,升级到v4+可获得更好的扩展性和性能。
内容的提问来源于stack exchange,提问作者Rohit Chakraborty
相关产品推荐
相关产品推荐

