
本文详解 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 无 data
gElement 是
✅ 正确做法是:保存 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 图表组件——无论是条形图的