G6 Mindmap 布局(思维导图树布局)完全指南:方向、间距与左右分侧配置详解
G6 Mindmap 布局(思维导图树布局)完全指南:方向、间距与左右分侧配置详解
导读
Mindmap(思维导图树)布局是 G6 中专门用于树形结构层级排布的布局方案,支持根节点左右双侧展开,同一深度的节点会被放置在同一个层级上。本文基于 MindmapLayout.en.md 官方文档,结合 G6 仓库内的源码与测试用例,系统讲解 mindmap 布局的全部配置项、五种方向模式的行为差异、节点尺寸/间距的计算方式,以及基于 getSide 实现上游/下游血缘图、自定义思维导图等实战方案,读完即可在项目中落地使用。
Overview:Mindmap 布局的定位与特性
Mindmap 树布局适用于树形结构(Tree)的层级排布,其核心特点有两个:
- 支持左右双侧展开:根节点的子节点可以被分到根节点的左右两侧,形成典型的思维导图形态;
- 同深度节点对齐:处于同一深度的节点会被放置在同一个层级(layer)上,保证层级清晰;
- 会考虑节点尺寸:与某些忽略节点大小的树布局不同,mindmap 布局会结合节点实际尺寸进行排布(这一特点通过
getWidth/getHeight配置项体现,详见下文)。
在 G6 中,mindmap 布局的实现来源于 @antv/hierarchy 库。从源码看,packages/g6/src/layouts/index.ts 直接导出了 compactBox、dendrogram、indented、mindmap 四个树布局,并统一在 packages/g6/src/registry/build-in.ts 的内置扩展注册表中以 mindmap: mindmap 注册为内置布局。因此在 G6 中直接使用 type: 'mindmap' 即可,无需额外注册。
在 G6 运行时中,mindmap 属于树布局(Tree Layout),packages/g6/src/utils/layout.ts 中的 isTreeLayout 将 'compact-box'、'mindmap'、'dendrogram'、'indented' 四种类型识别为树布局;packages/g6/src/runtime/layout.ts 中的 stepLayout 会为树布局走 treeLayout 分支:先通过 createTreeStructure 建立树结构,再对每棵树的根节点执行布局函数,最后将布局结果转换为 G6 的 LayoutMapping 格式回写节点坐标。
最小可用配置示例
在 Graph 构造函数的 layout 字段中配置即可:
const graph = new Graph({
layout: {
type: 'mindmap',
direction: 'H',
preLayout: false,
getHeight: () => 32,
getWidth: () => 32,
getVGap: () => 16,
getHGap: () => 72,
},
});
type: 'mindmap' 是必须项;preLayout: false 表示不提前布局(关于 preLayout 的说明见下文“与运行时管线的配合”一节);其余四个 getXXX 均为尺寸/间距计算函数。
Options:配置项总览
下表汇总了 mindmap 布局的全部配置项:
| Property | Description | Type | Default | Required |
|---|---|---|---|---|
| type | 布局类型 | mindmap |
- | ✓ |
| direction | 布局方向,取值见下文 | H | LR | RL | TB | BT |
LR |
|
| getHeight | 计算每个节点高度的函数 | (d?: Node) => number | ✓ | |
| getWidth | 计算每个节点宽度的函数 | (d?: Node) => number | ✓ | |
| getVGap | 每个节点的垂直间距。注意:两个节点之间的实际垂直间距是该值的两倍 | (d?: Node) => number | ||
| getHGap | 每个节点的水平间距。注意:两个节点之间的实际水平间距是该值的两倍 | (d?: Node) => number | ||
| getSide | 设置节点放置在根节点左侧还是右侧。仅当 direction 为 H 时生效 |
(d?: Node) => string |
需要特别提醒的是 getVGap / getHGap 的语义:传入的值会被当作半间距处理,两个节点之间的实际间距是设定值的 2 倍。例如设置 getHGap: () => 72,则相邻层级之间的实际水平距离为 144。这是在使用中容易踩坑的点,调整间距时请按“实际间距 = 设定值 × 2”来换算。
direction:五种布局方向详解
可选值:
H|LR|RL|TB|BT,默认值为'LR'。
树布局方向决定了子节点相对根节点的排布方位:
'H'(水平/双侧):根节点的子节点被分为两部分,分别放置在根节点的左右两侧。可以通过getSide方法为每个节点指定左右分布逻辑;如果未提供getSide,默认将前半部分子节点放在右侧,后半部分放在左侧。'LR'(左到右):子节点放置在根节点的右侧,形成典型的从左向右的树形展开;'TB'(上到下):与'LR'行为一致,子节点放置在根节点的右侧展开(即纵向深度方向上仍按层级纵向堆叠);'RL'(右到左):子节点放置在根节点的左侧,方向与'LR'相反;'BT'(下到上):子节点放置在根节点的右侧,随后整个图沿 X 轴旋转 180°,视觉上呈自下而上的生长方向。
注:仓库文档中
'LR'与'TB'共用同一行为描述(子节点放在根节点右侧),在 G6 的 mindmap 布局中二者等价;需要真正上下方向生长的树,可参考同仓库的 DendrogramLayout 等其他树布局。
getWidth / getHeight:节点尺寸计算
两者签名均为 (d?: Node) => number,分别返回每个节点的宽度与高度。mindmap 布局会据此为节点预留空间,这也是本布局“考虑节点尺寸”的直接体现。配置时可以按节点 ID 差异化返回:
(d) => {
// d 是一个节点
if (d.id === 'testId') return 50;
return 100;
};
在真实项目中,更常见的做法是让布局尺寸与节点的视觉尺寸保持一致,例如直接读取节点样式的 size。仓库测试用例 packages/g6/tests/demos/layout-mindmap-h.ts 中所有节点统一返回 16(配合节点 size: 26),说明该值只参与布局占位计算,不强制等于节点渲染尺寸——若节点实际渲染尺寸大于布局占位,节点之间可能发生视觉重叠,因此实践中建议让 getWidth / getHeight 的返回值与节点样式尺寸匹配。
getHGap / getVGap:间距计算
签名均为 (d?: Node) => number,分别计算每个节点的水平/垂直间距。注意实际间距为设定值的 2 倍(见前文):
(d) => {
// d 是一个节点
if (d.id === 'testId') return 50;
return 100;
};
从测试用例可以看到典型取值:layout-mindmap-h.ts 中 getVGap: () => 10、getHGap: () => 50,即实际垂直间距 20、水平间距 100;layout-mindmap-h-left.ts 中 getHGap: () => 100,实际水平间距达到 200,用于更宽的树形展开。这些用例同时验证了当间距较大时,direction: 'H' 布局会把节点向两侧推开,配合 cubic-horizontal 边与左右两侧 ports,即可形成标准的思维导图视觉效果。
getSide:左右分侧逻辑(仅 H 方向生效)
签名:(d?: Node) => string。设置节点放置在根节点的左侧还是右侧,仅在 direction: 'H' 时生效。返回值约定为 'left' 或 'right'。若未设置,默认将前半部分子节点放在右侧,后半部分放在左侧。
(d) => {
// d 是一个节点
if (d.id === 'test-child-id') return 'right';
return 'left';
};
从源码结构看,getSide 的自动计算逻辑由 @antv/hierarchy 的 separate-root 模块完成:未提供 getSide 时,布局会对根节点的子节点按顺序对半切分,前一半排布在右侧、后一半排布在左侧。因此当你需要“按业务语义”而不是“按顺序”决定节点方位时(例如血缘图中的上游/下游划分),务必显式提供 getSide。
仓库测试用例 layout-mindmap-h-custom-side.ts 演示了自定义分侧:
getSide: (d) => {
if (d.id === 'Classification') {
return 'left';
}
return 'right';
},
与之配套的单元测试 packages/g6/tests/unit/layouts/mindmap.spec.ts 覆盖了四种 H 方向场景:h(默认分侧)、h-left(全部在左)、h-right(全部在右)、h-custom-side(自定义分侧),均通过 SVG 快照断言布局结果。
与运行时管线的配合:preLayout 与树数据准备
mindmap 布局处理的是树结构数据。在 G6 中,通常先用 treeToGraphData 将树数据转换为图数据再交给 Graph,测试用例中的典型用法为:
import { Graph, treeToGraphData } from '@antv/g6';
import data from '@@/dataset/algorithm-category.json';
const graph = new Graph({
data: treeToGraphData(data),
layout: {
type: 'mindmap',
direction: 'H',
// ...
},
});
preLayout 控制布局执行的时机:
preLayout: false(默认):布局在元素渲染后执行,此时可读取元素的真实尺寸;preLayout: true:在元素初始化前提前计算位置。从 packages/g6/src/runtime/layout.ts 的getLayoutData可以看出,preLayout模式会跳过尚未渲染的元素(不要求元素已存在),并跳过隐藏、折叠祖先下的节点,适合需要提前确定位置的场景。
此外,运行时会为布局自动注入画布尺寸与中心点:initGraphLayout 中读取 viewport.getCanvasSize() 得到宽高并以 <a href="https://link.gitcode.com/i/92334177ed26a11f16f951887534104d" target="_blank">width / 2, height / 2] 作为布局中心(见 [packages/g6/src/runtime/layout.ts),因此 mindmap 布局默认会以画布中心为根节点位置展开,一般无需手动指定 center。
实战:基于 H 方向构建血缘图与思维导图
文档明确给出 mindmap 布局的两类典型适用场景:
1. 数据血缘图(Data lineage graph)
使用 direction='H' 渲染指定节点的上下游血缘:getSide 将上游节点归到左侧、下游节点归到右侧,中间节点作为根。例如:
layout: {
type: 'mindmap',
direction: 'H',
getSide: (d) => (d.data.isUpstream ? 'left' : 'right'),
// ...
},
2. 思维导图(Mind map)
基于 direction='H' 加上 getSide(或默认对半分侧)构建自定义思维导图组件。仓库中的完整案例 packages/g6/tests/demos/case-mindmap.ts 演示了如何把 mindmap 布局与自定义节点、collapse-expand 折叠展开、cubic-horizontal 水平曲线边组合成带配色的完整思维导图:根节点样式居中显示标签,子节点透明填充、按层级取色,并通过 ports 声明左右锚点让边从两侧引出。社区通用的“左右侧标签对齐”技巧在测试用例中也有体现(见 layout-mindmap-h.ts):比较节点 x 与根节点 x 的差,x >= 0 的节点标签放右侧、文字左对齐,否则放左侧、文字右对齐,保证标签始终朝外、不压住分支线。
小结
Mindmap 布局是 G6 内置的树布局之一,注册于 packages/g6/src/registry/build-in.ts,实现来自 @antv/hierarchy 的 mindmap 方法(packages/g6/src/layouts/index.ts)。配置要点可归纳为:
type必填,direction控制五种方向形态,默认'LR';getWidth/getHeight让布局感知节点尺寸,建议与节点渲染尺寸保持一致;getHGap/getVGap以“半间距”语义生效,实际间距为设定值 2 倍;getSide仅对H方向有效,用于自定义左右分侧,是实现血缘图、复杂思维导图的关键;- 树数据需先经
treeToGraphData转换,preLayout决定布局相对渲染的执行时机。
更多同系列树布局可对比阅读 CompactBoxLayout、DendrogramLayout 与 IndentedLayout 文档。