G6 脑图树布局(Mindmap)完全指南:左右双向展开的树布局配置与实战

原创2026-09-23 15:19:57541 阅读
文章标签:数据可视化前端图表库

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 返回固定值,可以实现全部节点单侧排布:

layout: {
  type: 'mindmap',
  direction: 'LR',        // 子节点统一排布在根节点右侧
  getHeight: () => 32,
  getWidth: () => 32,
  getVGap: () => 4,
  getHGap: () => 100,
}

布局适用场景

根据官方文档与站点示例,Mindmap 布局主要适用于以下两类场景:

  1. 数据血缘图:direction: 'H' 非常适合渲染血缘图中查看指定节点上下游血缘的场景——上游分布在中心节点的左侧,下游分布在右侧。结合 getSide 按节点类型分侧(如数据表在左、产出物在右),可以在同一张图中直观呈现完整的数据流向。

  2. 思维导图:利用 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 示例目录,你可以快速搭建出数据血缘图、思维导图等典型应用。

登录后查看全文
G6