D3 7 中 d3.hierarchy 根节点 API 详解:层级数据的表示、遍历与操作 📅 发布时间:2026/9/5 20:30:10 👁 浏览次数: D3 7 中 d3.hierarchy 根节点 API 详解层级数据的表示、遍历与操作【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3本文基于 d3 仓库的官方文档 hierarchy 展开系统讲解d3.hierarchy根节点的构造方式、六个核心属性、遍历/查找/排序/求值全套方法ancestors、descendants、find、path、links、sum、sort、each系列等。读完本文你可以将 JSON 或分组Map数据转成标准层级树并正确地把node.value与排序结果喂给 treemap、tree、pack 等层级布局。一、hierarchy API 在 d3-hierarchy 模块中的定位很多数据天然是层级结构的行政区划、组织架构、文件系统、软件包d3-hierarchy 模块为此提供了几类经典可视化节点连线图tree、cluster 树状图、邻接图partition 冰柱图、包围图treemap、pack 圆形嵌套详见模块总览 d3-hierarchy。所有布局的输入都不是原始 JSON而是一个根节点root node。要得到根节点有两条路数据已经是 JSON 等层级格式直接传给d3.hierarchy(data)数据是 CSV 等扁平表格先用 stratify 把「name,parent」两列重组为层级再交给布局。本文聚焦前者即d3.hierarchy本身。在 d3 7 中该 API 通过汇总包统一导出src/index.js 中的export * from d3-hierarchy;让d3.hierarchy与d3.stratify等全部可用而 package.json 声明的依赖为d3-hierarchy: ^3.1.2d3 版本 7.9.0。二、用 d3.hierarchy(data, children) 构造根节点d3.hierarchy(data[, children])从指定的层级数据构造根节点要求data必须是代表根节点的对象。文档给出的标准示例const data { name: Eve, children: [ {name: Cain}, {name: Seth, children: [{name: Enos}, {name: Noam}]}, {name: Abel}, {name: Awan, children: [{name: Enoch}]}, {name: Azura} ] };构造层级const root d3.hierarchy(data);可选的children访问函数会对每个数据项从根data开始调用须返回代表子节点的可迭代对象iterable不指定时默认为function children(d) { return d.children; }Map 数据的隐式转换若data是一个Map它会被隐式转换为条目[undefined, data]同时children访问函数的默认值变为function children(d) { return Array.isArray(d) ? d[1] : null; }这让你可以把 group 或 rollup 的结果直接传给d3.hierarchy——分组得到的Map键为分组键、值为该组的条目数组天然匹配「键 → 子数据数组」的结构无需手工展开。此外该方法也可用于判断节点是否为instanceof d3.hierarchy以及扩展节点原型。节点属性root 与每个子孙共有六个字段属性含义node.data传给d3.hierarchy的原始数据与输入共享引用node.depth根节点为 0每向后代深一层加 1node.height到任意后代叶节点的最大距离叶节点为 0node.parent父节点根节点为nullnode.children子节点数组叶节点为undefinednode.value可选的聚合值为节点与其后代 descendants 之和注意node.data与布局写入的x/y/r等坐标字段并存于同一对象上d3 v4 起布局直接以这些根节点为输入而不是操作原始 JSON从而把输入数据与计算结果分离见 CHANGES.md 中 d3-hierarchy 一节的设计说明这也是后文node.copy()能单独隔离布局变更的前提。三、祖先、后代与叶节点ancestors / descendants / leavesnode.ancestors()返回祖先节点数组从当前节点开始依次向上直到根节点。典型用途是鼠标悬停时高亮某节点的全部上级。node.descendants()返回后代节点数组从当前节点开始按拓扑顺序父先于子排列全部子孙。布局完成后调用它即可拿到带坐标的全部节点数组const nodes root.descendants();node.leaves()按遍历顺序返回叶节点数组。*叶节点leaf*指没有 children 的节点。四、查找与路径find / pathnode.find(filter)返回从当前node出发、第一个使filter返回真值的节点找不到返回undefined。find是 d3-hierarchy 3.x 新增的 APICHANGES.md 记录Addnode.find。node.path(target)返回从当前node到指定target节点的最短路径从起点上溯到两者的最近公共祖先LCA再下降到target。该方法取代了 d3 v3 时代的d3.layout.bundle是层级边缘捆绑hierarchical edge bundling的基础原语。五、生成边数据links()node.links()返回当前节点及其全部后代的边link数组每条边是带source与target属性的对象source 为父节点target 为子节点。const links root.links(); // 每条 link{ source: 父节点, target: 子节点 }配合 d3-shape 的 link 生成器即可渲染节点连线图。从 CHANGES.md 可见links()自 d3 v4 起取代了treemap.links等各布局私有的边生成方法成为所有层级布局的统一接口。六、值聚合sum(value) 与 count()node.sum(value)以后序遍历post-order对当前节点及每个后代求值并返回当前node。每个节点的node.value被设为该节点访问函数的返回值 所有子节点value之和。访问函数接收节点的data必须返回非负数。两个关键细节value访问函数会对节点和全部后代求值含内部节点若只希望叶节点贡献值请对含子节点的节点返回 0。例如作为node.count的替代按叶节点计数root.sum((d) d.value ? 1 : 0);必须在调用需要node.value的层级布局之前调用sum或count例如 treemap// Construct the treemap layout. const treemap d3.treemap(); treemap.size([width, height]); treemap.padding(2); // Sum and sort the data. root.sum((d) d.value); root.sort((a, b) b.height - a.height || b.value - a.value); // Compute the treemap layout. treemap(root); // Retrieve all descendant nodes. const nodes root.descendants();由于 API 支持方法链式调用同样可以写成d3.treemap() .size([width, height]) .padding(2) (root .sum((d) d.value) .sort((a, b) b.height - a.height || b.value - a.value)) .descendants()此示例假设节点数据带有value字段。node.count()统计当前节点下的叶节点数量并赋给node.value其每个后代同理若当前节点本身是叶节点计数为 1返回当前node。与sum的关系见上文。七、重排子节点sort(compare)node.sort(compare)按指定compare函数对当前节点及其每个后代的 children 执行**前序遍历pre-order**排序并返回当前node。与sum的一个重要区别compare函数接收的是两个节点a和b而不是两个节点的 data。约定与Array.prototype.sort一致a应在b之前返回负值反之返回正值否则相对顺序未定义。文档给出的三组典型用法均建议先sum按「聚合值」降序——circle-packing 的推荐排序root .sum((d) d.value) .sort((a, b) b.value - a.value);先按高度降序、再按值降序——treemap 与 icicle 图 的推荐排序root .sum((d) d.value) .sort((a, b) b.height - a.height || b.value - a.value);先按高度降序、再按 id 升序——tree 与 dendrogram 的推荐排序root .sum((d) d.value) .sort((a, b) b.height - a.height || d3.ascending(a.id, b.id));调用时机若希望新的排序顺序影响布局必须在调用布局之前执行node.sort。八、可迭代与三种遍历Symbol.iterator / each / eachAfter / eachBeforenodeSymbol.iterator返回按**广度优先顺序breadth-first order**迭代node后代的迭代器for (const descendant of node) { console.log(descendant); }这是 d3-hierarchy 3.x 新增的能力层级从此可直接for...of迭代CHANGES.md 记录Addnode[Symbol.iterator]; hierarchies are now iterable。node.each(function, that)以广度优先顺序对node及每个后代调用function某节点只有在所有更浅层节点及同层前序节点都访问完后才被访问。回调参数依次为当前descendant、零基遍历index、当前node即调用对象指定that时作为回调的this上下文。node.eachAfter(function, that)以**后序遍历post-order**调用节点在所有后代都被访问之后才被访问。这是sum内部使用的遍历方向。node.eachBefore(function, that)以**前序遍历pre-order**调用节点在所有祖先都被访问之后才被访问sort即以前序执行。三个 each 方法的回调自 d3-hierarchy 3.x 起都会传入遍历 indexCHANGES.mdChangenode.each / eachAfter / eachBefore to pass the traversal index。从仓库历史看CHANGES.md 4.0 一节这些非递归遍历方法正是层级布局内部实现的基础——布局tree、treemap 等改为用它们重写以避免递归在大数据集上的开销。九、深拷贝子树copy()node.copy()返回以当前node为根的子树深拷贝但拷贝共享同一份 data。返回节点是一棵新树的根其parent恒为nulldepth恒为 0。典型用途是隔离布局副作用布局会把x/y/r等坐标写回节点对象若想在同一数据上尝试不同布局而不互相污染可以先root.copy()再计算新布局CHANGES.md 4.0 一节即以此为例usenode.copy to isolate layout changes。十、实战要点小结扁平表格数据先经 stratify配合 d3.csvParse层级 JSON 直接经d3.hierarchyMap如 group 结果可直接传入并自动映射。需要面积编码的布局treemap、partition、pack要求节点带value务必在布局前sum或count需要自定义顺序时在其后sort且sort的 compare 比较的是节点而非 data。渲染时以root.descendants()取节点、root.links()取边交互高亮用ancestors()边缘捆绑用path()快速检索用find()。遍历策略按需选择按层处理用eachBFS、自底向上聚合用eachAfter、自顶向下展开用eachBefore、最简写法用for...of。同一数据尝试多种布局时用copy()隔离坐标副作用。进一步阅读d3-hierarchy 模块总览、stratify、tree、cluster、partition、pack、treemap。【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考