G6 脑图树布局(Mindmap)完全指南:左右双向展开的树布局配置与实战
G6 脑图树布局(Mindmap)完全指南:左右双向展开的树布局配置与实战
导读
本文面向使用 G6 进行树形数据可视化(思维导图、数据血缘图等)的开发者,系统讲解内置 mindmap 布局的核心机制:从布局方向(H / LR / RL / TB / BT)的选择、节点尺寸与间隙的计算函数,到 getSide 左右分侧策略的自定义。读完本文,你将能基于 G6 的 Mindmap 布局快速搭建左右双向展开的脑图组件,并能通过源码理解其底层实现,按需调整布局细节。
概述:什么是 Mindmap 布局
脑图树布局(Mindmap)是 G6 内置的层次化树布局之一,专门用于树状结构数据的展示。它有两大显著特点:
- 支持左右两侧展开:节点的子节点可以被放置在根节点的左侧、右侧或两侧,呈现经典的思维导图形态;
- 深度对齐 + 感知节点大小:深度相同的节点会被放置在同一层(同一水平/垂直层级)上,并且布局会考虑节点的大小——这也是为什么必须提供
getWidth/getHeight来告诉布局每个节点的尺寸。
在 G6 源码中,mindmap 布局由 packages/g6/src/layouts/index.ts 从 @antv/hierarchy 的 mindmap 实现直接导出,并经 packages/g6/src/registry/build-in.ts 注册为内置布局,同时通过 packages/g6/src/exports.ts 以 MindmapLayout 命名对外导出。在 packages/g6/src/utils/layout.ts 中,mindmap 与 compact-box、dendrogram、indented 一同被归为树布局(tree layout),需要配合树形数据使用。
基本配置方式
在创建 Graph 实例时,通过 layout 字段即可启用脑图树布局:
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
// 树形数据需要通过 treeToGraphData 转换为图数据
data: treeToGraphData(treeData),
layout: {
type: 'mindmap',
direction: 'H', // 水平布局,子节点分布在根节点左右两侧
preLayout: false, // 关闭前布局,见下文说明
getHeight: () => 32, // 每个节点的高度
getWidth: () => 32, // 每个节点的宽度
getVGap: () => 16, // 垂直间隙(节点间实际垂直间隙为 2 倍 vgap)
getHGap: () => 72, // 水平间隙(节点间实际水平间隙为 2 倍 hgap)
},
});
graph.render();
其中 treeToGraphData 是 G6 提供的树数据转换工具,定义于 packages/g6/src/utils/tree.ts,它接收嵌套的树形 JSON(如 { id, children: [...] }),将其转换为布局所需的图数据结构。
关于 preLayout 的说明
示例中显式设置了 preLayout: false,这与 G6 的布局选项推断机制有关。在 packages/g6/src/runtime/options.ts 的 inferLayoutOptions 中,G6 会对布局配置做自动推断:
// <zh/> 下列布局的标签位置待适配,需要手动配置 preLayout false
// <en/> The label position of the following layouts needs to be adapted,
// and preLayout needs to be manually configured as false
'mindmap', // 与其他布局一同被列入该名单
也就是说,mindmap 等树布局的标签位置(labelPlacement)尚未完全适配前布局(preLayout)流程,在标签位置依赖布局结果(如 H 方向下左侧节点的标签应放在节点左边)时,需要手动将 preLayout 设为 false,让元素在布局计算完成后才进行初始化与定位。反之,若标签位置不依赖布局结果,则可以省略或设为 true。
preLayout 及其他通用布局选项(如 nodeFilter、comboFilter、enableWorker、animation 等)的类型定义可参考 packages/g6/src/layouts/types.ts。
配置项总览
Mindmap 布局的核心配置项如下表所示:
| 属性 | 描述 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 布局类型 | mindmap |
- | ✓ |
| direction | 布局方向,可选值见下文 | H | LR | RL | TB | BT |
LR |
|
| getHeight | 计算每个节点的高度 | (d?: Node) => number |
✓ | |
| getWidth | 计算每个节点的宽度 | (d?: Node) => number |
✓ | |
| getVGap | 每个节点的垂直间隙(节点间实际垂直间隙为 2 倍 vgap) | (d?: Node) => number |
||
| getHGap | 每个节点的水平间隙(节点间实际水平间隙为 2 倍 hgap) | (d?: Node) => number |
||
| getSide | 设置节点排布在根节点的左侧/右侧;仅 H 方向生效 |
(d?: Node) => string |
需要特别指出的是:getWidth 与 getHeight 是必选配置——由于布局需要考虑节点大小以完成层级对齐与防重叠,缺少它们布局将无法正确计算节点位置。
direction:布局方向详解
H|LR|RL|TB|BT,默认值为'LR'
树布局的方向决定了子节点相对根节点的排布方位:
-
'H'(horizontal,水平):根节点的子节点被分成两部分,横向放置在根节点的左右两侧。可传入getSide方法指定每个节点的左右分布逻辑;不传时,默认将前半部分子节点放置在右侧,后半部分放置在左侧。这是最贴合思维导图形态的方向,也是"左右双向展开"能力的来源。 -
'LR'(Left to Right):所有子节点统一排布在根节点的右侧,呈现常规的自左向右的树形结构。 -
'TB'(Top to Bottom):与'LR'类似,所有子节点统一排布在根节点的右侧,但整体呈现自上而下的纵向展开效果。 -
'RL'(Right to Left):所有子节点统一排布在根节点的左侧,是LR的镜像形态。 -
'BT'(Bottom to Top):将子节点排布在根节点的右侧,然后将整个图沿 X 轴旋转 180°,形成自下而上的布局效果。
提示:
LR与TB在子节点相对根节点的方位上一致(均在右侧),差别主要体现在整体图结构的纵深方向上;BT则可理解为LR的旋转版本。选择哪个方向,取决于你的数据语义与阅读习惯(例如血缘图中"上游在左、下游在右"更符合直觉)。
尺寸与间隙:getWidth / getHeight / getHGap / getVGap
这四个回调函数用于计算每个节点的几何信息,入参 d 是当前节点数据对象,返回数值。它们支持按节点差异化计算,是让布局"感知节点大小"的入口。
getWidth:节点宽度
(d?: Node) => number
(d) => {
// d 是一个节点
if (d.id === 'testId') return 50;
return 100;
};
getHeight:节点高度
(d?: Node) => number
(d) => {
// d 是一个节点
if (d.id === 'testId') return 50;
return 100;
};
getHGap:水平间隙
(d?: Node) => number
每个节点的水平间隙。注意:实际两个节点间的水平间隙是 2 倍的 hgap——布局在节点的左右两侧各留出一个 hgap 的空间,因此相邻两列节点之间的空白距离为 2 * hgap。
(d) => {
// d 是一个节点
if (d.id === 'testId') return 50;
return 100;
};
getVGap:垂直间隙
(d?: Node) => number
每个节点的垂直间隙。与水平间隙同理,实际两个节点间的垂直间隙是 2 倍的 vgap。在 H 方向下,该值直接决定了同一侧相邻子节点之间的疏密程度。
(d) => {
// d 是一个节点
if (d.id === 'testId') return 50;
return 100;
};
getSide:左右分侧策略(仅 H 方向)
(d?: Node) => string
getSide 用于手动指定某个节点排布在根节点的左侧还是右侧,返回 'left' 或 'right'。注意:
- 该参数仅在
direction为'H'时生效; - 如未设置,算法会自动分配左右侧:默认将子节点的前半部分放置在右侧,后半部分放置在左侧(自动分配逻辑由底层
@antv/hierarchy的 separate-root 逻辑实现); - 返回值为
'left'的节点及其子树会被放置在根节点左侧,返回'right'的节点及其子树则被放置在右侧。
示例:
(d) => {
// d 是一个节点
if (d.id === 'test-child-id') return 'right';
return 'left';
};
getSide 实战:按节点属性分侧
仓库站点示例 custom-side.js 展示了一个完整的按节点 id 分侧的用法——将 Classification 节点及其子树放到左侧,其余全部放到右侧:
layout: {
type: 'mindmap',
direction: 'H',
preLayout: false,
getHeight: () => 32,
getWidth: () => 32,
getVGap: () => 4,
getHGap: () => 64,
getSide: (d) => {
if (d.id === 'Classification') {
return 'left';
}
return 'right';
},
}
配套的测试用例可参考 layout-mindmap-h-custom-side.ts,其中还展示了如何结合 graph.getNodeData() 判断节点相对根节点的 X 偏移,动态设置标签位置与对齐方式:
node: {
style: function (this: Graph, model) {
const root = this.getNodeData().find((node) => node.depth === 0);
const rootX = Number(root?.style?.x ?? 0);
const x = Number(model.style?.x ?? 0) - rootX;
return {
labelText: model.id,
size: 26,
// 右侧节点的标签放在右边,左侧节点的标签放在左边
labelPlacement: x >= 0 ? 'right' : 'left',
labelTextAlign: x >= 0 ? 'start' : 'end',
ports: [{ placement: 'right' }, { placement: 'left' }],
};
},
},
edge: { type: 'cubic-horizontal' },
这里的关键技巧是:在 H 方向下,节点的 x 坐标相对根节点为正则位于右侧,为负则位于左侧,据此可以精确控制左右两侧节点的标签朝向、端口位置,并搭配 cubic-horizontal 水平三次曲线边,形成标准的思维导图视觉样式。
单侧布局示例:LR 与全左/全右
如果不使用 getSide,H 方向默认会把子节点平分成左右两半(前半在右、后半在左,见 auto-side.js);而通过 getSide 返回固定值,可以实现全部节点单侧排布:
- 全部在左侧:
getSide: () => 'left',参考 layout-mindmap-h-left.ts 与站点示例 left-side.js; - 全部在右侧:
getSide: () => 'right',参考 layout-mindmap-h-right.ts; - 单侧树形:直接使用
direction: 'LR'也可实现子节点全部在根节点右侧的经典树形(right-side.js):
layout: {
type: 'mindmap',
direction: 'LR', // 子节点统一排布在根节点右侧
getHeight: () => 32,
getWidth: () => 32,
getVGap: () => 4,
getHGap: () => 100,
}
布局适用场景
根据官方文档与站点示例,Mindmap 布局主要适用于以下两类场景:
-
数据血缘图:
direction: 'H'非常适合渲染血缘图中查看指定节点上下游血缘的场景——上游分布在中心节点的左侧,下游分布在右侧。结合getSide按节点类型分侧(如数据表在左、产出物在右),可以在同一张图中直观呈现完整的数据流向。 -
思维导图:利用
H方向的左右双向展开特性,构建自定义的思维导图组件。可配合collapse-expand、drag-canvas、zoom-canvas等内置行为(见测试用例 layout-mindmap-h.ts 的behaviors配置),实现节点的展开/收起与画布漫游。
小结
Mindmap 布局是 G6 内置树布局中形态最灵活的一种:通过 direction 控制整体排布方位,通过 getWidth / getHeight / getHGap / getVGap 让布局感知节点尺寸并控制间距,通过 getSide 实现左右分侧自定义。实际使用时注意两点即可:一是 getWidth / getHeight 为必选项;二是当标签位置依赖布局结果时,需将 preLayout 显式设为 false。结合 layout-mindmap 系列示例 与站点 mindmap 示例目录,你可以快速搭建出数据血缘图、思维导图等典型应用。