Mermaid Ishikawa(鱼骨图)完全指南:用缩进语法绘制因果分析图(v11.12.3+)
Ishikawa 图(又称鱼骨图、石川图、因果图)是质量管理与根因分析中用于呈现“特定问题及其潜在原因”关系的经典工具。Mermaid 从 v11.12.3 起引入全新的 ishikawa 图类型,允许开发者像写 Markdown 一样用缩进文本描述“鱼骨”,并由渲染器自动生成完整的鱼骨骨架。本文将先讲解其声明式缩进语法与分层规则,再结合仓库源码剖析解析器、数据模型与渲染器,帮助你理解语法背后“鱼骨是怎么画出来的”,从而写出结构清晰、层级正确的 Ishikawa 图。
Ishikawa 图是什么
Ishikawa 图用于表达某个事件(问题)的原因分解结构,也被称为 fishbone(鱼骨)图、herringbone(鲱鱼骨)图或 cause-and-effect(因果)图。它的形态像一条鱼骨架:主问题位于“鱼头”,各类原因从“鱼脊椎”上分叉出去,形成一根根主骨与子骨。这种方法由日本质量管理学者石川馨(Kaoru Ishikawa)推广,常用于头脑风暴后的根因归类、缺陷分析与持续改进流程。
在 Mermaid 中,这一图类型的声明方式与原文档 syntax/ishikawa.md 描述的一致,并在仓库自动生成到面向文档站点的 docs/syntax/ishikawa.md。从 代码实现结构 与 测试文件 可以确认,它是 Mermaid 中一类独立注册的“外部图”(external diagram):ishikawa 只是起始关键字,图内容完全由“缩进 + 文本行”驱动。
基础语法:一图看懂
Ishikawa 图语法极其精简,只有三条规则:
- 第一行是图所表达的事件(问题),即鱼头;
- 后续每一行都是该事件的原因;
- “鱼骨”层级通过缩进表达:缩进越深,原因越细分(子原因)。
官方文档给出如下完整示例(仓库中同样收录在 示例程序 与 e2e 用例):
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
将其粘贴到任意支持 Mermaid v11.12.3 及以上的渲染环境(如 在线编辑器依赖说明见 usage 文档),即得到一张“模糊照片(Blurry Photo)”问题分析鱼骨图:
- 鱼头:
Blurry Photo(模糊照片) - 六根主骨(一级原因分类):
Process(流程)、User(用户)、Equipment(设备)、Environment(环境)……它们都缩进 4 个空格,代表并列的一级原因; - 子原因:例如
Equipment下缩进更多的LENS与SENSOR是设备分类下的二级分类,再下一层Dirty lens等则是具体原因(三级)。
注意:这是一个较新的图类型,语法可能在未来版本中演进。若需长期固化语法结果,请固定 Mermaid 版本并跟进 CHANGELOG 中的变更说明。
起始关键字:ishikawa-beta 与 ishikawa
虽然文档示例使用 ishikawa-beta,但从语法定义 parser/ishikawa.jison 的 lexer 规则可以看出,词法器同时接受 ishikawa-beta 与 ishikawa 两个起始词,且 %options case-insensitive 表明匹配是大小写不敏感的:
"ishikawa-beta" { return 'ISHIKAWA'; }
"ishikawa" { return 'ISHIKAWA'; }
相应地,负责“识别这段文本是不是 Ishikawa 图”的 detector 使用正则 /^\s*ishikawa(-beta)?\b/i 进行前缀匹配,二者均可触发延迟加载。因此 ishikawa-beta 是当前稳定的推荐写法,ishikawa 同样可用。
其他可用的语法细节
- 注释:
%%之后的内容在本行内会被视为注释(词法规则\s*\%\%.*),可用于解释某类原因,不影响渲染。 - 文本内容:除关键字外的整行文本都会成为节点文本;同一行内可以包含空格(如
Shutter speed too slow)。 - 空行:空白行(仅含空白或空行)在语法中被定义为
SPACELINE/NL并被安全忽略,不会打断原因行之间的父子归属。
这些规则在单元测试 ishikawa.spec.ts 中得到验证:诸如基本层级、无缩进根节点、效果行被额外缩进等输入都能正确解析。
缩进与层级:解析器如何理解“鱼骨”
Ishikawa 图的层级完全由前导空格数量决定。语法规则 parser/ishikawa.jison 将文本交给数据库层处理:
statement
: SPACELIST TEXT { yy.addNode($1.length, $2.trim()); }
| TEXT { yy.addNode(0, $1.trim()); }
也就是说,每一行文本会以“该行前导空格的数量(SPACELIST 长度,未缩进则为 0)+ 文本内容”的形式调用 addNode(level, text),数据模型层 ishikawaDb.ts 据此构建一棵多叉树(每个节点即 { text, children: [] },见 ishikawaTypes.ts):
- 第一个非空文本行(通常紧跟在关键字后的第一行)被视作根节点即鱼头事件,例如
Blurry Photo; - 从第二个文本行开始,第一行原因被作为基准层(
baseLevel); - 后续行的相对缩进差决定它在树中的深度:
- 与基准层缩进相同的行成为一级原因(直接挂在鱼头下);
- 比父层多缩进的行成为其子原因;
- 层级通过维护一个“栈”实现:每当新行层级不高于栈顶时,就弹出栈顶,直到栈顶为该行的父节点,再压栈。这保证了无论先写哪个分支,都能正确回溯。
一个重要的健壮性设计:基准层从首条“原因”而非“鱼头”计算
从 ishikawaDb.ts 的实现可以看到一个细节:
if (!this.root) {
this.root = { text: label, children: [] };
...
return;
}
// Set baseLevel from the first cause (not the effect/root line)
this.baseLevel ??= rawLevel;
let level = rawLevel - this.baseLevel + 1;
if (level <= 0) { level = 1; }
鱼头(根节点)并不参与基准层计算;基准层 baseLevel 取自第一条原因行的缩进。这样即使鱼头行与原因行的缩进并不一致(例如鱼头也被缩进了若干空格),各条原因之间的相对缩进关系依然保持正确。对应测试用例 'should handle effect indented more than causes' 覆盖了这一场景:
ishikawa-beta
Problem
Cause A
Subcause A1
Cause B
即便 Problem 行带缩进、而 Cause A 未缩进,解析结果依然是 Problem 作为根、Cause A/Cause B 作为两个并列子节点、Subcause A1 挂在 Cause A 之下。这一设计让新手不必纠结“鱼头要不要缩进”,同时相对缩进的可读性也被保留。
推荐实践:同级原因使用一致的缩进
虽然解析器具备容错能力,为了图和源码都清晰可读,仍然建议:
- 紧跟关键字的第一行写事件(问题);
- 所有一级原因使用相同数量的缩进(例如 4 个空格);
- 子原因在父原因基础上继续多缩进(例如每层 4 个空格);
- 不要混用空格与制表符——词法按空白数量(
SPACELIST长度)计算层级,若同一分支混用,缩进计数会不一致并造成意外的层级变化。
实战示例:用 Ishikawa 图做根因分析
鱼骨图最常见的落地场景是按 流程 / 人员 / 设备 / 环境 / 测量 等维度归类根因。仓库自带示例 packages/examples/src/examples/ishikawa.ts 中除了文档示例,还给出一个“外卖配送延误(Late Food Delivery)”的业务示例,可直接作为模板改写:
ishikawa-beta
Late Food Delivery
Process
Orders batched too long
Kitchen queue not prioritized
People
Not enough drivers on shift
New cook still in training
Equipment
Oven capacity too small
Delivery bags lose heat
Environment
Heavy rain
Road construction on main route
Measurement
No alert when prep time exceeds target
你可以把这个结构套用到任何根因分析场景:质量缺陷、事故复盘、KPI 未达标、代码缺陷分类等,只需要替换鱼头事件与各分类下的具体原因。若原因分类还需要再细分(例如设备下的“镜头”再拆出具体镜片问题),只需继续增加缩进即可,渲染器支持多层嵌套的“子骨”。
实时体验与渲染验证可参考 demos/ishikawa.html;仓库在 e2e/diagrams/ishikawa/ 目录维护了 Ishikawa 图的端到端渲染用例(如 1-should-render-a-simple-ishikawa-diagram.mmd),其对应快照测试位于 e2e/rendering/ishikawa/ishikawa.spec.ts。
渲染原理:骨架是如何“长”出来的
Ishikawa 图的渲染由 ishikawaRenderer.ts 完成。理解它的绘制策略,有助于解释文档示例中的一些“布局直觉”。
从脊椎到鱼头
渲染器首先在画布中以预置常量(如脊椎基础长度 SPINE_BASE_LENGTH = 250)确定脊椎位置,并在右端绘制“鱼头”:鱼头为一条曲线围成的“箭头形/鱼头形”路径(见 drawHead 中的 headPath),鱼头文本(事件)以加粗多行文本居中放置。整张图默认采用横向布局:问题在右,向左延伸出脊椎与主骨。
主骨的分边与分级分配
事件根节点下并列的所有一级原因会被按序分为上下两组(偶索引骨在脊椎上方,奇索引骨在下方):
const upperCauses = causes.filter((_, i) => i % 2 === 0);
const lowerCauses = causes.filter((_, i) => i % 2 === 1);
上下两条脊椎的长度并非固定:渲染器会统计每侧主骨子树的“后代总量”,按后代占比在 SPINE_BASE_LENGTH * 2 的区间内分配上/下脊椎长度,同时保证不小于最小长度与“按字体大小换算的最小间距”。这样,分支内容较多的一侧会自动获得更长骨架,从而减少文字与骨线之间的重叠。每根主骨相对脊椎以约 82°(常量 ANGLE) 张开,骨端点绘制分类标签框。
子骨的多层嵌套布局
当某根主骨下还有子原因时,渲染器使用“扁平化遍历 + 槽位分配”的策略放置文字与骨线(详见 flattenTree 与 drawBranch):
- 偶数深度子骨是水平线,从父骨上按比例取点向左延伸;
- 奇数深度子骨是斜线,在父骨与目标 Y 位置之间成角度连接;
- 文本按“先序/后序”交替的顺序占位(
yOrder),以保证斜骨落在其父骨张开的角度楔形内、不交叉相邻分支; - 每个标签会自动换行并约束在约 15 个字符宽度内;超长文本会被空格分词换行(
wrapText)。
这正是仓库 e2e 用例 中多层嵌套场景(如 LENS → Dirty lens)能保持分支清晰、标签不重叠的底层原因。从代码结构看,多行文本通过 tspan 逐行渲染,因此标签文本中换行符与 <br> 也会被拆分为多行。
箭头方向
在鱼骨语境中,因果流向通常理解为“从骨尾指向骨/鱼头”。非手绘模式下渲染器为骨线附加 marker 箭头(见 marker 与 ishikawa-arrow),而手绘模式由于 rough.js 不支持 SVG marker,会通过 drawArrowMarker 手工计算箭头三角形并绘制。
主题与手绘风格
Ishikawa 图复用 Mermaid 的主题体系,全部视觉变量定义在 ishikawaStyles.ts:
- 骨线(
ishikawa-spine、ishikawa-branch、ishikawa-sub-branch):使用主题lineColor,线宽 2(子骨为 1); - 鱼头(
ishikawa-head)与标签框(ishikawa-label-box):填充mainBkg、描边lineColor; - 文字统一使用主题的
fontFamily、fontSize、textColor,其中鱼头标签加粗(font-weight: 600)。
因此,切换 theme(default / neutral / dark / forest 等)或在 主题配置文档 中覆盖上述主题变量,即可整体改变鱼骨图的配色,无需逐元素手写样式。此外,渲染器会读取全局配置 look:当 look: 'handDrawn' 时(相关主题选项详见 主题文档 中的手绘说明),骨架、鱼头与标签框都会改用 rough.js 以手绘笔触绘制,handDrawnSeed 控制笔触随机种子以保证可复现。
图级配置项
Ishikawa 图实现了独立的配置接口 IshikawaDiagramConfig(定义见 config.type.ts,并在 配置 Schema 中以 ishikawa 键注册)。目前支持两个可选项:
| 配置键 | 类型 | 默认值 | 含义 |
|---|---|---|---|
ishikawa.diagramPadding |
number |
20 |
整图四周的内边距(像素),避免边缘文字被裁切 |
ishikawa.useMaxWidth |
boolean |
false |
是否让 SVG 尽可能占满父容器最大宽度 |
上述默认值可直接在渲染器源码中看到(drawConfig.ishikawa?.diagramPadding ?? 20、drawConfig.ishikawa?.useMaxWidth ?? false,见 ishikawaRenderer.ts)。useMaxWidth 的默认行为与其他部分图类型不同——它默认 false,即图会按实际像素尺寸渲染;如需让鱼骨图在响应式容器中自适应拉伸,请显式开启:
mermaid.initialize({
ishikawa: {
diagramPadding: 24,
useMaxWidth: true,
},
});
配置也可以写在 Mermaid 图的 YAML 前置指令(frontmatter/directives)中,相关写法见 directives 文档。
从源码看它的“运行机制”
如果你希望理解这一图类型在 Mermaid 内部如何被组织,可沿以下关键文件阅读:
| 关注点 | 文件 |
|---|---|
| 语法词法/语法定义(接受关键字、缩进、注释、空行) | packages/mermaid/src/diagrams/ishikawa/parser/ishikawa.jison |
| 数据结构与层级构建(栈式建树、基准层容错) | packages/mermaid/src/diagrams/ishikawa/ishikawaDb.ts |
| 节点类型定义 | packages/mermaid/src/diagrams/ishikawa/ishikawaTypes.ts |
| 渲染入口与绘制细节(头、脊椎、骨、标签、换行) | packages/mermaid/src/diagrams/ishikawa/ishikawaRenderer.ts |
| 主题样式变量 | packages/mermaid/src/diagrams/ishikawa/ishikawaStyles.ts |
| 图定义组装(parser/db/renderer/styles) | packages/mermaid/src/diagrams/ishikawa/ishikawaDiagram.ts |
| 文本检测与按需加载 | packages/mermaid/src/diagrams/ishikawa/ishikawaDetector.ts |
| 解析单元测试(覆盖缩进与层级用例) | packages/mermaid/src/diagrams/ishikawa/ishikawa.spec.ts |
| 端到端渲染用例与示例代码 | e2e/diagrams/ishikawa/ 、packages/examples/src/examples/ishikawa.ts |
从 diagram 注册编排 看,Ishikawa 属于可被按需动态导入的图类型之一(这也解释了为何 ishikawaDetector.ts 采用 import() 惰性加载),其新图引入的 beta 策略同时被 diagram-beta-policy.spec.ts 所约束——这是 ishikawa-beta 命名存在的上下文之一。
小结与使用清单
Mermaid 的 Ishikawa 图把经典的鱼骨分析法压缩成了一种“类 Markdown”的缩进语言。使用时可参考如下清单:
- 以
ishikawa-beta(或ishikawa)起首; - 首行(可缩进也可不缩进)书写待分析的事件/问题;
- 按统一缩进(如每层 4 空格)逐层书写一级原因分类与子原因;
- 用
%%书写行注释说明语境; - 需要时通过
ishikawa.diagramPadding与ishikawa.useMaxWidth微调边距与宽度,或通过主题与look: 'handDrawn'切换观感; - 在需要长期引用渲染结果的场景中锁定 Mermaid ≥ v11.12.3 的版本,并留意该新图语法的后续演进。
理解了解析器的“基准层取首条原因”与渲染器的“上下分骨 + 槽位排布”逻辑后,你就能预判缩进样式对最终布局的影响,快速写出不会重叠、语义清晰的鱼骨图。
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 StartedRust0624
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