
本文详解如何在 d3 v3 中为环形图(donut chart)正确绑定数据,解决 `d.data` 为 `undefined` 的常见问题,并实现可靠的 tooltip 交互逻辑。
在使用 D3 v3 构建环形图时,一个高频陷阱是事件回调中无法访问原始数据——例如 onMouseEnter(d) => console.log(d.data) 输出 undefined。根本原因在于:事件监听器被绑定在父容器(如
✅ 正确做法是:将事件监听器直接绑定到已绑定数据的
以下为修复后的核心逻辑(基于 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 是平滑过渡的关键:它为每个
缓存当前数据状态,供 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 差异较大,不可直接迁移。
通过将事件监听器精准绑定到数据驱动的










