Mermaid Ishikawa 图(鱼骨图):语法、配置与渲染实现详解
Ishikawa 图(又称鱼骨图、因果图)是 Mermaid 在 v11.12.3 引入的图表类型,用于将某一问题(如"照片模糊"、"服务器宕机")拆解为沿鱼脊展开的层次化原因,常用于质量分析和故障根因梳理(根因分析)。本文基于官方语法文档 docs/syntax/ishikawa.md,结合 ishikawa 图源码实现 与端到端测试用例,讲解其完整语法、缩进语义、可用配置项,以及"鱼头—鱼脊—鱼骨"的渲染布局原理。
图表定位与版本前提
Ishikawa 图用于表示某一事件(或问题)的成因。它还有几个常见别名:
- fishbone diagram(鱼骨图)
- herringbone diagram(鳗鱼骨图/人字骨图)
- cause-and-effect diagram(因果图)
图形形态模拟鱼的骨架:主要问题位于"鱼头",各级原因从中间的"鱼脊"向上下两侧分支延伸。
需要注意两点前提:
- 版本要求:该图表类型自 Mermaid v11.12.3 起可用。
- 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(照片模糊)是问题;Process、User、Equipment、Environment 是四大类原因(一级原因);LENS、SENSOR 是 Equipment 下的二级原因;Dirty lens、Damaged sensor 等是三级原因。这种"分类 → 子类 → 具体原因"的三层结构正是 Ishikawa 图在质量管理(如 4M1E:人、机、料、法、环)中的经典用法。
文本标签的换行
文档示例中未展示文本换行,但从渲染器源码 ishikawaRenderer.ts 可以确认两个文本处理机制:
splitLines会按<br>或\n拆分标签,因此标签文本中可以使用<br>实现手动换行,渲染为多行tspan;wrapText对超长文本做自动折行:一级原因标签超过 15 个字符时按空格折行,鱼头标签按max(6, 110 / (fontSize * 0.6))个字符的宽度上限折行,避免文字溢出图形。
语法细节:关键字与缩进语义
ishikawa-beta 与 ishikawa 两种写法均可
虽然文档统一使用 ishikawa-beta 关键字,但检测器 ishikawaDetector.ts 中的识别正则为 /^\s*ishikawa(-beta)?\b/i,即:
ishikawa-beta和ishikawa都会被识别为 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.ts 的 addNode 中:
- 第一行永远是根节点(问题/鱼头),与它的缩进无关,并被同时设为图表标题;
- 以第一个原因行的缩进作为基准级别
baseLevel(源码注释明确说明:baseLevel 取自第一个 cause 行而非 effect 行,以保证即使 effect 行缩进更深,原因之间的相对缩进仍被保留); - 后续行的层级为
rawLevel - baseLevel + 1,小于等于 0 时钳制为 1; - 通过一个栈维护当前父节点:弹出栈顶直到栈顶层级严格小于当前行,新行挂到栈顶节点下。
由此得到两条实用结论:
- 问题行的缩进不重要。即使问题行缩进比原因行还深,解析结果仍然正确。这一点有专门的单元测试覆盖: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-L50:drawConfig.ishikawa?.diagramPadding ?? 20 与 drawConfig.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.mmd、8-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 行)读出四步:
- 鱼头:
drawHead在鱼脊右端绘制一个鱼头形状(path的Q二次曲线勾勒出头轮廓),问题标签居中其中; - 上下两侧交替分布:一级原因按索引奇偶分配到上/下侧——
causes.filter((_, i) => i % 2 === 0)在上、i % 2 === 1在下(第 99-100 行)。因此原因的书写顺序决定了它出现在鱼脊的哪一侧:示例中Process、Equipment在上方,User、Environment在下方; - 鱼脊长度自适应:上/下两侧的长度按各自"后代节点总数"从
2 * SPINE_BASE_LENGTH的总池子中按比例分配,并保证不小于0.3 * SPINE_BASE_LENGTH,也不小于最大分支深度 × 2 × fontSize,从而避免标签重叠(第 102-119 行); - 骨内嵌套:
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 统一注册。
测试用例与示例索引
想验证行为边界或寻找更多写法时,可以参考仓库中的这些资源:
- 单元测试:packages/mermaid/src/diagrams/ishikawa/ishikawa.spec.ts 覆盖三种解析场景——基本层次结构、无缩进根节点嵌套原因、effect 行缩进比原因更深;
- e2e 渲染用例(e2e/diagrams/ishikawa/),文件名即断言意图,例如:
- 1-should-render-a-simple-ishikawa-diagram.mmd:最简两分支;
- 2-should-render-with-many-causes-on-both-sides.mmd:4M 场景,双侧多原因;
- 3-should-render-with-deeply-nested-causes.mmd 与 13-should-render-a-very-deep-nested-diagram.mmd:三级及以上深层嵌套(如"Server Outage → Hardware → Disk → Bad sectors");
- 4-should-render-with-a-single-cause.mmd、5-should-render-with-no-children-root-only.mmd:单原因与"只有问题没有原因"的退化情形(渲染器对
causes.length === 0有专门分支,仅绘制鱼头与鱼脊端点); - 9-should-render-with-custom-diagrampadding.mmd:
diagramPadding配置生效。
另外,仓库内置的 examples 包 提供了 ishikawa 官方示例,可在官方在线编辑器中直接查阅;本地演示页面见 demos/ishikawa.html。
小结
- 语法核心:
ishikawa-beta(或ishikawa)开头,第一行是问题,其余行按缩进组织原因层级;缩进以第一个原因行为基准,问题行缩进可随意; - 一级原因按书写顺序奇偶交替出现在鱼脊上/下两侧,深层原因以水平骨/斜骨交替形式嵌套;
- 可用配置仅
diagramPadding(默认 20)与useMaxWidth(默认 false),主题、手绘风格、字号走全局配置; - 作为 beta 图表,其语法未来可能演进,建议在文档中记录所用 Mermaid 版本以便复现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00