用代码画流程图/时序图

介绍 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 的表现力让我感到十分惊艳。

直到现在,我仍然对在职期间接触到这些内容心存感激。特别是我在后来撰写自己的文档时,我逐渐理解了一份好的文档,对于理解一个复杂的系统和上下文作用有多重要。特别是面对一个复杂系统的情况下。

本文写作动机是为补充现有的文档文字过多和未来编写新的文档而准备的笔记。因此在本文发布后,根据后期需要,可能后期会对内容做一些额外的补充。

说明

具备以下知识储备,才能流畅阅读本文内容:

  1. 具备 Markdown 基础知识
  2. 了解 YAML 语法
  3. 理解图形图像信息对人类信息摄入的积极作用

参考资料

目录

基本语法

此部分介绍一个 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 有两类配置:全局配置图例自定义配置

全局配置

在你的项目脚手架中,或者脚手架插件中,亦或者直接通过 npmmermaid 直接使用的。

不管通过什么方式初始化的,通过初始化时传入的配置,都称为全局配置,所有的图例都会用到全局的配置,当然也可以在图例中自定义属于图例自身的配置。

此部分本文不会涉及全局配置部分,因为这是另外一个话题,跟本文主题不一致。

图例配置

第二类是图例代码块中,用于超过全局配置优先级的配置,换句话说,如果图例提供了自身配置,那么将优先使用图例自身的,而不是全局的。

例如我需要为一个流程图独立配置一个标题,并且绘制方式采用手绘风格:

```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 的简介部分内容

到本文发布时,包含以下图例:

本文不会全部举例,而是只举例 流程图时序图,因为在博客中用得比较多。

流程图

正如基本语法中的案例一样,流程图类型以 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]

其中,中括号前面 Lm,则表示节点的唯一 标识/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

一. 交互方类型

交互方类型只有两种:actorparticipant。但是还有个不知道从什么版本引入的特性,通过 @ 语法可以为普通参与方类型制定不一样的图标。

例如,我想指定一个普通参与方类型为数据库的图标,@ 语法后面为一个 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!