在D3可视化项目中,树结构是最常用的数据展示形式之一,用户点击树节点后实时在页面指定区域显示节点名称,是提升交互体验的重要功能。该功能的核心逻辑包含树布局构建、节点事件绑定、文本动态更新三个部分,下面逐步讲解实现过程。

功能实现前置准备
首先需要准备基础的HTML结构和树结构数据,树数据采用嵌套对象格式,每个节点包含name属性和可选的children属性。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>D3树节点点击显示名称</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.node-text {
font-size: 12px;
}
#display-text {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
width: 300px;
font-size: 16px;
}
</style>
</head>
<body>
<svg width="600" height="400"></svg>
<div id="display-text">点击树节点查看名称</div>
</body>
</html>
对应的树结构测试数据如下:
const treeData = {
name: "根节点",
children: [
{
name: "子节点1",
children: [
{ name: "子节点1-1" },
{ name: "子节点1-2" }
]
},
{
name: "子节点2",
children: [
{ name: "子节点2-1" }
]
}
]
};
树布局初始化与节点绘制
接下来使用D3的树布局生成器创建树结构,将节点和连线绘制到SVG画布上。首先需要定义树布局的尺寸和层级间隔,然后生成树的层级数据。
// 选择SVG画布
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
// 创建树布局,设置尺寸
const treeLayout = d3.tree()
.size([width - 100, height - 100]);
// 生成层级数据
const root = d3.hierarchy(treeData);
const treeDataProcessed = treeLayout(root);
// 创建节点分组
const nodes = svg.selectAll(".node")
.data(treeDataProcessed.descendants())
.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x}, ${d.y})`);
// 绘制节点圆形
nodes.append("circle")
.attr("r", 10)
.attr("fill", "#69b3a2");
// 绘制节点文本
nodes.append("text")
.attr("class", "node-text")
.attr("dy", -15)
.attr("text-anchor", "middle")
.text(d => d.data.name);
// 绘制连线
const links = svg.selectAll(".link")
.data(treeDataProcessed.links())
.enter()
.append("path")
.attr("class", "link")
.attr("d", d3.linkVertical()
.x(d => d.x)
.y(d => d.y))
.attr("fill", "none")
.attr("stroke", "#ccc")
.attr("stroke-width", 2);
绑定节点点击事件动态更新文本
核心的交互逻辑是给每个树节点绑定点击事件,当点击节点时,获取当前节点的name属性,然后更新页面中显示文本的容器内容。
// 给所有节点分组绑定点击事件
nodes.on("click", function(event, d) {
// 阻止事件冒泡
event.stopPropagation();
// 获取当前节点的名称
const nodeName = d.data.name;
// 更新显示文本的容器内容
d3.select("#display-text")
.text(`当前选中节点名称:${nodeName}`);
// 可选:高亮当前点击的节点
d3.select(this).select("circle")
.attr("fill", "#ff7f0e");
// 恢复其他节点的颜色
nodes.select("circle")
.filter(function(otherD) {
return otherD !== d;
})
.attr("fill", "#69b3a2");
});
常见问题与注意事项
事件绑定失效问题
如果后续需要动态更新树数据,不要使用enter()之后的直接绑定,建议使用事件委托的方式,将点击事件绑定到父容器上,通过事件目标判断点击的是否为节点。
// 事件委托方式绑定点击事件
svg.on("click", function(event) {
const target = event.target;
// 判断点击的是节点圆形
if (target.tagName === "circle" && d3.select(target.parentNode).classed("node")) {
const d = d3.select(target.parentNode).datum();
const nodeName = d.data.name;
d3.select("#display-text")
.text(`当前选中节点名称:${nodeName}`);
}
});
文本更新不生效问题
如果更新文本时没有反应,首先检查选择器是否正确,确认#display-text容器存在于DOM中。其次检查节点数据是否正确绑定,通过console.log(d.data)打印节点数据确认name属性存在。
样式冲突问题
如果节点文本和连线样式异常,检查SVG元素的定位是否正确,树布局的size设置是否和SVG画布尺寸匹配,避免节点超出画布范围。
完整运行示例
将上述代码整合后,完整可运行的代码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>D3树节点点击显示名称</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.node-text {
font-size: 12px;
}
#display-text {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
width: 300px;
font-size: 16px;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2;
}
</style>
</head>
<body>
<svg width="600" height="400"></svg>
<div id="display-text">点击树节点查看名称</div>
<script>
const treeData = {
name: "根节点",
children: [
{
name: "子节点1",
children: [
{ name: "子节点1-1" },
{ name: "子节点1-2" }
]
},
{
name: "子节点2",
children: [
{ name: "子节点2-1" }
]
}
]
};
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
const treeLayout = d3.tree()
.size([width - 100, height - 100]);
const root = d3.hierarchy(treeData);
const treeDataProcessed = treeLayout(root);
const nodes = svg.selectAll(".node")
.data(treeDataProcessed.descendants())
.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x}, ${d.y})`);
nodes.append("circle")
.attr("r", 10)
.attr("fill", "#69b3a2");
nodes.append("text")
.attr("class", "node-text")
.attr("dy", -15)
.attr("text-anchor", "middle")
.text(d => d.data.name);
svg.selectAll(".link")
.data(treeDataProcessed.links())
.enter()
.append("path")
.attr("class", "link")
.attr("d", d3.linkVertical()
.x(d => d.x)
.y(d => d.y));
nodes.on("click", function(event, d) {
event.stopPropagation();
const nodeName = d.data.name;
d3.select("#display-text")
.text(`当前选中节点名称:${nodeName}`);
d3.select(this).select("circle")
.attr("fill", "#ff7f0e");
nodes.select("circle")
.filter(function(otherD) {
return otherD !== d;
})
.attr("fill", "#69b3a2");
});
</script>
</body>
</html>