首页
/ Mermaid Ishikawa 图(鱼骨图):语法、配置与渲染实现详解

Mermaid Ishikawa 图(鱼骨图):语法、配置与渲染实现详解

2026-09-06 14:52:14作者:冯梦姬Eddie

Ishikawa 图(又称鱼骨图、因果图)是 Mermaid 在 v11.12.3 引入的图表类型,用于将某一问题(如"照片模糊"、"服务器宕机")拆解为沿鱼脊展开的层次化原因,常用于质量分析和故障根因梳理(根因分析)。本文基于官方语法文档 docs/syntax/ishikawa.md,结合 ishikawa 图源码实现 与端到端测试用例,讲解其完整语法、缩进语义、可用配置项,以及"鱼头—鱼脊—鱼骨"的渲染布局原理。

图表定位与版本前提

Ishikawa 图用于表示某一事件(或问题)的成因。它还有几个常见别名:

  • fishbone diagram(鱼骨图)
  • herringbone diagram(鳗鱼骨图/人字骨图)
  • cause-and-effect diagram(因果图)

图形形态模拟鱼的骨架:主要问题位于"鱼头",各级原因从中间的"鱼脊"向上下两侧分支延伸。

需要注意两点前提:

  1. 版本要求:该图表类型自 Mermaid v11.12.3 起可用。
  2. Beta 状态:官方文档明确提示这是 Mermaid 中的新图表类型,"Its syntax may evolve in future versions"(语法可能在未来版本中演变)。当前语法已足够完整,但升级大版本时建议回归验证现有图文件。

该文档本身是自动生成文件,权威源位于 packages/mermaid/src/docs/syntax/ishikawa.md,修改文档时应编辑该文件。

基本语法

官方文档给出的标准示例:

ishikawa-beta
    Blurry Photo
    Process
        Out of focus
        Shutter speed too slow
        Protective film not removed
        Beautification filter applied
    User
        Shaky hands
    Equipment
        LENS
            Inappropriate lens
            Damaged lens
            Dirty lens
        SENSOR
            Damaged sensor
            Dirty sensor
    Environment
        Subject moved too quickly
        Too dark

语法规则非常简单,文档归纳为三条:

规则 说明
第一行 图表的事件(问题),渲染为右侧的"鱼头"
后续各行 事件的原因,按缩进形成树状层次
缩进(indentation) 表示"鱼骨"结构:缩进越深,原因层级越深

从示例可以看出典型的使用方式:Blurry Photo(照片模糊)是问题;ProcessUserEquipmentEnvironment 是四大类原因(一级原因);LENSSENSOREquipment 下的二级原因;Dirty lensDamaged sensor 等是三级原因。这种"分类 → 子类 → 具体原因"的三层结构正是 Ishikawa 图在质量管理(如 4M1E:人、机、料、法、环)中的经典用法。

文本标签的换行

文档示例中未展示文本换行,但从渲染器源码 ishikawaRenderer.ts 可以确认两个文本处理机制:

  • splitLines 会按 <br>\n 拆分标签,因此标签文本中可以使用 <br> 实现手动换行,渲染为多行 tspan
  • wrapText 对超长文本做自动折行:一级原因标签超过 15 个字符时按空格折行,鱼头标签按 max(6, 110 / (fontSize * 0.6)) 个字符的宽度上限折行,避免文字溢出图形。

语法细节:关键字与缩进语义

ishikawa-betaishikawa 两种写法均可

虽然文档统一使用 ishikawa-beta 关键字,但检测器 ishikawaDetector.ts 中的识别正则为 /^\s*ishikawa(-beta)?\b/i,即:

  • ishikawa-betaishikawa 都会被识别为 Ishikawa 图
  • 匹配不区分大小写case-insensitive,见 ishikawa.jison 中的 %options case-insensitive);
  • 要求关键字位于文本开头(允许前导空白)。

缩进决定层级,且以"第一个原因行"为基准

解析器(Jison 文法)对每行的处理见 ishikawa.jison

statement
  : SPACELIST TEXT    { yy.addNode($1.length, $2.trim()); }
  | TEXT              { yy.addNode(0, $1.trim()); }
  ...

即:每行传入"前导空白字符数"(缩进级别)和"去除首尾空白后的文本"。注意这里传入的是原始空白长度,而非折算后的层级。层级换算逻辑在数据模型 ishikawaDb.tsaddNode 中:

  1. 第一行永远是根节点(问题/鱼头),与它的缩进无关,并被同时设为图表标题;
  2. 以第一个原因行的缩进作为基准级别 baseLevel(源码注释明确说明:baseLevel 取自第一个 cause 行而非 effect 行,以保证即使 effect 行缩进更深,原因之间的相对缩进仍被保留);
  3. 后续行的层级为 rawLevel - baseLevel + 1,小于等于 0 时钳制为 1;
  4. 通过一个维护当前父节点:弹出栈顶直到栈顶层级严格小于当前行,新行挂到栈顶节点下。

由此得到两条实用结论:

  • 问题行的缩进不重要。即使问题行缩进比原因行还深,解析结果仍然正确。这一点有专门的单元测试覆盖:ishikawa.spec.ts 的 "should handle effect indented more than causes" 用例,以及对应的 e2e 用例 12-should-render-correctly-when-effect-is-indented-more-than-causes.mmd
  • 相对缩进决定父子关系,不需要精确的空格数(2 格、4 格混用也可以),只需保证子行比父行缩进更深。

数据模型是一个递归树 ishikawaTypes.ts

export interface IshikawaNode {
  text: string;
  children: IshikawaNode[];
}

所有标签在进入树之前会经过 sanitizeText 处理(ishikawaDb.ts#L43),遵循 Mermaid 全局的安全/转义策略。

配置项

Ishikawa 图支持独立的配置块,在 mermaid.initialize 中通过 ishikawa 字段设置。其类型定义见 config.schema.yaml 中的 IshikawaDiagramConfig

%%{init: {
  "ishikawa": {
    "diagramPadding": 20,
    "useMaxWidth": false
  }
}}%%
ishikawa-beta
  Blurry Photo
  Process
      Out of focus
  User
      Shaky hands
配置项 类型 默认值 说明
ishikawa.diagramPadding integer(最小 0) 20 整图四周的边距(像素),用于内嵌图表时留出边距
ishikawa.useMaxWidth boolean false 是否让 SVG 使用最大宽度模式

配置默认值在 defaultConfig.ts 中合并进全局配置;渲染器读取方式见 ishikawaRenderer.ts#L49-L50drawConfig.ishikawa?.diagramPadding ?? 20drawConfig.ishikawa?.useMaxWidth ?? false

此外,IshikawaDiagramConfig 通过 allOf 继承 BaseDiagramConfig,即其他图表共享的基础配置(如主题变量)同样适用。特别值得注意的两个全局配置对其有直接影响:

  • look: 'handDrawn':渲染器会切换到 rough.js 手绘风格(roughness: 1.5、hachure 填充的标签框、手绘鱼头与箭头),并用 handDrawnSeed 控制随机性,见 ishikawaRenderer.ts#L44-L62;e2e 目录下的 e2e/diagrams/ishikawa/handdrawn 保存了该风格的回归用例。
  • fontSize:影响折行宽度与最小行间距(minSpacing = fontSize * 2,见 ishikawaRenderer.ts#L115)。

主题方面,e2e 用例覆盖了 forest 与 dark 主题(7-should-render-with-forest-theme.mmd8-should-render-with-dark-theme.mmd),说明颜色取自 themeVariables,可随全局主题切换。

渲染布局原理

渲染器 ishikawaRenderer.ts 把树状数据布局成"鱼"的几何结构,关键常量如下(第 19-26 行):

常量 作用
SPINE_BASE_LENGTH 250 鱼脊基准长度
BONE_STUB / BONE_BASE / BONE_PER_CHILD 30 / 60 / 5 无子节点的短骨长度、有子节点的骨干长度、每个子节点额外增加的长度
ANGLE 82° 主骨(一级原因斜线)与水平方向的夹角

布局流程可以从 draw 函数(第 36-150 行)读出四步:

  1. 鱼头drawHead 在鱼脊右端绘制一个鱼头形状(pathQ 二次曲线勾勒出头轮廓),问题标签居中其中;
  2. 上下两侧交替分布:一级原因按索引奇偶分配到上/下侧——causes.filter((_, i) => i % 2 === 0) 在上、i % 2 === 1 在下(第 99-100 行)。因此原因的书写顺序决定了它出现在鱼脊的哪一侧:示例中 ProcessEquipment 在上方,UserEnvironment 在下方;
  3. 鱼脊长度自适应:上/下两侧的长度按各自"后代节点总数"从 2 * SPINE_BASE_LENGTH 的总池子中按比例分配,并保证不小于 0.3 * SPINE_BASE_LENGTH,也不小于 最大分支深度 × 2 × fontSize,从而避免标签重叠(第 102-119 行);
  4. 骨内嵌套drawBranch 递归处理子节点。flattenTree 将子树展平后为每个标签分配 Y 坐标(第 232-263 行):偶数深度标签用"前序"放置(靠近鱼脊侧),奇数深度用"后序"放置,目的是让斜骨始终落在父骨张开的楔形区域内。每个子节点再交替绘制水平骨(偶数深度,向左侧水平延伸)或斜骨(奇数深度,按 82° 角折回)。

SVG 中的语义化 class 便于自定义样式与 CSS 覆盖:ishikawa-spine(鱼脊)、ishikawa-branch(主骨)、ishikawa-sub-branch(子骨)、ishikawa-head / ishikawa-head-group / ishikawa-head-label(鱼头)、ishikawa-label(标签文本)、ishikawa-label-box(标签背景框)。对应样式集中在 ishikawaStyles.ts。非手绘模式下,分支末端通过 marker-start 引用 ishikawa-arrow-{id} 箭头,使箭头指向鱼脊,表达"原因指向问题"的语义(第 64-78 行、146-147 行)。

整个渲染管线的注册入口是 ishikawaDiagram.ts:标准的 Mermaid 图表定义对象,把 Jison parser、IshikawaDB、renderer 与 styles 装配在一起,由 diagram-orchestration.ts 统一注册。

测试用例与示例索引

想验证行为边界或寻找更多写法时,可以参考仓库中的这些资源:

另外,仓库内置的 examples 包 提供了 ishikawa 官方示例,可在官方在线编辑器中直接查阅;本地演示页面见 demos/ishikawa.html

小结

  • 语法核心:ishikawa-beta(或 ishikawa)开头,第一行是问题,其余行按缩进组织原因层级;缩进以第一个原因行为基准,问题行缩进可随意;
  • 一级原因按书写顺序奇偶交替出现在鱼脊上/下两侧,深层原因以水平骨/斜骨交替形式嵌套;
  • 可用配置仅 diagramPadding(默认 20)与 useMaxWidth(默认 false),主题、手绘风格、字号走全局配置;
  • 作为 beta 图表,其语法未来可能演进,建议在文档中记录所用 Mermaid 版本以便复现。
登录后查看全文
热门项目推荐
相关项目推荐