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

如何在D3.js Tidy Tree图中查找选中节点的关联节点

如何获取Tidy Tree中选中节点的所有关联节点?

问题背景

我现有一个基于D3.js实现的Tidy Tree图表,代码如下:

JavaScript代码

const data = {
  "name": "tree",
  "children": [{
    "name": "A",
    "children": [{
        "name": "B",
        "children": [{
          "name": "C",
          "size": 3938
        }, ]
      },
      {
        "name": "H",
        "children": [{
            "name": "D",
            "size": 3534
          },
          {
            "name": "E",
            "size": 5731
          },
          {
            "name": "F",
            "size": 7840
          },
        ]
      },
    ]
  }]
}

const width = 928;

// Compute the tree height; this approach will allow the height of the
// SVG to scale according to the breadth (width) of the tree layout.
const root = d3.hierarchy(data);
const dx = 10;
const dy = width / (root.height + 1);

// Create a tree layout.
const tree = d3.tree().nodeSize([dx, dy]);

// Sort the tree and apply the layout.
root.sort((a, b) => d3.ascending(a.data.name, b.data.name));
tree(root);

// Compute the extent of the tree. Note that x and y are swapped here
// because in the tree layout, x is the breadth, but when displayed, the
// tree extends right rather than down.
let x0 = Infinity;
let x1 = -x0;
root.each(d => {
  if (d.x > x1) x1 = d.x;
  if (d.x < x0) x0 = d.x;
});

// Compute the adjusted height of the tree.
const height = x1 - x0 + dx * 2;

const svg = d3.select("svg")
  .attr("width", width)
  .attr("height", height)
  .attr("viewBox", [-dy / 3, x0 - dx, width, height])
  .attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");

const link = svg.append("g")
  .attr("fill", "none")
  .attr("stroke", "#555")
  .attr("stroke-opacity", 0.4)
  .attr("stroke-width", 1.5)

link
  .selectAll()
  .data(root.links())
  .join("path")
  .attr("d", d3.linkHorizontal()
    .x(d => d.y)
    .y(d => d.x));

const node = svg.append("g")
  .attr("stroke-linejoin", "round")
  .attr("stroke-width", 3)
  .selectAll()
  .data(root.descendants())
  .join("g")
  .attr("transform", d => `translate(${d.y},${d.x})`)

node.append("circle")
  .attr("fill", d => d.children ? "#555" : "#999")
  .attr("r", 5.5)

node.append("text")
  .attr("dy", "0.31em")
  .attr("x", d => d.children ? -6 : 6)
  .attr("text-anchor", d => d.children ? "end" : "start")
  .text(d => d.data.name)
  .clone(true).lower()
  .attr("stroke", "white");

d3.selectAll("circle").on("contextmenu", (event) => {
  this.selectedNode = event.srcElement.__data__.data
});

const connectNodes = (t, f) => {
  let to = null,
    fr = null;
  node.each(d => {
    if (d.data.name === t) to = d;
    if (d.data.name === f) fr = d;
  });
  if (to && fr) {
    link.append("path")
      .attr("d", "M" + to.y + "," + to.x + "L" + fr.y + "," + fr.x)
      .attr("fill", "none")
      .attr("stroke", "red");
  }
};
connectNodes("B", "D");

HTML代码

<!doctype html>
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.js"></script>
</head>

<body>
  <svg></svg>
</body>

</html>

需求:当右键选中某个节点(例如节点B)时,获取该节点的所有关联节点——包括父节点(A)、子节点(C)以及通过connectNodes添加的自定义连接节点(D)。


解决方案

要实现这个功能,需要分三步处理:维护自定义连接关系、编写关联节点查询函数、整合到选中事件中。

1. 维护自定义连接关系

原代码中connectNodes仅绘制连线,未存储连接关系。我们需要新增一个集合来记录所有自定义连接,方便后续查询:

// 新增:存储所有自定义双向连接(避免重复)
const customConnections = new Set();

// 修改connectNodes函数,同时记录连接关系
const connectNodes = (t, f) => {
  let to = null,
    fr = null;
  node.each(d => {
    if (d.data.name === t) to = d;
    if (d.data.name === f) fr = d;
  });
  if (to && fr) {
    link.append("path")
      .attr("d", "M" + to.y + "," + to.x + "L" + fr.y + "," + fr.x)
      .attr("fill", "none")
      .attr("stroke", "red");
    // 用排序后的字符串存储,确保"A-B"和"B-A"视为同一连接
    const pair = [t, f].sort().join("-");
    customConnections.add(pair);
  }
};
connectNodes("B", "D");

2. 编写获取关联节点的函数

该函数需要遍历目标节点的父节点、子节点,同时查询自定义连接中的关联节点:

// 获取指定节点的所有关联节点
const getRelatedNodes = (targetNodeName) => {
  const related = new Set();
  let targetNode = null;

  // 先找到目标节点的d3.hierarchy对象
  root.each(d => {
    if (d.data.name === targetNodeName) {
      targetNode = d;
    }
  });

  if (!targetNode) return [];

  // 1. 添加父节点
  if (targetNode.parent) {
    related.add(targetNode.parent.data.name);
  }

  // 2. 添加子节点
  if (targetNode.children) {
    targetNode.children.forEach(child => {
      related.add(child.data.name);
    });
  }

  // 3. 添加自定义连接的节点
  customConnections.forEach(pair => {
    const [a, b] = pair.split("-");
    if (a === targetNodeName) {
      related.add(b);
    } else if (b === targetNodeName) {
      related.add(a);
    }
  });

  // 转换为数组返回,排除自身
  return Array.from(related).filter(name => name !== targetNodeName);
};

3. 整合到右键选中事件中

修改右键菜单事件,调用getRelatedNodes函数获取并处理关联节点:

d3.selectAll("circle").on("contextmenu", (event) => {
  event.preventDefault(); // 阻止默认右键菜单弹出
  const selectedData = event.srcElement.__data__.data;
  const relatedNodes = getRelatedNodes(selectedData.name);
  
  // 控制台输出结果(可根据需求替换为其他逻辑)
  console.log(`选中节点:${selectedData.name}`);
  console.log("关联节点:", relatedNodes);
  
  // 可选:高亮选中节点和关联节点
  node.selectAll("circle").attr("fill", d => {
    if (relatedNodes.includes(d.data.name) || d.data.name === selectedData.name) {
      return "#ff0000"; // 红色高亮
    }
    return d.children ? "#555" : "#999"; // 恢复默认颜色
  });
});

效果验证

右键点击节点B时,控制台会输出:

选中节点:B
关联节点: ["A", "C", "D"]

同时图表中B、A、C、D节点会被红色高亮,完全符合需求。

内容的提问来源于stack exchange,提问作者morteza mortezaie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 21:55:03