Mermaid 甘特图:语法、坑,以及与可视化编辑器的往返
Mermaid 的 gantt 代码块在 GitHub、GitLab、Notion 和 Obsidian 里都能原生渲染,这让它成为把进度计划放到工作现场最省事的方式。它同时也极其难改:动一个日期,下游每一条 after 链都得手工重新推算。本文讲语法、讲坑,也讲那个缺失的环节, , 可视化编辑之后把代码取回来。
一次过完语法
一个 gantt 代码块以 gantt 加几行头部声明开始,然后是 section 标题,标题下面是任务行。
一行任务是名称、一个冒号,然后是逗号分隔的字段:
Task name :tag, id, start, duration
- 标签, ,
done、active、crit、milestone中的任意一个,可省略。 - id, , 一个不带空格的词,只有当别处要引用这项任务时才需要。
- start, , 一个日期,或
after someId,或省略以接在上一项任务之后。 - duration, ,
5d、2w,或者第二个日期。
值得记住的头部声明:dateFormat(你的文件里日期是怎么写的)、excludes weekends(跳过周六和周日)、title,以及 axisFormat(坐标轴标签怎么显示,用的是 strftime 风格的占位符)。缩进只是书写习惯,不是语法的一部分。字段是按形状识别的,不严格看位置,所以 :done, res, 2026-01-05, 5d 和 :res, done, 2026-01-05, 5d 都能解析。
一段可以直接贴进 README 的例子
一个完整可用的代码块,描述一件真实的工作:把面向公众的接口从 v1 迁到 v2,同时上线小程序 2.0。它用到了 section、绝对日期、after 链、全部四个标签、一个里程碑,以及排除周末和清明假期。把它放进任意 GitHub Markdown 文件的 mermaid 代码围栏里就能渲染。
gantt
title 接口 v2 迁移与小程序 2.0
dateFormat YYYY-MM-DD
axisFormat %m-%d
excludes weekends
excludes 2026-04-04, 2026-04-05, 2026-04-06
section 调研
盘点 v1 接口 :done, audit, 2026-03-02, 5d
编写 OpenAPI 规范 :done, spec, after audit, 4d
规范评审 :active, review, after spec, 2d
section 开发
鉴权服务重构 :crit, auth, after review, 10d
资源类接口 :res, after auth, 12d
客户端 SDK 重新生成 :sdk, after res, 3d
section 切换
预发环境泡测 :soak, after sdk, 5d
公测开放 :milestone, beta, 2026-05-04, 0d
下线 v1 :dep, after beta, 2w
逐行读一遍:
dateFormat YYYY-MM-DD告诉 Mermaid 怎么读你打进去的日期。它管的是输入格式,不是输出, , 改它不会改变坐标轴。axisFormat %m-%d才是输出侧,坐标轴显示为"03-02"这样的形式。跨度超过一个季度时用%V显示周次更好读。excludes weekends让所有横条跳过周六和周日,作用于整张图, , 没有针对单个任务的开关。- 第二行
excludes排除了 2026 年清明的三天。但要注意:调休上班的那个周六,Mermaid 没有办法表达。国内的法定节假日通常伴随调休,而这个格式只能减不能加, , 想让某个周六算成工作日,只能改成绝对日期硬写,或者干脆把排期交给真正的工作日历去算。 盘点 v1 接口 :done, audit, 2026-03-02, 5d, , 标签、id、一个绝对开始日(周一)、五天。工期含首日,所以它在 3 月 6 日(周五)结束。after audit意思是"audit结束后开始", , 因为排除了周末,它落在 3 月 9 日周一,而不是 7 号周六。:crit, auth, ...把这条横条涂成关键路径的颜色。注意是"涂", , 原因见下面那一节。资源类接口 :res, after auth, 12d完全没有标签,第一个字段只是一个 id。没有标签就表示尚未开始的工作。公测开放 :milestone, beta, 2026-05-04, 0d是固定日期上的一个零长度标记。里程碑和别的任务一样可以有 id,所以最后一行的after beta是合法的。2w是两周。Mermaid 也接受h和m,在这里很少用得上。
section 之间的空行纯属装饰,Mermaid 会忽略,我们的导入器也忽略。但还是留着, , 一个四十行的代码块没有空行,在 diff 里根本没法看。
任务行字段速查
每个字段和修饰符的作用,以及它实际长什么样。
| 字段或修饰符 | 示例 | 作用 |
|---|---|---|
id | audit | 给任务起的一个名字,好让 after 能引用它。不能有空格和标点。除非有别的任务依赖它,否则可以不写。 |
after | after audit | 在指定任务结束后开始。只有完成-开始,没有延时。可以写多个 id, , after a b 等两者中较晚的那个。 |
done | :done, audit, … | 把横条画成已完成。没有百分比, , 100% 和"基本做完了"看起来一模一样。 |
active | :active, review, … | 把横条画成进行中,同样不带任何数字。 |
crit | :crit, auth, … | 把横条涂成关键色。它是你手打上去的一个断言,不是 Mermaid 推导出来的, , 没有任何东西拿它跟依赖关系核对。 |
milestone | :milestone, beta, … | 画成菱形而不是横条。和 0d 搭配使用。 |
| 工期单位 | 5d · 2w · 8h | 天、周、小时(还有 m)。含开始当天:周一起算的 5d 在周五结束。 |
| 结束日期 | 2026-03-02, 2026-03-06 | 用第二个日期代替工期,适用于结束日被外部固定的情况。 |
dateFormat | dateFormat YYYY-MM-DD | 文件里的日期按什么格式解析。头部声明,每张图写一次。 |
axisFormat | axisFormat %m-%d | 坐标轴标签怎么显示,用 strftime 占位符。纯粹是外观。 |
excludes | excludes weekends | 非工作日。也接受具体日期(excludes 2026-04-06)和星期名。作用于整张图。 |
四个一定会咬你的地方
1. 工期包含开始当天。从 5 号周一算起的 5d 到 9 号周五结束,不是 10 号。这里差一天,会把整份文件里所有任务都推移,而且照样渲染得完美无缺。
2. after 碰上 excludes weekends,才是真正出 bug 的地方。如果前置任务在周五结束,后继任务应当从周一开始,而不是周六。任何用"加一个自然日"来解析 after 的工具,都会在一份明令排除周末的文件里悄悄把任务排到周末。(我们的工具一度也是如此。现在有一条测试专门断言:任何推导出的日期都不会落在被排除的日子上。)
3. 没有转义机制。冒号开启字段列表,逗号分隔字段,所以一项名为 Phase 2: design, review 的任务会被解析成完全不同的东西。任务名里别用冒号和逗号。
4. 无法解析的工期会静默变成零。写成 3dd,你得到的是一条零长度的横条,而不是一个报错。批量改完之后,记得扫一遍有没有看不见的任务。
Mermaid 表达不了的东西
它是一种图示格式,不是排程引擎。你一旦想让图去回答问题、而不只是把一个答案画出来,差别立刻就显出来了:
- 没有资源。没有字段记录谁来干、花多少钱、投入多少工时。你没法在 Mermaid 里把一个人排超载,因为 Mermaid 根本不知道有"人"这回事。
- 没有浮动时间,也不会算关键路径。
crit只是你手工涂上去的一种颜色。没有任何东西去遍历依赖网络、计算最早最晚日期,或者告诉你哪条链在驱动完工日期。一张所有横条都标了crit的图,和一张一个都没标的图,同样合法。 - 没有基准。没有地方记录上个月的计划长什么样,所以既没有偏差可显示,也没有延误可度量。
- 只有完成-开始一种关系。
after就是无延时的 FS。开始-开始、完成-完成、开始-完成以及任何延时或提前量都无处安放。真实的计划里到处是"开发开始三天后开始测试",在 Mermaid 里这只能写成一个写死的日期,而那条关系就此消失了。 - 没有完成百分比。完成 40% 和完成 90% 的任务,都只是"active"。
- section 是平的。没有嵌套分组,所以超过一层的工作分解结构在导入时会被压平。
这些都不说明它是个糟糕的格式,只说明它是一种发布格式:适合把一份进度计划展示出来,不适合把它推算出来。这正是往返转换重要的原因。
所以往返转换会有损耗,但损耗方式是已知且乏味的。导出时,gantts.app 把 100% 映射为 done,中间的任何值映射为 active;导入时把 active 读回为 50%, , 这是一个猜测,工具会明说,而不是让你在某份状态报告里才发现。写不成 after 的依赖关系会退化为绝对日期,这些日期依然正确,只是不再便于维护。
有一处刻意的不对称,而且它不是 bug:crit 会被导出,但从不被导入。往外写的时候它是推导出来的, , 编辑器已经从依赖关系网络算出了关键路径,所以写下去的那个标签当时是真的。往里读的时候,它只是某个人在一份可能已经过期好几周的文件里打下的一个词,相信它就等于允许一张旧图把一条并不关键的链条涂成红色。所以它被写出、然后被无视:你在导入之后看到的关键路径,是重新算过的,不是被声明出来的。
顺带说明我们这边关键路径的口径:它是"按当前位置"计算的, , 一条依赖关系只能把任务往后推,永远不会把它往前拉;工具算的是总浮动时间,不算自由浮动时间。所以一份从 Mermaid 导进来、日期被作者手工排得很松的计划,导入后不会被自动压紧,关键路径反映的是这些横条实际所在的位置。
可视化编辑,再把代码粘回去
能渲染 Mermaid 的工具不少。一直缺的是反方向, , 拖动横条,然后把语法拿回来。
- 在 gantts.app 里粘贴或打开你的图, ,
.mmd文件,或含有代码围栏的.md文件,都可以。它按内容识别 gantt 代码块,而不是看扩展名。 - 像编辑任何图表一样拖动、连线、改日期。
excludes weekends会把工作日历打开,因此产出的日期与它来源的那份文件一致。 - 点⬇ 导出,选 🧜 Mermaid 甘特图(文本),点复制到剪贴板或下载 .mmd,粘回你的 README,提交这个 diff。
- 提交前先勾一下关键路径看一眼:导出的
crit标签用的就是这一次计算的结果,所以你在图上看到什么,README 里写的就是什么。
对用大模型起草进度计划的人还有一个不错的副作用:让它输出 Mermaid gantt 语法,粘进来,你就得到一张真正可编辑、还带关键路径的图表, , 不需要 API 密钥,也不需要后端。
常见问题
Mermaid 里怎么写甘特图?
代码块以 gantt 开头,加上 dateFormat YYYY-MM-DD,然后写 section 标题,标题下按"名称 :tag, id, start, duration"的形式写任务行,例如 "Research :done, res, 2026-01-05, 5d"。
Mermaid 里的 5d 包含开始当天吗?
包含。从 5 号周一开始的 5d 任务在 9 号周五结束。这种含首日的计数方式是最常见的差一天错误来源。
Mermaid 甘特图里依赖关系怎么写?
在 start 字段写 "after someId"。它永远是无延时的完成, 开始关系,开始, 开始、完成, 完成以及延时都无法表达。
Mermaid 能显示完成百分比吗?
不能,它只有 done 和 active 两个标签。把 active 读成某个具体百分比只能靠猜;gantts.app 取 50%,并且会明确告知。
Mermaid 甘特图在哪里能渲染?
GitHub、GitLab、Notion 和 Obsidian 都能在 Markdown 里原生渲染,mermaid.live 也可以在浏览器里渲染。
可以把 Mermaid 甘特图变成可编辑的图表吗?
可以。在 gantts.app 里打开 .mmd 或 Markdown 文件,可视化编辑之后用"导出 ▸ Mermaid 甘特图"把更新后的语法复制回去。
相关模板
延伸阅读
本文也提供英文版本。