
本文详解如何在 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 差异较大,不可直接迁移。
通过将事件监听器精准绑定到数据驱动的