D3.js 中 d3.stratify 详解:把 CSV 表格与路径字符串转化为分层数据的实战指南
2026/9/5 21:24:09 网站建设 项目流程

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)。读完本文,你将掌握stratifystratify(data)stratify.idstratify.parentIdstratify.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:完整的输入示例

官方文档以一张人物关系表作为贯穿始终的示例:

NameParent
Eve
CainEve
SethEve
EnosSeth
NoamSeth
AbelEve
AwanEve
EnochAwan
AzuraEve

由于这些名字互不重复,这份关系表可以无歧义地表示为一个 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拆分为marksmarks/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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询