D3.js绑定数据的选择集遍历调用data()报错问题咨询
d3遍历元素时报错
selection.data() is not a function的修复方案 错误成因
- D3的
selectAll()返回的顶层对象才是拥有.data()、.transition()等方法的封装选择实例。直接用for...of遍历该选择集时,迭代拿到的是内层原生DOM节点,不是D3封装的选择实例,本身没有.data()方法,因此触发类型错误。 - 此前
targetYear.data()能正常返回绑定数据,是因为该方法直接调用在顶层D3选择实例上,返回的是元素绑定数据组成的普通JS数组,和遍历得到的原生DOM节点属于完全不同的对象类型。 - 代码存在变量命名冲突:外层遍历年份数组的变量为
years,内层循环变量也命名为years,会造成作用域混淆,干扰后续调试。
标准修复实现
遵循D3的设计规范,使用内置的.each()方法遍历选择集,直接在回调中获取当前元素的绑定数据,不需要手动拆分DOM与数据的映射:
function leavingQuarter() { let yearRange = d3.range(1980, 2022, 1); yearRange.forEach((t) => { let targetYearNodes = d3.selectAll(`.year${t}`); let boundData = targetYearNodes.data(); let wordCountList = []; let lowerThreshold; let upperThreshold; for (let item of boundData) { wordCountList.push(item.wordcount); } // 拷贝数组后排序,避免污染原始列表 let sortedCounts = [...wordCountList].sort(d3.ascending); let totalCount = sortedCounts.length; // 增加边界判断,避免元素数量较少时出现索引越界 let lowerIndex = Math.max(0, Math.floor(totalCount * 0.2)); let upperIndex = Math.min(totalCount - 1, Math.floor(totalCount * 0.8)); lowerThreshold = sortedCounts[lowerIndex]; upperThreshold = sortedCounts[upperIndex]; // 用each遍历替代错误的for-of循环 targetYearNodes.each(function(d) { // 入参d为当前迭代元素绑定的原始数据,无需额外调用.data() if (d.wordcount < lowerThreshold || d.wordcount > upperThreshold) { // this指向当前原生DOM节点,重新包装为D3实例后执行动画 d3.select(this) .transition() .duration(500) .style('opacity', 0); } }); }); }
兼容原生循环的实现方式
如果必须使用原生for...of遍历,需要将循环拿到的原生DOM节点重新包装为D3选择实例,再通过.datum()获取单节点绑定的数据,不可直接在原生DOM对象上调用D3方法:
for (let rawNode of targetYearNodes) { const nodeBoundData = d3.select(rawNode).datum(); if (nodeBoundData.wordcount < lowerThreshold || nodeBoundData.wordcount > upperThreshold) { d3.select(rawNode) .transition() .duration(500) .style('opacity', 0); } }
内容的提问来源于stack exchange,提问作者BS100
相关产品推荐
相关产品推荐

