
本文详解 d3 v3 中环形图(donut chart)的数据绑定原理,解决 `d.data` 为 `undefined` 的常见问题,并提供可稳定触发 tooltip 的事件绑定方案。
在使用 D3.js(尤其是 v3 版本)构建环形图时,一个高频陷阱是:事件回调中无法访问绑定的原始数据,例如 onMouseEnter(d) => console.log(d.data) 输出 undefined。根本原因并非数据未绑定,而是 事件监听器被错误地注册在父容器(如
D3 的 .data() 绑定作用域是链式调用中最近一次 .selectAll() 或 .select() 的选择集。在原始代码中:
gElement.on("mouseenter", onMouseEnter); // ❌ 错误:gElement 无 datagElement 是
✅ 正确做法是:保存 enter() 后的 path 选择集,并在其上注册事件:
const paths = gElement
.selectAll("path")
.data(chartPie)
.enter()
.append("path");
paths
.attr("class", "donut_chart")
.attr("fill", d => color(d.data[0]))
.attr("d", chartArc)
.each(function(d) { this._current = d; })
.on("mouseenter", onMouseEnter) // ✅ 正确:绑定到 path,d.data 可用
.on("mouseleave", onMouseLeave);此时,在 onMouseEnter(d) 中,d 即为 pie() 生成的弧形数据对象,结构如下:
{
data: ["high", 3], // 原始键值对(Object.entries 输出)
value: 3,
startAngle: ...,
endAngle: ...,
padAngle: ...
}因此 d.data[0](键名)和 d.data[1](数值)均可安全访问。
⚠️ 补充注意事项:
- 避免使用 explicitOriginalTarget:该属性属非标准 DOM 实现,行为不可靠,且在 Shadow DOM 或现代浏览器中可能失效;
- 过渡动画需同步数据更新:.transition().attrTween("d", arcTween) 应在 .data() 之后调用,确保新旧数据匹配;
- arcTween 中的 interpolate 需提前定义:D3 v3 要求手动引入 d3.interpolateObject 或使用 d3.interpolate,否则会报错(示例中已隐含此前提);
- v3 与 v5+ 差异提示:D3 v4/v5 已废弃 d3.svg.arc 等命名空间,改用 d3.arc(),且 pie().value() 接收函数而非字符串;若升级版本,需全面重构 API 调用。
完整可运行示例(修复后核心逻辑):
function onMouseEnter(d) {
console.log("Data key:", d.data[0]); // e.g., "high"
console.log("Data value:", d.data[1]); // e.g., 3
// ✅ 此处可安全创建 tooltip
}
function updateChart(data) {
const pie = d3.layout.pie()
.startAngle(-Math.PI / 2 * 5)
.value(d => d[1])
.sort(null)
.padAngle(padAngle);
const pieData = pie(data);
const paths = gElement.selectAll("path").data(pieData);
// 退出过渡
paths.exit().remove();
// 更新过渡
paths.transition()
.duration(transitionDuration)
.attrTween("d", function(d) {
const i = d3.interpolate(this._current, d);
this._current = i(0);
return t => chartArc(i(t));
});
// 进入元素
paths.enter()
.append("path")
.attr("class", "donut_chart")
.attr("fill", d => color(d.data[0]))
.attr("d", chartArc)
.each(function(d) { this._current = d; })
.on("mouseenter", onMouseEnter)
.on("mouseleave", () => tooltip.style("opacity", 0));
}总结:D3 数据驱动的核心在于 “在哪绑定,就在哪交互”。始终将事件监听器挂载到 .data() 直接作用的 DOM 元素上,是保障 d 对象完整、可靠的关键。这一原则适用于所有 D3 图表组件——无论是条形图的










