G6 生态树(Dendrogram)布局实战指南:方向、间距与径向模式配置详解

原创2026-09-23 17:31:501,343 阅读
文章标签:数据可视化前端图表库

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 布局的完整执行链路如下:

  1. 注册:dendrogram 在 build-in.ts 中注册为内置布局,运行时根据 layout.type 查表取用;
  2. 识别:utils/layout.ts 中的 isTreeLayout 将其归入树图布局族,与其他树布局共享同一套数据处理管线;
  3. 数据转换:utils/tree.ts 的 treeToGraphData 将嵌套树数据拍平为 { nodes, edges };
  4. 布局计算:核心算法来自 @antv/hierarchy 的 dendrogram 函数(layouts/index.ts),其基本思想是忽略节点尺寸(等价于 1px 点),仅依据 nodeSep 与 rankSep 排布坐标,因此无论节点是 20px 还是 200px,都不会影响树的结构间距;
  5. 测试验证: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 示例目录。

登录后查看全文
G6