G6 生态树(Dendrogram)布局实战指南:方向、间距与径向模式配置详解
G6 生态树(Dendrogram)布局实战指南:方向、间距与径向模式配置详解
导读
本文围绕 G6(AntV 图可视化框架)内置的 dendrogram 生态树布局展开,结合 packages/site/examples/layout/dendrogram 目录下的三个官方示例(生态树、垂直布局、径向布局),系统讲解如何将树形数据转换为图数据、如何配置布局方向(LR/RL/TB/BT/H/V)、如何通过 nodeSep 与 rankSep 控制间距,以及如何开启 radial 辐射模式。读完后,你将能够独立复现 G6 中最常见的三类层次树可视化,并理解其在源码中的注册与执行原理。配套的参数速查可参考 DendrogramLayout.zh.md。
什么是生态树布局
生态树(Dendrogram)布局是层次聚类数据可视化的经典方案。它最显著的特征是:所有子节点被排布在同一层级上,节点自身大小不参与布局计算,即每个节点在布局时被当作 1px 的“点”处理,最终呈现为一棵结构清晰、层级分明的“鱼骨式”层级树。它与思维导图(Mindmap)的关键区别在于:生态树中同一父节点的所有子节点共享同一层级(同轴对齐),而不会像思维导图那样围绕父节点逐层展开。
在 G6 中,dendrogram 属于四种树图布局之一。从 布局工具函数 可以看到,框架内部通过 isTreeLayout 统一识别四类树布局:
export function isTreeLayout(options: STDLayoutOptions) {
const { type } = options;
return ['compact-box', 'mindmap', 'dendrogram', 'indented'].includes(type);
}
该布局的核心实现由 @antv/hierarchy 提供,并在 布局导出文件 中导出:
export { compactBox, dendrogram, indented, mindmap } from '@antv/hierarchy';
随后在 内置扩展注册表 中以 dendrogram: dendrogram 的形式注册为内置布局,因此你在 Graph 配置中直接使用 layout.type: 'dendrogram' 即可,无需额外引入。
数据准备:从树数据到图数据
生态树布局输入的是树形嵌套数据,而 G6 的 Graph 接收的是 { nodes, edges } 结构的图数据。两者之间的桥梁是 treeToGraphData。其转换逻辑位于 树工具函数:它通过深度优先遍历(DFS)将每个树节点转换为图节点,同时把“父节点 → 子节点”的关系生成为图边,并顺带为每个节点记录 depth 深度。
在官方示例中,数据来自远程 JSON(也可直接使用仓库内置数据集 algorithm-category.json):
import { Graph, treeToGraphData } from '@antv/g6';
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
const graph = new Graph({
container: 'container',
autoFit: 'view',
data: treeToGraphData(data),
// ...节点、边、布局配置
});
graph.render();
});
要点:treeToGraphData 默认将节点 children 数组映射为边 source → target,并保留节点的 id;如果你的树数据字段名不同(例如使用 subNodes),可传入自定义 getter 覆盖 getNodeData、getEdgeData、getChildren 三个取数函数。
三种官方示例逐行拆解
示例目录 dendrogram 下包含三个 demo,分别对应生态树的三种形态:水平方向生态树、垂直方向生态树、径向生态树。下面逐一展开。
示例一:生态树(左右方向,LR)
文件:basic.js。这是最典型的“根在左、向右生长”的生态树:
import { Graph, treeToGraphData } from '@antv/g6';
function isLeafNode(d) {
return !d.children || d.children.length === 0;
}
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
const graph = new Graph({
container: 'container',
autoFit: 'view',
data: treeToGraphData(data),
node: {
style: {
labelText: (d) => d.id,
labelPlacement: (d) => (isLeafNode(d) ? 'right' : 'left'),
labelBackground: true,
// 左右方向布局下,边从节点右侧接入、左侧接出
ports: [{ placement: 'right' }, { placement: 'left' }],
},
animation: {
enter: false,
},
},
edge: {
// 水平三次贝塞尔曲线,适合 LR/RL 方向
type: 'cubic-horizontal',
animation: {
enter: false,
},
},
layout: {
type: 'dendrogram',
direction: 'LR', // H / V / LR / RL / TB / BT
nodeSep: 36,
rankSep: 250,
},
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
});
graph.render();
});
值得注意的细节:
labelPlacement通过回调按节点类型动态设置:叶子节点标签放在右侧,非叶子节点放在左侧,避免标签互相遮挡;labelBackground: true为文字添加底色,提升可读性;ports定义了边的接入/接出锚点,保证cubic-horizontal边水平进出节点;animation.enter: false关闭入场动画,保证大数据量下首帧快速呈现;collapse-expand行为让树支持点击折叠/展开,适合层级很深的分类树。
仓库中与之一致的测试用例见 layout-dendrogram-basic.ts,并经由 dendrogram.spec.ts 做快照回归验证(preLayout: true 表明该 demo 在元素初始化前即完成布局计算)。
示例二:垂直布局(上下方向,TB)
文件:vertical.js。将方向改为 TB(根在上、向下生长),并使用垂直三次贝塞尔边 cubic-vertical:
import { Graph, treeToGraphData } from '@antv/g6';
function isLeafNode(d) {
return !d.children || d.children.length === 0;
}
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
const graph = new Graph({
container: 'container',
autoFit: 'view',
data: treeToGraphData(data),
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
node: {
style: (d) => {
const style = {
labelText: d.id,
labelPlacement: 'right',
labelOffsetX: 2,
labelBackground: true,
// 垂直方向布局下,边从节点顶部接入、底部接出
ports: [{ placement: 'top' }, { placement: 'bottom' }],
};
if (isLeafNode(d)) {
Object.assign(style, {
// 叶子节点文字旋转 90° 并平移,避免竖向排列时互相挤压
labelTransform: [
['rotate', 90],
['translate', 18],
],
labelBaseline: 'center',
labelTextAlign: 'left',
});
}
return style;
},
animation: {
enter: false,
},
},
edge: {
type: 'cubic-vertical',
animation: {
enter: false,
},
},
layout: {
type: 'dendrogram',
direction: 'TB', // H / V / LR / RL / TB / BT
nodeSep: 50,
rankSep: 120,
},
});
graph.render();
});
该示例展示了垂直生态树的典型优化技巧:当树在垂直方向生长时,叶子节点通常密集排列,因此对叶子节点的标签执行 rotate 90° 旋转,并配合 labelBaseline: 'center'、labelTextAlign: 'left' 让竖排文字整齐对齐,是处理高密度叶节点的实用方案。
示例三:径向布局(radial)
文件:radial.js。仅需将 radial 置为 true,整棵树便以根节点为圆心呈辐射状分布,适合表现以根为核心的层次数据:
import { Graph, treeToGraphData } from '@antv/g6';
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
.then((res) => res.json())
.then((data) => {
const graph = new Graph({
container: 'container',
autoFit: 'view',
data: treeToGraphData(data),
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
node: {
style: {
labelText: (d) => d.id,
labelBackground: true,
},
animation: {
enter: false,
},
},
layout: {
type: 'dendrogram',
radial: true,
nodeSep: 40,
rankSep: 140,
},
});
graph.render();
});
径向模式与普通模式相比,只多了一个 radial: true,其余配置完全复用。需要注意的是:径向布局下节点标签围绕圆环分布,若标签较多,可结合 G6 的 place-radial-labels 变换(示例见 transform-place-radial-labels.ts)自动避让,相关转换实现位于 place-radial-labels.ts。
配置项详解
综合示例代码与 DendrogramLayout.zh.md 手册,dendrogram 布局的核心配置项如下:
| 属性 | 描述 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 布局类型,必须为 dendrogram |
dendrogram |
- | ✓ |
| direction | 布局方向,可选值见下方说明 | LR | RL | TB | BT | H | V |
LR |
|
| nodeSep | 节点间距,即同一层级内相邻节点之间的距离(像素) | number | 20 | |
| rankSep | 层级间距,即相邻两个层级之间的距离(像素) | number | 200 | |
| radial | 是否启用辐射状布局 | boolean | false |
除了上表,布局还继承自 BaseLayoutOptions 的通用能力,包括:
preLayout:是否在元素初始化前预先计算布局位置(示例一对应的测试用例即开启了该项);nodeFilter/comboFilter:按条件筛选参与布局的节点/组合;width/height:布局区域宽高,默认取画布尺寸;animation:是否启用布局动画过渡;enableWorker:是否将布局放到 Web Worker 中执行(适合超大图)。
direction 六种方向
direction 决定树的生长方向,示例中注释 H / V / LR / RL / TB / BT 即全部可选值:
LR:根节点在左,向右生长(默认值,示例一);RL:根节点在右,向左生长,与LR镜像对称;TB:根节点在上,向下生长(示例二);BT:根节点在下,向上生长,与TB镜像对称;H:根节点在中间,左右水平对称展开,适合根节点居中的家族谱系图;V:根节点在中间,上下垂直对称展开。
实际选型建议:LR/RL 方向配合 cubic-horizontal 边,TB/BT 方向配合 cubic-vertical 边,视觉上最顺滑。
radial 辐射模式
radial: true 启用辐射模式后,节点以根节点为中心按同心圆环分布,同一层级的节点落在同一半径上。官方手册建议:启用 radial 时,将 direction 设为 'LR' 或 'RL' 可获得最佳效果,因为这两种方向在径向模式下对应角度计算最自然。
布局的底层执行原理
从源码视角看,一个 dendrogram 布局的完整执行链路如下:
- 注册:
dendrogram在 build-in.ts 中注册为内置布局,运行时根据layout.type查表取用; - 识别:utils/layout.ts 中的
isTreeLayout将其归入树图布局族,与其他树布局共享同一套数据处理管线; - 数据转换:utils/tree.ts 的
treeToGraphData将嵌套树数据拍平为{ nodes, edges }; - 布局计算:核心算法来自
@antv/hierarchy的dendrogram函数(layouts/index.ts),其基本思想是忽略节点尺寸(等价于 1px 点),仅依据nodeSep与rankSep排布坐标,因此无论节点是 20px 还是 200px,都不会影响树的结构间距; - 测试验证:dendrogram.spec.ts 对
basic与tb两种形态分别创建 demo 图并做 SVG 快照断言,确保布局输出在版本迭代中保持稳定。
与节点、边、行为组合的完整建议
将上述三个示例的配置组合起来,可以总结出一套可直接套用的最佳实践:
const graph = new Graph({
container: 'container',
autoFit: 'view',
data: treeToGraphData(treeData),
node: {
style: {
labelText: (d) => d.id,
labelBackground: true,
labelPlacement: (d) => (d.children?.length ? 'left' : 'right'),
ports: [{ placement: 'left' }, { placement: 'right' }],
},
},
edge: { type: 'cubic-horizontal' },
layout: {
type: 'dendrogram',
direction: 'LR',
nodeSep: 36, // 同层节点间距
rankSep: 250, // 层级间距
},
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'collapse-expand'],
});
- 节点:
labelText用回调取d.id;labelPlacement按叶子/非叶子动态切换左右;labelBackground增强文字对比度;ports与边类型保持一致(水平树用左右锚点,垂直树用上下锚点); - 边:
LR/RL配cubic-horizontal,TB/BT配cubic-vertical,径向模式可考虑cubic-radial(示例); - 行为:
collapse-expand支持节点折叠展开(对应实现 collapse-expand.ts),配合drag-canvas、zoom-canvas、drag-element即可获得可交互的完整体验; - 大数据优化:为节点与边关闭
animation.enter,并按需开启preLayout与enableWorker。
小结
G6 的 dendrogram 生态树布局通过“忽略节点尺寸、同级对齐”的策略,为层次聚类数据提供了三种即插即用的可视化形态:水平生态树(LR)、垂直生态树(TB)与径向树(radial)。核心配置仅有 direction、nodeSep、rankSep 与 radial 四项,配合合适的边类型与 collapse-expand 行为即可搭建出完整、可交互的层级树应用。更全面的布局参数说明与方向示意图可进一步查阅 DendrogramLayout.zh.md(英文版见 DendrogramLayout.en.md),示例源码可直接复用 dendrogram 示例目录。