首页
/ D3.js 中 d3.stratify 详解:把 CSV 表格与路径字符串转化为分层数据的实战指南

D3.js 中 d3.stratify 详解:把 CSV 表格与路径字符串转化为分层数据的实战指南

2026-09-04 22:04:52作者:董宙帆

本文基于 D3.js 官方文档 Stratify 章节 展开,讲解 d3.stratify 如何将扁平的表格数据(如 CSV 解析出的记录数组)或 Unix 风格的分隔路径,重建为可供树形、矩形树图等布局算法消费的层次结构(hierarchy)。读完本文,你将掌握 stratifystratify(data)stratify.idstratify.parentIdstratify.path 五个 API 的默认行为、参数约定与约束条件,并能独立完成「CSV → 层级节点」这一 D3 层次数据可视化的标准预处理流程。

d3.stratify 将扁平表格构建成以 Eve 为根的分层树结构

为什么需要 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 根节点,可以立即传给任意的层次布局算子,例如 treetreemappack 进行可视化。

这里有一个值得留意的细节:CSV 中 Eve 的 parent 是空字符串,而 stratify.parentId 的约定是「空字符串与 null 等价于 undefined」,因此空字符串恰好被识别为「无父节点」,即根节点。表格中根行 parent 列留空的写法与 stratify 的语义天然吻合。

API 详解:五个方法及其默认值

stratify 算子的完整 API 共五个方法,全部实现在 d3-hierarchy 模块的 src/stratify.js(在本仓库中通过 package.jsond3-hierarchy: ^3.1.2 依赖引入,并由 src/index.jsexport * 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 访问器,idparentId 访问器即被忽略,stratify 改为基于路径访问器返回的斜杠(/)分隔字符串计算类 Unix 文件系统式的层次结构,并在必要时自动补全中间层父节点和父节点 id——这是 path 模式与 id/parentId 模式最重要的行为差异:表格里不需要显式列出每一层目录。

path 模式实战:把 find 命令输出变成目录树

官方文档给出了一个非常典型的场景:对当前目录执行 UNIX find 命令,得到一组相对路径:

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 相关条目)。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384