D3.js 中 d3.stratify 详解:把 CSV 表格与路径字符串转化为分层数据的实战指南
【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3
本文基于 D3.js 官方文档 Stratify 章节 展开,讲解d3.stratify如何将扁平的表格数据(如 CSV 解析出的记录数组)或 Unix 风格的分隔路径,重建为可供树形、矩形树图等布局算法消费的层次结构(hierarchy)。读完本文,你将掌握stratify、stratify(data)、stratify.id、stratify.parentId、stratify.path五个 API 的默认行为、参数约定与约束条件,并能独立完成「CSV → 层级节点」这一 D3 层次数据可视化的标准预处理流程。
为什么需要 stratify:表格数据与层级数据之间的桥梁
D3 的层次布局(如 tidy 树、dendrogram、treemap、circle-packing)都要求输入是一棵以根节点为起点的树。官方在 d3-hierarchy 模块说明 中把这一模块定位为「组织层次数据并可视化」,而 Hierarchies 章节 开篇就点明了两条数据入口:
- 数据本身已经是嵌套结构(如 JSON),可以直接交给
d3.hierarchy(data); - 数据是扁平的表格形式(如 CSV),则需要先用 stratify 把表格「重排」为层次结构。
现实中的组织关系、文件清单、分类目录绝大多数以扁平表格形式存储——每行一条记录,用「自身 id + 父级 id」两列描述从属关系。d3.stratify正是把这类「父子邻接表」(parent pointer table)转换为树的操作算子。
从关系表格到 CSV:完整的输入示例
官方文档以一张人物关系表作为贯穿始终的示例:
| Name | Parent |
|---|---|
| Eve | |
| Cain | Eve |
| Seth | Eve |
| Enos | Seth |
| Noam | Seth |
| Abel | Eve |
| Awan | Eve |
| Enoch | Awan |
| Azura | Eve |
由于这些名字互不重复,这份关系表可以无歧义地表示为一个 CSV 文件:
name,parent Eve, Cain,Eve Seth,Eve Enos,Seth Noam,Seth Abel,Eve Awan,Eve Enoch,Awan Azura,Eve用 csvParse 解析该文本:
const table = d3.csvParse(text);它返回一个由{name, parent}对象组成的数组(注意:CSV 解析出的所有值默认都是字符串):
[ {"name": "Eve", "parent": ""}, {"name": "Cain", "parent": "Eve"}, {"name": "Seth", "parent": "Eve"}, {"name": "Enos", "parent": "Seth"}, {"name": "Noam", "parent": "Seth"}, {"name": "Abel", "parent": "Eve"}, {"name": "Awan", "parent": "Eve"}, {"name": "Enoch", "parent": "Awan"}, {"name": "Azura", "parent": "Eve"} ]注意根节点 Eve 的parent是空字符串""——这一点与后面stratify的约定直接相关。
核心用法:stratify(data) 构建层次结构
拿到表格数据后,调用 stratify 算子即可生成层次结构:
const root = d3.stratify() .id((d) => d.name) .parentId((d) => d.parent) (table);返回的root就是一个标准的 hierarchy 根节点,可以立即传给任意的层次布局算子,例如 tree、treemap、pack 进行可视化。
这里有一个值得留意的细节:CSV 中 Eve 的parent是空字符串,而stratify.parentId的约定是「空字符串与 null 等价于 undefined」,因此空字符串恰好被识别为「无父节点」,即根节点。表格中根行 parent 列留空的写法与 stratify 的语义天然吻合。
API 详解:五个方法及其默认值
stratify 算子的完整 API 共五个方法,全部实现在 d3-hierarchy 模块的src/stratify.js(在本仓库中通过 package.json 以d3-hierarchy: ^3.1.2依赖引入,并由 src/index.js 的export * from "d3-hierarchy"统一导出为全局d3.stratify)。
stratify()
构造一个使用默认设置的 stratify 算子:
const stratify = d3.stratify();它本身只是一个可配置的工厂;真正执行转换要等到把数据传入算子。
stratify(data)
从给定的表格data生成一棵新的层次结构:
const root = stratify(data);这是唯一「执行型」方法,返回 hierarchy 根节点,其内部会按id/parentId(或path)访问器建立节点间的父子指针。
stratify.id(id)
若传入id,将 id 访问器设置为给定函数并返回该算子(支持链式调用);否则返回当前 id 访问器。默认访问器为:
function id(d) { return d.id; }id 访问器会对传入 stratify 算子的每条记录调用一次,参数依次为当前数据项 (d) 和当前索引 (i)。返回值(字符串)用于结合 parent id 识别节点之间的从属关系。约束条件如下:
- 叶子节点的 id 可以为 undefined;
- 非叶子节点的 id必须唯一;
- null 和空字符串等价于 undefined。
默认访问器读的是记录的d.id字段;如果数据里用别的字段名(如示例中的name),就必须像上文那样显式调用.id((d) => d.name)覆盖。
stratify.parentId(parentId)
若传入parentId,将父节点 id 访问器设置为给定函数并返回该算子;否则返回当前访问器。默认访问器为:
function parentId(d) { return d.parentId; }parent id 访问器同样对每条记录调用,参数为 (d,i)。返回值与 id 访问器配合确定节点的父子关系。关键约束:
- 根节点的 parent id 应为 undefined(空字符串/null 等价于 undefined);
- 输入数据中必须恰好存在一个根节点;
- 不允许出现循环引用。
这三条约束是排查 stratify 报错(如找不到根、出现孤儿节点)时的第一检查点:常见原因是数据里混入了第二行 parent 为空的记录,或 parent 指向了不存在于表中的 id。
stratify.path(path)
若传入path,将路径访问器设置为给定函数并返回该算子;否则返回当前路径访问器,默认值为 undefined。
一旦设置了 path 访问器,id 与 parentId 访问器即被忽略,stratify 改为基于路径访问器返回的斜杠(/)分隔字符串计算类 Unix 文件系统式的层次结构,并在必要时自动补全中间层父节点和父节点 id——这是 path 模式与 id/parentId 模式最重要的行为差异:表格里不需要显式列出每一层目录。
path 模式实战:把 find 命令输出变成目录树
官方文档给出了一个非常典型的场景:对当前目录执行 UNIXfind命令,得到一组相对路径:
const paths = [ "axes.js", "channel.js", "context.js", "legends.js", "legends/ramp.js", "marks/density.js", "marks/dot.js", "marks/frame.js", "scales/diverging.js", "scales/index.js", "scales/ordinal.js", "stats.js", "style.js", "transforms/basic.js", "transforms/bin.js", "transforms/centroid.js", "warnings.js", ];一行即可构建目录层次:
const root = d3.stratify().path((d) => d)(paths);这里(d) => d表示数据项本身就是字符串,直接作为路径使用。stratify 会把marks/dot.js拆分为marks→marks/dot.js两级,并自动推断出中间节点marks的 id 和父节点""(根)。
这种用法在本仓库的文档站点中就有真实应用。what-is-d3 页面 的源码脚本里,先用侧边栏配置递归生成一组{path, link}路径数据,随后计算树图宽度时直接组合了 stratify 的 path 模式与 tree 布局:
const root = d3.tree().nodeSize([1, 1])(d3.stratify().path((d) => d.path)(paths));这恰好演示了「路径数组 → stratify.path → 层次布局」的完整链路:访问器改为(d) => d.path,从记录对象中取出路径字段即可。
组合使用:从 CSV 到可视化的完整管线
把前面各节串起来,一个典型的表格数据树状图管线为:
// 1. 解析 CSV 文本为记录数组 const table = d3.csvParse(text); // 2. 用 id / parentId 访问器把表格转成层次结构 const root = d3.stratify() .id((d) => d.name) .parentId((d) => d.parent) (table); // 3. 交给层次布局算子,例如 tidy 树 d3.tree()(root);得到root之后,就可以使用 Hierarchies 文档 中介绍的全部节点方法:root.descendants()、root.links()、root.sum(...)、root.eachBefore(...)等,驱动 SVG/Canvas 的绘制。
小结与检查清单
d3.stratify()是「扁平表格 → 树」的转换算子,stratify(data)执行转换,.id()/.parentId()配置两列 id,.path()切换到按斜杠路径自动推断层级的模式;- id 默认读
d.id,parentId 默认读d.parentId;字段名不同时必须显式指定访问器; - 空字符串与 null 等价于 undefined,因此 CSV 中根节点 parent 列留空即可;
- 输入必须恰好有一个根节点、非叶子节点 id 唯一、无循环引用;
- 设置了 path 访问器后,id 与 parentId 访问器失效,中间层节点会被自动补全;
- 输出是标准 hierarchy 根节点,可直接接入 tree、cluster、treemap、partition、pack 等全部 d3-hierarchy 布局算法(见 d3-hierarchy 模块总览 与 API 索引 中 d3.stratify 相关条目)。
【免费下载链接】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),仅供参考