G6 Fishbone Layout 实战指南:在 @antv/g6 中用鱼骨图布局呈现因果与层次数据
2026/9/24 9:46:01 网站建设 项目流程
  • 数据可视化
  • 前端
  • 图表库

【免费下载链接】G6

♾ A Graph Visualization Framework in JavaScript.

项目地址:https://gitcode.com/gh_mirrors/g6/G6
点击查看免费下载

Fishbone Layout(鱼骨图布局)是 @antv/g6 内置的一种层次化图布局,它通过模拟鱼骨形态把树形数据按层级排列成“鱼头 + 鱼脊 + 鱼肋”的结构,特别适合因果分析、质量管理、故障归因等需要展示因果链与分类关系的场景。本文以 Fishbone.en.md 为主线,结合 fishbone.ts 源码与 layout-fishbone.ts 测试用例,带你掌握 Fishbone 布局的完整配置项、底层坐标计算原理与实战接入方式,读完即可独立完成一张可交互的质量分析鱼骨图。

Fishbone Layout 是什么

Fishbone Layout 是 G6 内置的、专门用于表示层次结构数据的图形布局。它通过模拟鱼骨的形状,将数据节点按照层次结构排列,使数据的层次关系更加清晰直观。在 fishbone.ts 的类注释中,官方将其对应到经典的 Ishikawa 图(石川图,又称因果图):鱼头是问题或结果,鱼脊是主干,斜向伸出的“鱼肋”是各类原因分支。

该布局特别适用于三类数据集:

  • 层次结构数据:如组织结构、分类体系;
  • 问题分析过程:如故障分析、质量分析(典型如 QC 七大手法中的因果图);
  • 决策过程:如决策树、要素分析。

由于它要求数据具备树形层次,使用前通常需要将嵌套的树形数据转换为 G6 的图数据(nodes + edges)。G6 提供了现成的转换工具treeToGraphData(实现见 tree.ts),它会深度优先遍历树,为每个节点记录depth深度,并把children转换为节点 id 数组与source/target边。

快速上手:最小可用配置

Fishbone 是 G6 内置布局,直接以type: 'fishbone'声明即可,无需额外注册:

import { Graph, treeToGraphData } from '@antv/g6'; const graph = new Graph({ layout: { type: 'fishbone', direction: 'LR', hGap: 50, vGap: 50, getRibSep: () => 60, }, });

一个完整的最小示例(树形数据 + 折线边 + 画布拖拽):

import { Graph, treeToGraphData } from '@antv/g6'; const graph = new Graph({ layout: { type: 'fishbone', }, autoFit: 'view', data: treeToGraphData({ nodes: [ { id: 'root', data: { label: 'Root' } }, { id: 'child1', data: { label: 'Child 1' } }, { id: 'child2', data: { label: 'Child 2' } }, { id: 'child3', data: { label: 'Child 3' } }, ], edges: [ { id: 'e1', source: 'root', target: 'child1' }, { id: 'e2', source: 'root', target: 'child2' }, { id: 'e3', source: 'root', target: 'child3' }, ], }), edge: { type: 'polyline', style: { lineWidth: 3, }, }, behaviors: ['drag-canvas'], });

几点实战建议(均有源码佐证):

  • 官方示例普遍搭配edge.type: 'polyline'。这是因为 Fishbone 布局会在布局结果中为每条边写入controlPoints(见 fishbone.ts),折线边利用这些控制点可绘出贴合“鱼脊 + 鱼肋”走向的路径;
  • 建议开启autoFit: 'view',树越深布局范围越大,自动适配视口可以避免节点溢出;
  • 交互上可叠加drag-canvaszoom-canvasdrag-element(参考 layout-fishbone.ts)。

配置项详解

Fishbone 的完整配置项与默认值如下表(来源:Fishbone.en.md 与 FishboneLayoutOptions):

属性说明类型默认值必填
type布局类型fishbone-
direction布局方向:RL(从右到左,鱼头在右)、LR(从左到右,鱼头在左)RL|LRRL
hGap水平间距number节点最大宽度*
vGap垂直间距number节点最大高度*
getRibSep获取鱼肋间距的函数(node: NodeData) => number() => 60
width布局宽度number画布宽度
height布局高度number画布高度
nodeSize节点尺寸number | [number, number] | [number, number, number] | ((node: NodeData) => number | [number, number] | [number, number, number])-
isLayoutInvisibleNodes不可见节点是否参与布局(preLayout为 true 时生效)boolean-
nodeFilter参与该布局的节点筛选(node: NodeData) => boolean-
preLayout使用前布局,在初始化元素前计算布局(不适用于流水线布局)boolean见下文说明

*注:表格中 hGap/vGap 官方文档未给出默认值,但源码在execute()中做了兜底——当二者未配置时,会用所有节点的最大宽度/高度作为默认间距(见 fishbone.ts)。

direction:鱼头朝向

direction决定鱼头(根节点)的位置:

  • RL:从右到左,鱼头在右——这是源码static defaultOptions中的默认值(fishbone.ts),也是质量分析场景最常用的方向;
  • LR:从左到右,鱼头在左。

从实现看,RL并不是重新算一遍坐标,而是在LR布局完成后执行一次水平镜像:把每个节点的x变为width - x,边的控制点同步镜像(见rightToLeft方法,fishbone.ts)。因此width的取值会影响RL方向的最终坐标范围。

hGap / vGap:间距与斜率

hGap控制相邻层级间的水平距离,vGap控制同侧分支的垂直间距。源码中calculateX对奇数深度节点使用hGap / vGap作为斜率(fishbone.ts),因此二者的比值直接决定鱼肋的倾斜程度:hGap相对越大,鱼肋越平缓;vGap相对越大,鱼肋越陡峭。交互调试时(参考 fishbone.md),官方示例将二者放在 20~100 区间内步进 10 调整。

getRibSep:鱼肋间距

getRibSep是一个以节点为入参的函数,返回该节点所在鱼肋(一级分支)与其相邻鱼肋之间的间距,默认() => 60。在doLayout中,ribX(鱼脊上各肋的锚点 X 坐标)由getSize(root)[0] + getRibSep(root)起步(fishbone.ts),并且每处理一个深度为 1 的节点都会用x + getRibSep(node)更新临时肋间距(fishbone.ts)。这意味着你可以按节点返回不同的值,实现“大分支更宽、小分支更窄”的自适应鱼骨。

nodeSize

节点尺寸既可以是数值/数组,也可以是一个返回尺寸的函数,最终经parseSize规范化为标准尺寸参与间距计算(formatSize方法,fishbone.ts)。当节点使用不同大小(如按权重缩放)时,通过函数形式返回可以让布局间距与实际渲染尺寸保持一致。

preLayout 与 isLayoutInvisibleNodes

preLayout表示在初始化元素之前先计算布局。G6 在inferLayoutOptions中会自动为fishbone等布局打开preLayout: true(见 runtime/options.ts,其中 L35 将'fishbone'列入清单)。因此你通常无需手动配置它;文档同时提示该选项“不适用于流水线布局”,当你使用流水线式布局编排时需注意这一限制。isLayoutInvisibleNodes则控制在preLayout: true时不可见节点是否参与布局计算,用于控制visibility相关场景的布局稳定性。

nodeFilter

nodeFilter继承自BaseLayoutOptions(layouts/types.ts),用于筛选哪些节点参与本次布局,便于在同一画布中做局部重排。

源码级原理解析:坐标是怎么算出来的

深入 doLayout,可以发现 Fishbone 布局的核心是一组基于树深度奇偶性的坐标推导:

  1. 单根约束getRoot()通过model.getRootsData()获取根节点,当根节点数量为空或超过 2 个时直接返回(fishbone.ts),取第一个根作为鱼头。因此 Fishbone 面向的是单根树数据,多根或不连通图不会生效。

  2. 深度奇偶分流isAtEvenDepth判断节点深度是否为偶数(fishbone.ts)。偶数深度节点的 X 由“辅助点 + 水平偏移”推算,奇数深度节点则沿鱼脊方向按hGap / vGap斜率推算,两条路径共同保证了树形不会重叠。

  3. 交替翻转placeAlterative会把索引为奇数的鱼肋(index % 2 !== 0的一级分支)连同其全部子孙绕鱼头 Y 轴做垂直镜像(fishbone.ts)。这就是鱼骨图“上下交错排布”的视觉效果来源——偶数肋在上、奇数肋在下,视觉上呈鱼骨状。

  4. 边控制点:布局完成后,每条入边都会获得一个控制点(getAuxiliaryPoint(node), y或父节点 y),并写入边的controlPoints(fishbone.ts),折线边据此画出符合鱼骨走向的连线。

  5. 结果回写execute最终把节点坐标与边控制点通过assignElementStyle合并进元素样式后返回(fishbone.ts)。

从架构上看,Fishbone 与内置布局一样继承自抽象基类BaseLayout(base-layout.ts),并在 build-in.ts 中以fishbone: FishboneLayout注册进内置布局表,同时从 layouts/index.ts 对外导出FishboneLayoutFishboneLayoutOptions类型。

完整实战:质量管理因果鱼骨图

经典的质量分析场景是“人机料法环测”(Man Power / Machine / Material / Method / Measurement / Milieu)六大要素。下面这段代码来自 layout-fishbone.ts,用树形数据构造了一张完整的多层鱼骨图,并通过 GUI 面板动态切换direction

import { Graph, treeToGraphData } from '@antv/g6'; const data = { id: 'Quality', children: [ { id: 'Machine', children: [{ id: 'Mill' }, { id: 'Mixer' }, { id: 'Metal Lathe', children: [{ id: 'Milling' }] }], }, { id: 'Method' }, { id: 'Material', children: [ { id: 'Masonite', children: [ { id: 'spearMint' }, { id: 'pepperMint', children: [{ id: 'test3' }] }, { id: 'test1', children: [{ id: 'test4' }] }, ], }, { id: 'Marscapone', children: [{ id: 'Malty' }, { id: 'Minty' }], }, { id: 'Meat', children: [{ id: 'Mutton' }] }, ], }, { id: 'Man Power', children: [ { id: 'Manager' }, { id: "Master's Student" }, { id: 'Magician' }, { id: 'Miner' }, { id: 'Magister', children: [{ id: 'Malpractice' }] }, { id: 'Massage Artist', children: [{ id: 'Masseur' }, { id: 'Masseuse' }] }, ], }, { id: 'Measurement', children: [{ id: 'Malleability' }] }, { id: 'Milieu', children: [{ id: 'Marine' }] }, ], }; export const layoutFishbone = async (context) => { const graph = new Graph({ ...context, container: 'container', autoFit: 'view', data: treeToGraphData(data), node: { type: 'rect', style: { size: [32, 32], label: false, labelFill: '#262626', labelFontFamily: 'Gill Sans', labelMaxLines: 2, labelMaxWidth: '100%', labelPlacement: 'center', labelText: (d) => d.id, labelWordWrap: true, }, }, edge: { type: 'polyline', style: { lineWidth: 3 }, }, layout: { type: 'fishbone', vGap: 48, hGap: 48, direction: 'RL', }, behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'], animation: false, }); await graph.render(); layoutFishbone.form = (panel) => { const config = { type: 'fishbone', direction: 'RL' }; return [ panel .add(config, 'direction', ['LR', 'RL']) .name('Direction') .onChange((value) => { graph.setLayout((prev) => ({ ...prev, direction: value })); graph.render(); }), ]; }; return graph; }; layoutFishbone();

这段代码的要点:

  • 数据形态:传入的是嵌套children的树,treeToGraphData负责转换成图数据;文档中还有一种等价写法,直接给节点数据加depth字段、children存子节点 id 数组,效果一致;
  • 动态切换方向:通过graph.setLayout((prev) => ({ ...prev, direction: value }))局部更新布局配置,再调用graph.render()重新布局。文档与 fishbone.md 均提示:切换方向后部分节点可能溢出屏幕,建议重新执行fitView(或保持autoFit: 'view');
  • 样式配合:矩形节点 + 居中标签 + 折线边,是鱼骨图最直观的呈现组合。

小结

Fishbone Layout 把经典的 Ishikawa 因果图带进了 G6:direction控制鱼头朝向,hGap/vGap决定鱼肋斜率,getRibSep调节肋间距,底层通过深度奇偶分流、奇数肋交替翻转与RL镜像三套机制生成鱼骨形态。配合treeToGraphData转换树数据、polyline折线边复用布局控制点,你可以在几十行代码内构建出可用于质量管理、故障归因、决策分析的交互式鱼骨图。想进一步调整细节,可参考交互式演示 fishbone.md 的 GUI 配置面板,或直接在 layout-fishbone.ts 基础上改造数据与样式。

  • 数据可视化
  • 前端
  • 图表库

【免费下载链接】G6

♾ A Graph Visualization Framework in JavaScript.

项目地址:https://gitcode.com/gh_mirrors/g6/G6
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询