
本文详解如何在 d3 v3 中为环形图(donut chart)正确绑定数据,解决 `d.data` 为 `undefined` 的常见问题,并实现可靠的 tooltip 交互逻辑。
在使用 D3 v3 构建环形图时,一个高频陷阱是事件回调中无法访问原始数据——例如 onMouseEnter(d) => console.log(d.data) 输出 undefined。根本原因在于:事件监听器被绑定在父容器(如 <g>)上,而非实际承载数据的 <path> 元素。D3 的 .data() 绑定作用于选中的 DOM 节点集合,而 gElement.on("mouseenter", ...) 中的 d 指向的是 <g> 的绑定数据(此处为空),并非 <path> 的饼图扇形数据。
✅ 正确做法是:将事件监听器直接绑定到已绑定数据的 <path> 元素上。这需要在调用 .data().enter().append("path") 后,保存该选择集(selection)并复用它,而非重复调用 gElement.selectAll("path")(后者返回的是无数据的新选择集)。
以下为修复后的核心逻辑(基于 D3 v3.5.17):
function updateChart(data) {
// 生成饼图布局数据
const pieData = d3.layout.pie()
.startAngle(-(Math.PI / 2) * 5)
.value(d => d[1])
.sort(null)
.padAngle(padAngle)(data);
// 更新已有路径:过渡动画
gElement.selectAll("path")
.transition()
.duration(transitionDuration)
.attrTween("d", arcTween);
// 关键:获取并保存新创建的 path 选择集
const paths = gElement.selectAll("path")
.data(pieData)
.enter()
.append("path");
// 对 paths 集合统一设置属性和事件
paths
.attr("class", "donut_chart")
.attr("fill", d => color(d.data[0])) // ✅ d.data 现在有效!
.attr("d", chartArc)
.each(function(d) { this._current = d; })
.on("mouseenter", onMouseEnter) // ✅ 事件绑定到 path,d 即饼图扇形数据
.on("mouseleave", onMouseLeave);
}
function onMouseEnter(d) {
console.log("Data:", d.data); // → ["high", 3], ["middle", 4], etc.
console.log("Value:", d.value); // → 3, 4, 2
console.log("Index:", d.index); // → 0, 1, 2
}⚠️ 注意事项:
- 避免滥用 event.explicitOriginalTarget.__data__:虽然可临时取数,但属非标准、不可靠且破坏封装的 hack 方式,生产环境严禁使用。
- .each() 中的 this._current = d 是平滑过渡的关键:它为每个 <path> 缓存当前数据状态,供 arcTween 函数插值使用。
- d.data 结构说明:d3.layout.pie() 输出的每个 d 对象包含 data(原始输入项,如 ["high", 3])、value(数值)、startAngle/endAngle 等字段,d.data[0] 即类别名,d.data[1] 即对应值。
- D3 版本适配:本文示例基于 D3 v3;若升级至 D4+,需改用 d3.pie()、d3.arc() 及 selection.join(),API 差异较大,不可直接迁移。
通过将事件监听器精准绑定到数据驱动的 <path> 元素,并确保 data() 调用与事件注册处于同一选择集上下文,即可彻底解决 d.data 丢失问题,为 tooltip、高亮、动态标签等交互功能奠定坚实基础。










