如何用d3.groups和JS reduce替换依赖d3.nest的旧D3.js(v7)代码
将D3.js v7中的d3.nest替代方案实现嵌套对象
问题背景
原D3旧版本使用d3.nest()实现数据嵌套,代码如下:
d3 .nest() .key(function (d) { return d.year; }) .key(function (d) { return d.mw; }) .key(function (d) { return d.variant; }) .map(csv)
升级到D3 v7后,d3.nest已被移除,需要用d3.groups结合JS原生方法实现相同的嵌套对象结构。目标是将如下原始数据:
const dummy = [ { variant: "0", year: "1950", mw: "m", pop: "31962", pop_0_4: "540" }, { variant: "0", year: "1950", mw: "w", pop: "32052", pop_0_4: "545" }, { variant: "0", year: "1951", mw: "m", pop: "31453", pop_0_4: "520" }, { variant: "0", year: "1951", mw: "w", pop: "35444", pop_0_4: "526" }, { variant: "1", year: "1950", mw: "m", pop: "41253", pop_0_4: "510" }, { variant: "1", year: "1950", mw: "w", pop: "42356", pop_0_4: "515" }, { variant: "1", year: "1951", mw: "m", pop: "45633", pop_0_4: "500" }, { variant: "1", year: "1951", mw: "w", pop: "49500", pop_0_4: "501" }, ];
转换为可通过desired['1950'].m['0'][0].pop_0_4取值的嵌套对象结构。
注意:你提供的
desired对象存在重复键(同一个1950/1951出现两次),这在JS对象中是不合法的,后面的键会覆盖前面的。实际正确的嵌套结构应该是每个年份下包含所有对应的mw和variant分组。
用户尝试了d3.groups和reduce,但未能完全将嵌套数组转为对象:
// 用户尝试代码 let test; test = d3 .groups( dummy, (d) => d.year, (d) => d.mw, (d) => d.variant ) .reduce((acc, curr) => { let obj = {}; curr[1].forEach((item) => (obj[item[0]] = item[1])); acc[curr[0]] = obj; return acc; }, {});
解决方案
d3.groups返回的是[[key1, [[key2, [[key3, dataArray]]]]]]的嵌套数组结构,每一级都是[分组键, 下一级分组/数据数组]的形式。需要将这种数组结构逐层转换为对象属性,以下是两种可行实现方式:
方式一:递归转换函数
编写通用递归函数,适配任意层级的分组转换:
function groupsToObject(groups) { return groups.reduce((acc, [key, value]) => { // 判断下一级是否仍为分组结构,递归转换 if (Array.isArray(value[0]) && typeof value[0][0] === 'string') { acc[key] = groupsToObject(value); } else { // 最底层为数据数组,直接赋值 acc[key] = value; } return acc; }, {}); } // 使用方式 const desired = groupsToObject( d3.groups( dummy, d => d.year, d => d.mw, d => d.variant ) ); // 验证取值 console.log(desired['1950'].m['0'][0].pop_0_4); // 输出 "540" console.log(desired['1950'].m['1'][0].pop_0_4); // 输出 "510"
方式二:逐层手动处理
如果分组层级固定,可直接逐层解构转换,避免递归:
const desired = d3.groups(dummy, d => d.year, d => d.mw, d => d.variant) .reduce((yearAcc, [year, mwGroups]) => { yearAcc[year] = mwGroups.reduce((mwAcc, [mw, variantGroups]) => { mwAcc[mw] = variantGroups.reduce((variantAcc, [variant, data]) => { variantAcc[variant] = data; return variantAcc; }, {}); return mwAcc; }, {}); return yearAcc; }, {}); // 验证取值 console.log(desired['1951'].w['1'][0].pop_0_4); // 输出 "501"
内容的提问来源于stack exchange,提问作者PaoloRossi
相关产品推荐
相关产品推荐

