使用 @visx/hierarchy 在 React 中可视化树形与层次数据:五大布局组件实战指南
【免费下载链接】visx🐯 visx | visualization components项目地址: https://gitcode.com/gh_mirrors/vi/visx
导读
@visx/hierarchy是 visx 体系中专门面向**层次数据(hierarchical data)**的可视化子包,为 React 提供了一组开箱即用的布局组件,整体设计与d3-hierarchy保持一一对应。本指南将以 packages/visx-hierarchy/Readme.md 为核心骨架,结合仓库源码深入讲解:如何用hierarchy()构造 d3 格式的根节点、五种布局(Tree / Cluster / Pack / Partition / Treemap)各自的适用场景与完整 props,以及如何通过渲染回调(render prop)和自定义节点/连线组件实现完全可控的层次可视化。读完本文,你可以直接在自己的 React 项目中搭建树图、圈图、旭日图和矩形树图。
为什么需要层次数据组件
许多真实数据集天然具有层级结构:文件系统目录、公司组织架构、商品类目树、基因谱系、词云词频嵌套……在 React 生态中处理这类数据,通常面临两个问题:
- 布局算法复杂:树、集群、打包、分区、矩形树都有各自的几何布局规则,手写成本高;
- 与渲染解耦困难:布局结果需要转成 SVG 元素,并且要支持样式定制。
@visx/hierarchy的解决方案是:用d3-hierarchy完成布局计算,用 React 组件负责渲染。所有组件接收同一个d3-hierarchy定义的根节点(root node)作为输入,因此你在不同布局之间切换时,数据层完全无需改动。
从 src/index.ts 可以看到,包对外同时导出五类能力:
- 五种布局组件:
Tree、Cluster、Pack、Partition、Treemap; - 三个默认渲染组件:
HierarchyDefaultLink、HierarchyDefaultNode、HierarchyDefaultRectNode; - d3-hierarchy 的布局与工具函数再导出:
hierarchy、stratify、以及treemapSquarify、treemapBinary、treemapResquarify、treemapDice、treemapSlice、treemapSliceDice六种 treemap 平铺算法; - 完整的类型再导出(见 src/types.ts):
HierarchyNode、HierarchyPointNode、HierarchyCircularNode、HierarchyRectangularNode、HierarchyPointLink、HierarchyCircularLink、HierarchyRectangularLink、HierarchyLink以及自定义平铺方法类型TileMethod<Datum>。
// equivalent to `import { hierarchy } from 'd3-hierarchy';` import { hierarchy } from '@visx/hierarchy';安装与依赖
npm install --save @visx/hierarchy根据 package.json,@visx/hierarchy的运行时依赖非常克制:
d3-hierarchy(^1.1.4):布局计算引擎;@visx/group:提供<Group>容器用于整体位移(top/left偏移);classnames:合并 className;@types/d3-hierarchy:TypeScript 类型支持。
其 peerDependencies 为react@^18 || ^19,并声明了 ESM/CJS 双入口(exports字段分别指向esm/index.js与lib/index.js),支持 tree-shaking。
第一步:构造层级根节点
所有布局组件都接收一个root: HierarchyNode<Datum>属性——这与d3-hierarchy模块定义的根节点格式完全一致。因此第一步是把普通 JS 对象转换成 d3 层级结构:
import { hierarchy } from '@visx/hierarchy'; const root = hierarchy({ name: 'root', children: [ { name: 'child #1' }, { name: 'child #2', children: [{ name: 'grandchild #1' }, { name: 'grandchild #2' }, { name: 'grandchild #3' }], }, ], });转换后的root是HierarchyNode<Datum>实例,携带depth、height、parent、children、descendants()、links()等方法,可以直接传给任意布局组件。hierarchy默认从每个节点的children属性读取子节点,并可以通过第二个参数自定义 accessor:
const root = hierarchy(data, (d) => d.subItems);此外,包还再导出了stratify,适合把扁平记录(如{ id, parentId }列表)组装成层级结构,例如数据库中的目录表或 CSV 导入的组织数据。
五种布局组件:适用场景与完整 Props
五种布局组件在 API 设计上高度一致:都接收root、top、left、className,都支持children渲染回调覆盖默认输出,也都允许通过nodeComponent/linkComponent注入自定义渲染组件。下面逐一展开。
Tree:经典树状图
Tree对应d3-hierarchy的tree()布局,将树节点按“兄弟在同一水平层、叶子平铺”的方式排列,是最常用的目录树 / 组织架构图布局。核心 props(src/hierarchies/Tree.tsx):
| Prop | 类型 | 说明 |
|---|---|---|
root | HierarchyNode<Datum> | 必填,d3 层级根节点 |
size | [number, number] | 布局尺寸[width, height]。坐标系统是任意的,例如径向布局可传[360, radius]表示 360° 广度、radius 深度 |
nodeSize | [number, number] | 每个节点的固定尺寸[width, height];设置后根节点固定在⟨0, 0⟩ |
separation | (a, b) => number | 相邻节点分离度 accessor,控制兄弟/表亲节点间距 |
linkComponent/nodeComponent | React 组件 | 自定义连线/节点渲染 |
children | (node) => ReactNode | 渲染回调,接收布局结果HierarchyPointNode<Datum> |
top/left | number | 整体位移,作用于外层<Group> |
className | string | 追加到外层<Group>的 class(默认带visx-tree) |
最简单的用法是让组件用默认节点与连线完成渲染:
import { Tree, hierarchy } from '@visx/hierarchy'; const data = hierarchy({ name: 'root', children: [{ name: 'a' }, { name: 'b' }] }); export default function SimpleTree() { return ( <svg width={500} height={300}> <Tree top={20} left={20} size={[460, 260]} root={data} /> </svg> ); }Cluster:集群图(叶节点等距)
Cluster对应d3-hierarchy的cluster()布局。它与Tree的区别在于:所有叶节点被等距排布在底层,父节点居于子节点正中,非常适合表现具有明显层级且叶子数量可观的“谱系/聚类”结构。其 props 与Tree几乎一致(src/hierarchies/Cluster.tsx):size、nodeSize、separation、linkComponent、nodeComponent、children、top、left、className。
源码中有一处细节值得注意:Tree用if (nodeSize)判断是否设置nodeSize,而Cluster使用if (nodeSize !== undefined),允许显式传入[0, 0]这类假值场景,细节处理上更严谨。
Pack:圆形打包图(气泡图)
Pack对应d3-hierarchy的pack()布局,用大小不一的圆表示节点,通过圆面积体现数值大小,适合展示“部分与整体”的占比关系。核心 props(src/hierarchies/Pack.tsx):
| Prop | 类型 | 说明 |
|---|---|---|
radius | (node) => number | 叶子圆半径 accessor;若不传,半径由叶子节点的value推导并按布局size等比缩放 |
size | [number, number] | 布局尺寸[width, height] |
padding | number | 节点间的近似间距 |
nodeComponent | React 组件 | 自定义节点渲染(节点为HierarchyCircularNode,含x、y、r) |
注意Pack的输出节点是HierarchyCircularNode类型,默认节点组件HierarchyDefaultNode恰好兼容:它读取node.x、node.y、node.r渲染一个<circle>(见 HierarchyDefaultNode.tsx,默认半径 15、填充色#21D4FD)。因此Pack与Tree/Cluster可共用同一个默认节点组件。
Partition:分区图(旭日图 / 冰柱图)
Partition对应d3-hierarchy的partition()布局,把节点空间按 value 递归切分为矩形切片,横向或纵向堆叠,是**旭日图(Sunburst)和冰柱图(Icicle)**的底层布局。核心 props(src/hierarchies/Partition.tsx):
| Prop | 类型 | 说明 |
|---|---|---|
size | [number, number] | 布局尺寸 |
round | boolean | 是否对坐标取整(避免亚像素渲染模糊) |
padding | number | 相邻子节点之间的间隔 |
nodeComponent | React 组件 | 自定义节点渲染(节点为HierarchyRectangularNode,含x0/x1/y0/y1) |
Partition默认使用HierarchyDefaultRectNode(HierarchyDefaultRectNode.tsx),它从节点的x0、x1、y0、y1计算width、height并渲染<rect>。若需要旭日图效果,只需把size设为极坐标语义的[2 * Math.PI, radius],再把矩形节点替换为弧形渲染即可。
Treemap:矩形树图
Treemap对应d3-hierarchy的treemap()布局,将矩形区域按 value 递归切块,是磁盘占用分析、商品销售结构等场景的标准选择,也是本包中props 最丰富的组件(src/hierarchies/Treemap.tsx):
| Prop | 类型 | 说明 |
|---|---|---|
tile | TileMethod<Datum> | 平铺算法,包内置再导出treemapSquarify(默认)、treemapBinary、treemapResquarify、treemapDice、treemapSlice、treemapSliceDice六种 |
size | [number, number] | 布局尺寸 |
round | boolean | 是否取整坐标 |
padding | number \| accessor | 同时设置内、外 padding |
paddingInner | number \| accessor | 相邻子节点间距 |
paddingOuter | number \| accessor | 节点与其子节点的间距 |
paddingTop/paddingRight/paddingBottom/paddingLeft | number \| accessor | 四个方向单独控制的边距 |
nodeComponent | React 组件 | 自定义节点渲染(HierarchyRectangularNode) |
实现上(Treemap.tsx#L73-L83),padding系列参数统一经由 utils/setNumOrNumAccessor.ts 处理:无论你传一个数字还是返回数字的 accessor 函数,都会正确绑定到 d3 treemap 布局上。这意味着你完全可以按节点属性(如node.depth或node.value)动态决定边距:
import { Treemap, hierarchy, treemapSquarify } from '@visx/hierarchy'; const root = hierarchy(data).sum((d) => d.value); <Treemap root={root} tile={treemapSquarify} size={[500, 300]} round paddingInner={2} paddingOuter={4} nodeComponent={({ node }) => ( <rect x={node.x0} y={node.y0} width={node.x1 - node.x0} height={node.y1 - node.y0} fill={node.depth === 0 ? '#21D4FD' : '#b6fbff'} stroke="#fff" /> )} />;注意这里调用了root.sum((d) => d.value)——sum()是 treemap、pack、partition 布局计算数值的前置步骤,它把每个节点的value汇总到祖先节点,布局算法正是依据这些 value 分配面积。这也是HierarchyNode与普通对象最大的差异点之一。
渲染回调(children)与自定义组件
五种布局组件都支持两条渲染路径,源码结构完全一致:
const data = layout(root); if (children) return <>{children(data)}</>; // 渲染回调优先 return ( <Group top={top} left={left} className={cx('visx-tree', className)}> {/* 默认遍历 links() 与 descendants() 渲染 */} </Group> );(以 Tree.tsx#L68-L87 为例。)
- 渲染回调模式:传入
children后,组件只负责计算布局,把HierarchyPointNode(或HierarchyCircularNode/HierarchyRectangularNode)交给你自由渲染。这是构建交互式、动画化、异形节点图表的首选方式,典型用法如径向树图:
<Tree root={root} size={[2 * Math.PI, 400]}> {(tree) => ( <Group> {tree.links().map((link, i) => ( <path key={i} d={`M${link.source.x},${link.source.y} A... `} // 用弧线连接父子 fill="none" stroke="#999" /> ))} {tree.descendants().map((node, i) => ( <circle key={i} cx={node.x} cy={node.y} r={node.depth === 0 ? 8 : 4} fill="#21D4FD" /> ))} </Group> )} </Tree>默认渲染模式:不传
children时,组件遍历data.links()与data.descendants(),用linkComponent与nodeComponent逐个渲染。三个默认组件可直接复用或作为自定义组件的起点:HierarchyDefaultLink(HierarchyDefaultLink.tsx):渲染一条<line>,strokeWidth=2、stroke="#999"、strokeOpacity=0.6;HierarchyDefaultNode:渲染<circle>,r=15、fill="#21D4FD",兼容x/y/r与x/y两种节点结构;HierarchyDefaultRectNode:渲染<rect>,由x0/x1/y0/y1计算宽高。
所有默认渲染都包在@visx/group的<Group>中,且每种布局有独立的 className 前缀(visx-tree、visx-cluster、visx-pack、visx-partition、visx-treemap),便于全局样式覆盖。
小结:从数据到图表的四步路径
综合以上内容,用@visx/hierarchy做层次可视化的固定套路可以归纳为四步:
- 构造层级:用
hierarchy(data)(或stratify(rows))把原始数据变成HierarchyNode; - 汇总数值(仅面积类布局需要):调用
.sum((d) => d.value)让祖先节点累加 value; - 选择布局:树状结构用
Tree/Cluster,面积占比用Pack/Partition/Treemap,并通过size、padding、tile等 props 调参; - 定制渲染:要么用默认节点/连线组件快速出图,要么用
children渲染回调完全掌控输出,配合自定义组件实现径向树、旭日图等高级形态。
仓库配套的测试(如 test/Tree.test.tsx、test/Cluster.test.tsx、test/Defaults.test.tsx)覆盖了布局坐标、默认组件渲染与节点/连线计数等行为,可作为你自定义实现时的行为参考。此外,visx-demo包的 sandboxes 目录中有各布局的完整可运行示例,packages/visx-demo/src/sandboxes 下的 tree、treemap、pack、partition 等示例可直接对照学习。
由于布局完全依赖d3-hierarchy的确定性算法,同一份root在不同布局间切换时数据层零改动——这也是本包“以文档规定的 d3 根节点为唯一数据契约”设计理念的最大红利。
【免费下载链接】visx🐯 visx | visualization components项目地址: https://gitcode.com/gh_mirrors/vi/visx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考