用代码画流程图/时序图
介绍 Mermaid 语法,通过在 Markdown 文档中嵌入 Mermaid 库,并通过 Mermaid 的 DSL 代码块,结合 Mermaid 生成时序图/类图/流程图等。
本文主要介绍 Mermaid 语法。Mermaid 是一个 JS 库,通过在 Markdown 文档中嵌入 Mermaid 库,并通过 Mermaid 的 DSL 代码块,结合 Mermaid 生成时序图/类图/流程图等。
前言
我已记不起是从哪里开始接触到的 Markdown,暂且认为从 Git 仓库 readme.md 开始的吧。
不过最让我印象深刻的是 Markdown 可以用编写代码的方式生成一份看起来过得去的文档。
在这之前,我对文档的理解一直是以微软 Office 套件为基础模型的,再厉害些,应该是要写一份 HTML 文档才对。
直到我接触到了 Markdown,再到后来我入职了 成都掌控者(Holder)。公司要求 2 天的时间,将开发任务的实施细节落实到文档中。
再后来,在参考一位同僚间绰号为 clojure 的同事所输出的文档中,了解到了 Mermaid。Mermaid 的表现力让我感到十分惊艳。
直到现在,我仍然对在职期间接触到这些内容心存感激。特别是我在后来撰写自己的文档时,我逐渐理解了一份好的文档,对于理解一个复杂的系统和上下文作用有多重要。特别是面对一个复杂系统的情况下。
本文写作动机是为补充现有的文档文字过多和未来编写新的文档而准备的笔记。因此在本文发布后,根据后期需要,可能后期会对内容做一些额外的补充。
说明
具备以下知识储备,才能流畅阅读本文内容:
参考资料
目录
基本语法
此部分介绍一个 Mermaid 语法(代码)应该包含哪些结构。为后续在编写 Mermaid 语法(代码)的时候,对整体有个了解,而不是只知道怎么出图。
一. Mermaid 语句(代码)块
在 Markdown 中渲染一个 Mermaid 图表,与在 Markdown 中插入代码片段类似。为了类比相似性,这里将插入一段 JavaScript 代码片段
```javascript
function sum(){
return Array.from(arguments).reduce((curr,pre)=>pre+curr)
}
```
渲染效果如下
function sum(){
return Array.from(arguments).reduce((curr,pre)=>pre+curr)
}
相似的, Mermaid 语法如下所示
```mermaid
<在此区域编写mermaid代码>
```
例如
```mermaid
---
title: 流程图标题
---
graph TD
A-->B
A-->C
B-->D
C-->D
```
渲染效果如下
---
title: 流程图标题
---
graph TD
A-->B
A-->C
B-->D
C-->D
此部分的演示仅仅提供一个样例,代码块中的具体部分将在下文详细讲解
二. 语句分类
涉及到的代码块总共分为 3 大类:图例配置、图例类型及方向、图例语句。
1. 图例配置
原则上,Mermaid 有两类配置:全局配置和图例自定义配置。
全局配置
在你的项目脚手架中,或者脚手架插件中,亦或者直接通过 npm 包 mermaid 直接使用的。
不管通过什么方式初始化的,通过初始化时传入的配置,都称为全局配置,所有的图例都会用到全局的配置,当然也可以在图例中自定义属于图例自身的配置。
此部分本文不会涉及全局配置部分,因为这是另外一个话题,跟本文主题不一致。
图例配置
第二类是图例代码块中,用于超过全局配置优先级的配置,换句话说,如果图例提供了自身配置,那么将优先使用图例自身的,而不是全局的。
例如我需要为一个流程图独立配置一个标题,并且绘制方式采用手绘风格:
```mermaid
---
title: 图例标题配置
config:
look: handDrawn
---
flowchart LR
start[开始] --> I{判断些什么}
I ---|满足|做些什么
I ---|不满足|做些其他
做些什么 & 做些其他 --> 结束((结束))
```
渲染效果如下
---
title: 图例标题配置
config:
look: handDrawn
---
flowchart LR
start[开始] --> I{判断些什么}
I ---|满足|做些什么
I ---|不满足|做些其他
做些什么 & 做些其他 --> 结束((结束))
此部分可以参考官方文档。
2. 图例类型及绘制方向
图例类型非常简短,一个图例有两个部分组成:一个是图例类型,一个是图例绘制方向。
类型为第一个语法词组
例如,流程图类型。
[!TIP] 以下代码只需要关注高亮的行
```mermaid
graph
id
```
或
```mermaid
flowchart
id
```
得到图例
graph
id
又或者时序图
```mermaid
sequenceDiagram
小王->>小李: 最近咋样?
小李-->>小王: 还不错!
```
渲染结果如下
sequenceDiagram
小王->>小李: 最近咋样?
小李-->>小王: 还不错!
图例绘制方向为图例类型的第二个词组,是可选的
例如,一个朝下的图例
```mermaid
flowchart TB
start[开始] --> I{判断些什么}
I ---|满足|做些什么
I ---|不满足|做些其他
做些什么 & 做些其他 --> 结束((结束))
```
得到的渲染结果
flowchart TB
start[开始] --> I{判断些什么}
I ---|满足|做些什么
I ---|不满足|做些其他
做些什么 & 做些其他 --> 结束((结束))
一个朝从左往右的图例
```mermaid
flowchart LR
start[开始] --> I{判断些什么}
I ---|满足|做些什么
I ---|不满足|做些其他
做些什么 & 做些其他 --> 结束((结束))
```
渲染结果如下
flowchart LR
start[开始] --> I{判断些什么}
I ---|满足|做些什么
I ---|不满足|做些其他
做些什么 & 做些其他 --> 结束((结束))
其中,总共包含四个方向,如下表所示:
| 符号 | 代表绘制方向 |
|---|---|
| TB 或 TD | 自上而下 |
| BT | 自下而上 |
| LR | 从左往右 |
| RL | 从右往左 |
有些图例可以嵌套,可以嵌套的图例即图例中还可以嵌套一些子图例。例如,流程图中可能存在子流程一样。 因此图例类型也可以分两类:一类是代码块中的顶级图例类型,二类是子图例。
此处按照流程图的子图举个例子:
```mermaid
flowchart
s-->se
s-->s2
subgraph 子图标题
s2-->s2_e
end
```
渲染结果如下
flowchart
s-->se
s-->s2
subgraph 子图标题
s2-->s2_e
end
3. 图例语句
如果确定了配置和类型,接下来的语句块中就是具体图例细节了。此部分会根据图例类型的不同而产生变化。
这里可以猜测一下其代码实现:Mermaid 的实现方式应该包含根据不同的实例类型,而执行不同的模块代码,从而实现模块之间的解耦才可能有可维护性。
之所以这么看待,因为 不同的图例采用的线条语法和节点图形都有较大差别。
[!WARNING] 一些特殊字符和单词可能会导致图形不正常渲染,如下表:
| 符号/单词 | 作用 | 解决办法 |
|---|---|---|
| %% | 符号表示单行注释,%% 符号后面的内容将不会正常渲染,常用于内部注释 | |
| end | 此单词用于结束子图的标识 | 需要使用到本单词,只需要将双引号包裹即可,像这样 “end” |
更详细的信息可参考官方说明。
Mermaid 图例
Mermaid 的图例有非常多,可以参考 Mermaid 的简介部分内容。
到本文发布时,包含以下图例:
- 流程图
- 时序图
- 甘特图
- 类图
- Git 图
- 实体关系图
- 象限图
- XY 图(ECharts 柱状图)
- 状态图
- 用户旅程图
- 饼图
- 需求关系图
- C4(实验)
- 思维导图(实验)
- 时间线
- ZenUML
- 桑基图
- 块图
- 包图(数据块/数据包)
- 看板图
- 构建流程图
- 雷达图
- 树图
本文不会全部举例,而是只举例 流程图 和 时序图,因为在博客中用得比较多。
流程图
正如基本语法中的案例一样,流程图类型以 graph 开头。或者还有个别名:flowchart。
因此以下两种写法得到的图例是一样的,当然 只有流程图有个别名,其他图例就没有了。
```mermaid
graph
id
```
或
```mermaid
flowchart
id
```
得到图例仅包含一个图形节点的流程图
graph
id
流程图基本内容
流程图语句块包含以下内容:流程节点(图形)、关系(线段和箭头)、子图。
流程节点是流程图的基本元素之一,可以为节点图形块中添加一个自定义的文本内容。
例如:
```mermaid
---
title: 具有文本内容的节点
---
flowchart
L["这是L节点的文本内容, 本节点的标识/id是L"]
m[这是m节点的文本内容, 本节点的标识/id是m]
```
渲染效果如下
---
title: 具有文本内容的节点
---
flowchart
L["这是L节点的文本内容, 本节点的标识/id是L"]
m[这是m节点的文本内容, 本节点的标识/id是m]
其中,中括号前面 L 和 m,则表示节点的唯一 标识/id,后续可通过此 标识/id 用于与其他节点建立关系,记住这一点。
其中注释节点使用 ”` `” 包裹,则被包裹的部分则可以采用 Markdown 编写,不可过于依赖此特性,因为其仅支持部分特性。
```mermaid
---
title: 具有 markdown 文本内容的节点
---
flowchart LR
m["`这是 **markdown** 内容`"]
```
渲染效果
---
title: 具有 markdown 文本内容的节点
---
flowchart LR
m["`这是 **markdown** 内容`"]
1. 节点图形
在 Mermaid 11.3.0 之前的版本,仅通过键盘上 ({[\|/]})> 字符就可以表示以下简单图形。
以表格汇总如下:
| 包裹符号 | 表示形状 |
|---|---|
() | 圆边直角四边形 |
([]) | 体育场跑道形状 |
[[]] | 子例程 |
[()] | 圆柱形 |
(()) | 圆形 |
>] | 标签(不规则图形) |
{} | 菱形 |
{{}} | 六边形 |
[//] 或 [\\] | 平行四边形 |
[/\] 或 [\/] | 等腰梯形 |
((())) | 双圆形 |
[!TIP] 在 Mermaid 官方在 11.3.0 新增的新型语法可以绘制更加复杂的图形,本文不再介绍,可以参考官方文档。
举个例子:
我想要一个 圆柱形的节点
```mermaid
---
title: 圆柱形节点
---
flowchart
D[(数据库)]
```
渲染结果如下
---
title: 圆柱形节点
---
flowchart
D[(数据库)]
或者我想要个平行四边形形状的节点
```mermaid
---
title: 平行四边形图形
---
flowchart
D[/平行四边形/]
```
渲染结果如下
---
title: 平行四边形图形
---
flowchart
D[/平行四边形/]
又或者我想要个不规则图形的节点
```mermaid
---
title: 不规则图形
---
flowchart
D>不规则图形文本内容]
```
渲染结果如下
---
title: 不规则图形
---
flowchart
D>不规则图形文本内容]
2. 关系
流程图中每个流程节点的关系建立通过线段和箭头在可视化图形上建立关系。
其中关系的链接中包含线段和箭头部分,线段和箭头分别可以有多种表示方法。
建立关系基本代码结构语法如下:
```mermaid
flowchart
NODE<关系描述符号>NODE
```
通过表格汇总如下,请 注意空格:
| 关系符号 | 线段 & 箭头说明 |
|---|---|
NODE --- NODE2 | 实线 |
NODE --> NODE2 | 实线 & 箭头 |
NODE ---> NODE2 | 有箭头 & 加长的实线 |
NODE-- 文本描述 ---NODE2 | 实线 & 描述 |
NODE---|文本描述|NODE2 | 实线 & 描述 |
NODE-- 文本描述 -->NODE2 | 实线 & 箭头 & 描述 |
NODE-->|文本描述|NODE2 | 实线 & 箭头 & 描述 |
NODE -.- NODE2 | 虚线 |
NODE-.-|文本描述|NODE2 | 虚线 & 描述 |
NODE-.|文本描述|.-NODE2 | 虚线 & 描述 |
NODE-.->|文本描述|NODE2 | 虚线 & 描述 & 箭头 |
NODE === NODE2 | 加粗实线 |
NODE ==> NODE2 | 加粗实线 & 箭头 |
NODE== 关系描述 ===NODE2 | 加粗实线 & 描述 |
NODE== 关系描述 ==>NODE2 | 加粗实线 & 箭头 & 描述 |
NODE ~~~ NODE2 | 不可见关系 |
NODE --> NODE2 --> NODE3 | 链式关系 |
NODE --> NODE2 & NODE3 --> NODE4 | 共同目标关联 |
NODE & NODE2 --> NODE3 & NODE4 | 交叉关联 |
包含箭头的连接
```mermaid
---
title: 包含箭头的关系
---
flowchart
D[节点] --> T((结束))
```
渲染效果
---
title: 包含箭头的关系
---
flowchart
D[节点] --> T((结束))
不包含箭头的连接
```mermaid
---
title: 没有箭头的关系
---
flowchart LR
D[节点] --- T((结束))
```
渲染效果
---
title: 没有箭头的关系
---
flowchart LR
D[节点] --- T((结束))
包含文字描述的连接
```mermaid
---
title: 关系描述I型
---
flowchart
D>不规则图形文本内容] --线性联系--> t((结束))
```
渲染效果
---
title: 关系描述I型
---
flowchart
D>不规则图形文本内容] --线性联系--> t((结束))
或
```mermaid
---
title: 关系描述II型
---
flowchart
D[节点]-- 关系描述 ---T((结束))
```
渲染效果
---
title: 关系描述II型
---
flowchart
D[节点]-- 关系描述 ---T((结束))
[!TIP] 更高级的语法,包括给关系(线段)命名(标识/ID),通过给线段命名(标识/ID)后,将动画效果与其命名绑定,即可实现矢量动画。
动画例子:
```mermaid
flowchart LR
NODE_A l@==> NODE_B
l@{ animate: true }
```
渲染效果
flowchart LR
NODE_A l@==> NODE_B
l@{ animate: true }
如果需要自定义动画效果,可以参考官方文档。
时序图
图例类型为 sequenceDiagram。
序列图是一种交互图,用于显示过程如何相互运行以及顺序。
那么作为交互的基本元素中,包含 参与方、交互方向(箭头) 和 交互内容。
基本元素和语法如下:
```mermaid
sequenceDiagram
小王->>小李: 最近咋样?
小李-->>小王: 还不错!
```
例如,参与方类型为演员名称为 Alice 与普通参与方类型 Blob 交互:
```mermaid
sequenceDiagram
actor Alice
participant Bob
Alice->>Bob: hello
```
sequenceDiagram actor Alice participant Bob Alice->>Bob: hello
一. 交互方类型
交互方类型只有两种:actor 和 participant。但是还有个不知道从什么版本引入的特性,通过 @ 语法可以为普通参与方类型制定不一样的图标。
例如,我想指定一个普通参与方类型为数据库的图标,@ 语法后面为一个 JSON 对象。例如下面的例子:
```mermaid
sequenceDiagram
actor Alice
participant Bob@{ "type": "database" }
Alice->>Bob: connection
```
sequenceDiagram
actor Alice
participant Bob@{ "type": "database" }
Alice->>Bob: connection
其中根据官方例子,参与方类型可以包含如下类型:
| 类型 | 描述 |
|---|---|
boundary | 边界 |
control | 控制 |
entity | 实体 |
database | 数据库 |
collections | 收藏/收集 |
queue | 队列 |
二. 参与方别名
当参与方过多时,不想写参与方过长的名字,还可以为参与方设置一个别名(标记/ID/标识)。
例如:
```mermaid
sequenceDiagram
participant A as Alice
participant J as John
A->>J: Hello John, how are you?
J->>A: Great!
```
sequenceDiagram participant A as Alice participant J as John A->>J: Hello John, how are you? J->>A: Great!
三. 箭头类型
交互方向包含如下类型:
| 类型 | 描述 |
|---|---|
-> | 无需箭头的坚实线条 |
--> | 无箭头的点缀线 |
->> | 带有箭头的坚实线条 |
-->> | 带箭头的点状线 |
<<->> | 带双向箭头的实线 (v11.0.0+) |
<<-->> | 带双向箭头的虚线 (v11.0.0+) |
-x | 末端有十字线 |
--x | 末端有十字线 |
-) | 末端有开箭头的实线 (async) |
--) | 末端有开箭头的点状线 (async) |
四. 交互内容
时序图中包含很多参与方的交互。其中我挑选了部分我的文章中会涉及到的部分,包括 激活、循环、判断、可选。
1. 激活状态
```mermaid
sequenceDiagram
Alice->>John: Hello John, how are you?
activate John
John-->>Alice: Great!
deactivate John
```
sequenceDiagram
Alice->>John: Hello John, how are you?
activate John
John-->>Alice: Great!
deactivate John
2. 笔记/提示
最简单的例子:
```mermaid
sequenceDiagram
participant John
Note right of John: 在john的右边的笔记
```
sequenceDiagram
participant John
Note right of John: 在john的右边的笔记
或者将笔记覆盖多个参与方:
```mermaid
sequenceDiagram
Alice->John: Hello John,<br/>最近咋样?
Note over Alice,John: 经典互动<br/>但是是两行
```
sequenceDiagram
Alice->John: Hello John,<br/>最近咋样?
Note over Alice,John: 经典互动<br/>但是是两行
3. 循环
如果时序中存在循环,可以通过 loop 表达式完成:
```mermaid
sequenceDiagram
Alice->John: 最近过的咋样?
loop 循环区域的名称
John-->Alice: 还可以!
end
```
sequenceDiagram
Alice->John: 最近过的咋样?
loop 循环区域的名称
John-->Alice: 还可以!
end
4. 判断/分支
如果交互中存在根据某个状态做不同的交互,主要语法如下:
```mermaid
sequenceDiagram
alt 判断描述文本
... statements ...
else
... statements ...
end
```
例如:
```mermaid
sequenceDiagram
小王->>小明: 小明, 最近咋样?
alt 病了
小明->>小王: 不太好 :(
else 状态不错
小明->>小王: 活力四射
end
```
sequenceDiagram
小王->>小明: 小明, 最近咋样?
alt 病了
小明->>小王: 不太好 :(
else 状态不错
小明->>小王: 活力四射
end
可选的交互:
```mermaid
sequenceDiagram
opt 可选交互区域的描述
... statements ...
end
```
```mermaid
sequenceDiagram
小王->>小明: 小明, 最近咋样?
alt 病了
小明->>小王: 不太好 :(
else 状态不错
小明->>小王: 活力四射
end
opt 额外的回复
小明->>小王: 谢谢你的关心
end
```
sequenceDiagram
小王->>小明: 小明, 最近咋样?
alt 病了
小明->>小王: 不太好 :(
else 状态不错
小明->>小王: 活力四射
end
opt 额外的回复
小明->>小王: 谢谢你的关心
end
5. 并行分支
如果某个参与方在一段同时发起两个交互,而不管另外的参与方是否返回的交互情形,则采用 par and 语句。
如下:
```mermaid
sequenceDiagram
par [动作1]
... statements ...
and [动作2]
... statements ...
and [动作N]
... statements ...
end
```
举个例子:
```mermaid
sequenceDiagram
par 跟小王打招呼
莉莉->>小王: 吊毛!
and 跟小李打招呼
莉莉->>小李: 吊毛!
end
小王-->>莉莉: 小莉来啦?!
小李-->>莉莉: 哟,这不小莉吗!
```
sequenceDiagram
par 跟小王打招呼
莉莉->>小王: 吊毛!
and 跟小李打招呼
莉莉->>小李: 吊毛!
end
小王-->>莉莉: 小莉来啦?!
小李-->>莉莉: 哟,这不小莉吗!
6. 关键交互区域
如果某个交互非常关键,希望通过圈出的区域的方式以达到着重提示的效果,则采用 critical option 语句。
```mermaid
sequenceDiagram
critical [Action that must be performed]
... statements ...
option [Circumstance A]
... statements ...
option [Circumstance B]
... statements ...
end
```
例子
```mermaid
sequenceDiagram
critical 与数据库建立连接
Service-->DB: 发起连接
option 网络连接超时
Service-->Service: 记录错误日志
option 认证异常
Service-->Service: 记录错误日志
end
```
渲染效果
sequenceDiagram
critical 与数据库建立连接
Service-->DB: 发起连接
option 网络连接超时
Service-->Service: 记录错误日志
option 认证异常
Service-->Service: 记录错误日志
end
五. 交互编号
可以通过给每个交互添加上标号,让复杂的交互更加具备可读性。
```mermaid
---
sequence:
showSequenceNumbers: true
---
sequenceDiagram
autonumber
Alice->>John: Hello John, how are you?
loop HealthCheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```
---
sequence:
showSequenceNumbers: true
---
sequenceDiagram
autonumber
Alice->>John: Hello John, how are you?
loop HealthCheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!