You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>

修改说明

  1. 修正节点关联逻辑:原代码用字符串索引匹配节点导致错误,改为通过desc属性精准匹配。
  2. 添加z轴力模拟:手动实现3D空间内的节点斥力和连线拉力,让z坐标参与力导向计算。
  3. 透视投影转换:将3D坐标转为2D屏幕坐标,同时把z值映射到节点大小、透明度和连线粗细,实现近大远小的3D视觉效果。
  4. 移除无效属性:删除SVG不支持的cz、z1/z2属性。

关键注意事项

  • 如果需要真正的3D交互(如视角旋转),SVG伪3D方案有局限,建议结合WebGL渲染库(如Three.js)使用D3的力布局数据。
  • D3 v3已停止维护,升级到v4+可获得更好的扩展性和性能。

内容的提问来源于stack exchange,提问作者Rohit Chakraborty

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 14:01:18