☰
diagram-design:将图表作为代码管理的工程化实践指南
2026/10/17 4:14:59 网站建设 项目流程

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题

第一次看到 diagram-design 这个词,很多人会以为它只是“画图”的另一种说法。但真正在项目里被图表折磨过的人知道,画一张图不难,难的是让图能持续用、能协作、能改、能交付。diagram-design 的核心不是“画”,而是“设计一套图表的生成与维护机制”。

我最早接触这个概念是在一个内部工具重构项目里。当时团队有十几张架构图、流程图、状态机图散落在不同人的电脑里,格式有 PNG、有 Sketch 文件、有 PPT 截图,甚至还有手绘拍照。每次系统改一个模块,就要手动找到对应的图去改,改完还要重新导出、重新发群、重新同步给文档。一个季度下来,光是维护这些图就消耗了将近一个人周的工作量。diagram-design 要解决的,就是这种“图与人、图与代码、图与文档”之间的脱节问题。

它的核心思路可以概括成一句话:把图表当作代码来管理,把设计当作系统来构建。具体来说,就是用文本描述图表结构,用版本控制管理变更,用渲染管线输出多种格式,用设计规范约束视觉表达。这样做的直接好处是,图表可以像代码一样被 review、被 diff、被自动化测试,也可以像设计资产一样被复用、被主题化、被批量更新。

适合参考这套方法的人其实比想象中多。如果你是后端开发者,需要维护系统架构图;如果你是技术文档工程师,需要保证文档里的图与代码同步;如果你是产品经理,需要快速产出流程图和状态图;如果你是设计师,需要建立一套可复用的图表视觉规范——diagram-design 都能给你一套可落地的工作流。它不要求你成为绘图专家,但要求你愿意把“画图”这件事从手工劳动升级成工程实践。

我见过太多团队在图表上踩坑:有人用在线工具画了几十张图,结果服务一停全部丢失;有人把图嵌在文档里,改一个颜色要手动改二十处;有人用代码生成图,但生成出来的图丑到没人愿意看。diagram-design 的价值就在于,它同时兼顾了“可维护性”和“可交付性”,让图既能被机器处理,也能被人阅读。

2. 核心思路拆解:为什么要把图表当代码管

2.1 文本描述与图形渲染的分离

diagram-design 最底层的设计决策,是把“图的内容”和“图的样式”彻底分开。内容用文本描述,样式用主题配置,渲染引擎负责把两者合成最终图形。这个思路借鉴了前端开发里“结构、样式、行为”分离的原则,只不过这里的行为变成了渲染管线的触发逻辑。

为什么一定要分离?因为不分离的代价太大了。我试过用传统绘图工具维护一套二十张的系统图,每次品牌色调整,就要打开每一张图,逐个修改线条颜色、填充色、字体。二十张图改完,半天没了,还容易漏改。分离之后,我只需要改一个主题配置文件里的色值,重新跑一次渲染命令,所有图自动更新。这个效率差异不是百分之几十,而是几十倍。

文本描述通常用 DSL(领域特定语言)来实现。常见的 DSL 有 Mermaid、PlantUML、Graphviz DOT、D2 等。选哪个 DSL,取决于你的图表类型和团队习惯。Mermaid 适合流程图、时序图、甘特图,语法接近自然语言,学习成本低;PlantUML 支持的图类型更全,包括类图、组件图、部署图,但语法相对严格;Graphviz DOT 更适合复杂的网络拓扑和有向图,布局算法强但样式控制偏弱;D2 是较新的选择,语法简洁,布局和样式分离做得比较彻底。

我个人的经验是,如果团队里非技术角色多,优先选 Mermaid,因为它最容易看懂和修改;如果是纯技术团队维护架构图,PlantUML 或 D2 更合适,因为能表达更复杂的结构关系。关键不是选“最好的”,而是选“团队里最多人能改的”。

2.2 版本控制与变更追溯

把图表文本化之后,一个自然的好处就是可以放进 Git 仓库。这意味着每一次图表变更都有 commit 记录,谁改的、改了什么、为什么改,全部可追溯。这在系统架构演进过程中特别有价值。

我经历过一次线上故障复盘,需要确认某个服务是什么时候被加入架构图的。如果图是二进制文件,根本没法 diff,只能靠记忆和聊天记录。但因为我们的图是文本 DSL,直接 git log 一查,发现是三个月前一次重构时加的,commit message 里还写了原因。这种可追溯性在传统绘图工具里几乎不可能实现。

版本控制还带来了分支和合并的能力。比如要设计一个新架构方案,可以开一个分支改图,review 通过后再合并到主分支。这和代码开发流程完全一致,团队不需要额外学习一套协作机制。

2.3 自动化渲染与多格式输出

文本描述的图需要经过渲染才能变成可阅读的图形。diagram-design 的渲染管线通常包含几个环节:解析 DSL、应用主题、布局计算、输出目标格式。目标格式可以是 SVG、PNG、PDF,甚至直接嵌入 HTML 页面。

自动化渲染的关键在于“可重复”。同样的输入,无论什么时候跑,都应该得到同样的输出。这就要求渲染环境是确定的,依赖版本是锁定的。我见过有人本地渲染出来的图和 CI 里渲染出来的图不一致,排查半天发现是字体缺失导致布局偏移。所以渲染环境最好容器化,把字体、渲染引擎版本、主题文件全部固化进去。

多格式输出的意义在于适配不同场景。SVG 适合网页和文档,矢量缩放不糊;PNG 适合聊天工具和 PPT 嵌入;PDF 适合打印和正式交付。一套源文件,多种输出,不需要维护多个版本。

2.4 设计规范与视觉一致性

diagram-design 里的“design”不只是“画出来”,还包括“画得一致”。一套好的图表设计规范应该定义:颜色语义(比如蓝色代表计算、绿色代表存储、红色代表告警)、形状语义(圆角矩形代表服务、圆柱代表数据库、菱形代表判断)、字体层级(标题、标签、注释的字号和字重)、间距规则(节点间距、层级间距、画布边距)。

这些规范如果只写在文档里,没人会遵守。必须把它们编码进主题配置里,让渲染引擎自动应用。这样即使是不懂设计的人来改图,只要用对了语义标签,出来的图也是符合规范的。

我踩过的一个坑是,早期没有定义颜色语义,结果不同人画的图里,同一个服务有时是蓝色有时是绿色,读者以为代表不同含义。后来我们把颜色语义写进主题文件,并且在 CI 里加了一个检查:如果 DSL 里用了未定义的颜色变量,渲染直接失败。这样从机制上保证了视觉一致性。

3. 核心细节解析:DSL 选型、主题设计与渲染管线

3.1 主流 DSL 的对比与选型建议

选 DSL 是 diagram-design 的第一步,也是最容易纠结的一步。我把常见的几种放在一起对比,方便你根据场景做决定。

DSL适合图表类型学习成本样式控制布局能力生态成熟度
Mermaid流程图、时序图、状态图、甘特图低中等中等高
PlantUML类图、组件图、部署图、时序图中中等中等高
Graphviz DOT有向图、网络拓扑、依赖图中高低强高
D2架构图、流程图、ER 图低中高强中

选型的核心考量不是“哪个最强”,而是“哪个最匹配你的图表类型和团队能力”。如果你的图主要是业务流程和交互时序,Mermaid 足够用,而且很多文档平台原生支持。如果你要画复杂的系统部署和组件依赖,PlantUML 或 D2 更合适。如果图的结构非常复杂、节点上百,Graphviz 的布局算法会更稳。

还有一个容易被忽略的点:DSL 的可读性。图是给人看的,源文件也是给人改的。如果 DSL 写出来像天书,维护成本会很高。我建议在选型时让团队里最不熟悉技术的人试着读一段 DSL,如果他能大致猜出图的结构,这个 DSL 就选对了。

3.2 主题配置的结构与参数设计

主题配置是 diagram-design 里最体现“设计”的部分。一个完整的主题通常包含以下几类参数:

  • 颜色系统:主色、辅助色、语义色、背景色、边框色、文字色。语义色要按用途定义,比如color-compute、color-storage、color-network、color-alert。
  • 形状系统:不同节点类型的默认形状、圆角半径、边框粗细、阴影参数。
  • 字体系统:字体族、字号层级、字重、行高。字号层级通常分三级:图标题、节点标签、边标签。
  • 间距系统:节点水平间距、垂直间距、层级间距、画布内边距。
  • 连线系统:线宽、线型(实线、虚线、点线)、箭头样式、标签背景。

这些参数不是随便定的,背后有可读性依据。比如节点间距,太近会显得拥挤,太远会浪费空间。我的经验值是:水平间距至少是节点平均宽度的 1.5 倍,垂直间距至少是节点平均高度的 2 倍。字号层级方面,节点标签字号不应小于 12px(在 1x 缩放下),否则投影或打印时看不清。

主题配置最好用变量引用而不是硬编码。比如定义primary-color: #2B6CB0,然后在节点样式里引用fill: primary-color。这样改一处就能全局生效。如果直接写色值,改起来就是灾难。

3.3 渲染管线的搭建与自动化

渲染管线的基本流程是:读取 DSL 文件 → 解析成图结构 → 应用主题配置 → 计算布局 → 输出目标格式。手动跑这个流程很简单,但要做到自动化,需要把它集成到 CI/CD 或文档构建流程里。

我推荐的做法是用一个 Makefile 或 npm script 来编排渲染任务。比如:

# 渲染所有图表为 SVG make render-svg # 渲染指定图表为 PNG make render-png FILE=architecture.d2 # 监听文件变化并自动重新渲染 make watch

在 CI 里,可以加一个检查步骤:如果 DSL 文件有变更但渲染产物没有更新,就报错。这样可以防止有人改了源文件但忘记重新渲染。

渲染环境的一致性很重要。我建议用 Docker 镜像来固化渲染工具链,把 DSL 编译器、字体、主题文件全部打包进去。这样无论本地还是 CI,渲染结果都一样。字体尤其关键,很多布局偏移问题都是字体缺失或版本不一致导致的。

3.4 图表语义与视觉映射的设计原则

diagram-design 里有一个容易被忽视但极其重要的环节:语义映射。也就是说,图里的每个视觉元素都应该有明确的含义,不能随意使用。

比如颜色,如果蓝色代表计算服务,那所有计算服务都应该是蓝色,不能有的蓝有的绿。如果虚线代表异步调用,那所有异步调用都应该是虚线,不能混用。这种一致性读者可能不会刻意注意,但一旦破坏,就会造成理解混乱。

我建议在项目初期就建立一份“视觉语义表”,明确每个颜色、形状、线型的含义。这份表不需要很复杂,一张表格就够:

视觉元素语义使用场景
蓝色圆角矩形计算服务应用服务、函数、容器
绿色圆柱数据存储数据库、缓存、对象存储
橙色菱形判断节点条件分支、网关
红色虚线告警链路监控、告警、异常流
灰色实线同步调用请求-响应链路

有了这份表,任何人改图时都有据可依,review 时也有标准可查。

4. 实操过程:从零搭建一套图表工程

4.1 项目目录结构与初始化

一个可维护的图表工程,目录结构应该清晰反映“源文件、主题、产物、脚本”的分离。我常用的结构是这样的:

diagram-design/ ├── src/ # DSL 源文件 │ ├── architecture/ │ │ ├── overview.d2 │ │ └──>title: 订单系统架构 user: 用户 { shape: person } gateway: API 网关 { shape: hexagon style.fill: "#2B6CB0" } order-service: 订单服务 { shape: rectangle style.fill: "#2B6CB0" } payment-service: 支付服务 { shape: rectangle style.fill: "#2B6CB0" } order-db: 订单数据库 { shape: cylinder style.fill: "#38A169" } payment-db: 支付数据库 { shape: cylinder style.fill: "#38A169" } user -> gateway: HTTPS gateway -> order-service: 创建订单 gateway -> payment-service: 发起支付 order-service -> order-db: 读写 payment-service -> payment-db: 读写 order-service -> payment-service: 调用支付

这段 DSL 有几个设计决策值得说明。第一,节点名称用英文标识符,显示标签用中文,这样源文件可读性好,渲染出来也符合中文读者习惯。第二,颜色直接写在节点样式里,适合快速原型;正式项目应该引用主题变量。第三,连线标签明确写了动作,避免读者猜测。

写 DSL 的一个经验是:先画结构,再调样式。不要一开始就纠结颜色和间距,先把节点和连线关系理清楚,渲染出来看看逻辑对不对,再逐步加样式。这样迭代效率最高。

4.3 主题文件的编写与变量引用

主题文件通常用 JSON 或 YAML 格式。以下是一个简化版的主题配置:

{ "colors": { "primary": "#2B6CB0", "success": "#38A169", "warning": "#D69E2E", "danger": "#E53E3E", "background": "#FFFFFF", "text": "#1A202C", "border": "#CBD5E0" }, "fonts": { "family": "Inter, Noto Sans SC, sans-serif", "titleSize": 20, "labelSize": 14, "noteSize": 12 }, "spacing": { "nodeHorizontal": 80, "nodeVertical": 100, "canvasPadding": 40 }, "shapes": { "service": { "fill": "primary", "borderRadius": 8 }, "database": { "fill": "success", "borderRadius": 0 }, "gateway": { "fill": "warning", "borderRadius": 16 } } }

在 DSL 里引用主题变量的方式取决于工具。D2 支持通过vars或外部配置文件注入,Mermaid 通过%%{init}%%指令配置主题。关键是要保证 DSL 里不出现硬编码的色值和字号,全部走主题变量。

我踩过的一个坑是:主题文件里的字体族写了Inter,但渲染环境里没装这个字体,结果回退到默认字体,布局全变了。后来我在 Docker 镜像里预装了所有用到的字体,并且在主题里加了 fallback 字体族,问题才解决。

4.4 渲染脚本与自动化集成

渲染脚本的核心是把“编译 DSL”和“应用主题”两个步骤串起来。以下是一个 bash 脚本示例:

#!/bin/bash set -e THEME=${THEME:-default} SRC_DIR=src DIST_DIR=dist mkdir -p "$DIST_DIR/svg" "$DIST_DIR/png" for file in $(find "$SRC_DIR" -name "*.d2"); do name=$(basename "$file" .d2) echo "Rendering $name..." d2 --theme "$THEME" --layout dagre "$file" "$DIST_DIR/svg/$name.svg" d2 --theme "$THEME" --layout dagre --format png "$file" "$DIST_DIR/png/$name.png" done echo "Done."

这个脚本做了几件事:遍历源文件、用指定主题渲染、同时输出 SVG 和 PNG、失败时立即退出。set -e很重要,避免某个文件渲染失败后继续跑,导致产物不完整。

集成到 CI 的方式有很多种。我常用的是在 GitLab CI 或 GitHub Actions 里加一个 job,当src/或themes/目录有变更时触发渲染,并把产物提交回仓库或发布到文档站点。如果团队用静态站点生成器,也可以把渲染步骤集成到构建流程里。

注意:渲染产物是否提交到仓库,团队里要有明确约定。提交的好处是 review 时能直接看到图的变化,坏处是仓库体积会变大。我的建议是 SVG 提交,PNG 不提交,因为 SVG 是文本格式,diff 友好,PNG 是二进制,提交了也没法 review。

5. 常见问题与排查技巧实录

5.1 渲染结果与预期不符的排查思路

渲染出来的图和预期不一样,是最常见的问题。排查时按以下顺序检查,能覆盖九成以上的情况:

  1. DSL 语法是否正确:很多渲染工具在语法错误时不会报错,而是静默输出一个空图或残缺图。先用工具的 lint 功能检查语法。
  2. 主题变量是否生效:如果颜色或字体没变,检查主题文件路径是否正确、变量名是否拼错、DSL 里是否硬编码覆盖了主题。
  3. 字体是否安装:字体缺失会导致布局偏移、文字截断。用fc-list检查渲染环境里的字体列表。
  4. 布局引擎是否匹配:不同布局引擎(dagre、elk、dot)对同一张图的排布差异很大。如果布局很乱,换一个引擎试试。
  5. 画布尺寸是否足够:节点太多时,默认画布可能装不下,导致节点重叠。调整画布尺寸或间距参数。

我遇到过一次特别隐蔽的问题:图在本地渲染正常,在 CI 里渲染出来节点全挤在一起。排查了半天,发现是 CI 环境里没有装中文字体,中文标签渲染成了方块,宽度计算错误,导致布局塌陷。后来在 CI 镜像里装了 Noto Sans SC,问题解决。

5.2 图表可读性差的优化手段

图能渲染出来,不代表能读。可读性差通常表现为:节点太密、连线交叉太多、标签看不清、层级不分明。对应的优化手段如下:

问题优化手段
节点太密增大节点间距,拆分成多张子图
连线交叉多调整节点顺序,使用分层布局,减少跨层连线
标签看不清增大字号,给标签加背景色,缩短标签文字
层级不分明用颜色或形状区分层级,加分组框
图太大拆分为概览图和细节图,用链接关联

我的经验是,一张图里的节点数量最好控制在 15 到 20 个以内。超过这个数量,读者就很难在脑子里建立整体印象。如果系统确实复杂,就拆成多张图,用一张总览图加若干细节图的方式组织。

还有一个技巧是“视觉锚点”:在图的左上角或中心位置放一个最核心的节点,用稍大的尺寸或更深的颜色突出它,让读者的视线有落脚点。这样即使图比较复杂,读者也能快速抓住重点。

5.3 团队协作中的版本冲突与合并策略

多人同时改图时,Git 冲突几乎不可避免。DSL 文件是文本,冲突可以手动解决,但如果不了解 DSL 结构,解决起来会很痛苦。

减少冲突的策略有几个。第一,按图拆分文件,不要把所有图写在一个大文件里。这样不同人改不同的图,冲突概率大大降低。第二,约定节点命名规范,避免两个人用不同的名字表示同一个节点。第三,改图前先 pull,改完尽快 push,减少并行修改的时间窗口。

如果冲突已经发生,解决时要注意:DSL 的冲突通常出现在节点定义和连线定义两部分。节点定义的冲突一般是属性不同,比如一个人改了颜色,另一个人改了形状,这种可以合并。连线定义的冲突一般是关系不同,比如一个人加了连线,另一个人删了连线,这种需要人工判断哪个是最新意图。

提示:可以在 CI 里加一个检查,如果 DSL 文件有冲突标记(<<<<<<<),直接让构建失败。这样能防止带冲突标记的文件被合并。

5.4 渲染性能与大规模图表的处理

当图表数量多或单张图节点多时,渲染会变慢。我遇到过一张有 200 多个节点的图,渲染一次要十几秒,改一下等半天,体验很差。

优化渲染性能的手段包括:增量渲染(只渲染变更的文件)、并行渲染(多个文件同时渲染)、缓存布局结果(如果结构没变,只改样式时复用布局)。D2 和 Mermaid 都支持一定程度的增量编译,具体用法看工具文档。

如果单张图实在太大,渲染慢且难读,就应该考虑拆图。拆图的依据可以是业务域、调用链路、部署单元等。拆完之后用一张总览图串联,总览图里每个节点链接到细节图。这样既保证了可读性,也提升了渲染速度。

还有一个容易被忽略的点:渲染产物的体积。SVG 里如果嵌入了大量字体或冗余路径,文件会很大,网页加载慢。可以用 SVGO 之类的工具压缩 SVG,去掉无用元数据,通常能减小 30% 到 50% 的体积。

6. 从工具到习惯:让图表工程持续运转

6.1 建立图表 review 机制

图表要持续可用,必须像代码一样被 review。我建议在 PR 流程里加一条:如果变更涉及系统结构或流程,必须同时更新对应的图表,并且图表变更要有人 review。

review 图表时重点看几件事:语义是否正确(颜色、形状是否按规范使用)、结构是否清晰(层级、分组是否合理)、信息是否完整(关键节点和连线是否遗漏)、可读性是否达标(字号、间距、连线交叉)。这些检查项可以做成 checklist,review 时逐条对照。

如果团队用 GitLab 或 GitHub,可以配置一个 bot,当src/目录有变更时自动在 PR 里评论,提醒 reviewer 查看渲染后的图。有些工具还能直接生成图的前后对比,方便 review 时看变化。

6.2 图表与文档的同步策略

图表和文档脱节是另一个常见问题。文档里引用的图还是旧版本,读者看到的信息就是错的。解决这个问题的关键是“单一数据源”:图表的源文件只有一份,文档通过引用或嵌入的方式使用渲染产物,而不是复制粘贴。

具体做法有两种。一种是文档构建时动态渲染图表,把 SVG 直接嵌入 HTML。另一种是图表渲染后发布到固定 URL,文档里用链接或图片引用。两种方式都能保证文档里的图始终是最新的。

如果文档平台支持,最好在图表旁边加一个“查看源文件”的链接,读者如果对细节感兴趣,可以直接跳到 DSL 源文件。这样既保证了文档的简洁,又提供了深入查看的入口。

6.3 图表资产的复用与扩展

一套图表工程建起来之后,很多资产是可以复用的。比如主题配置可以在多个项目间共享,通用节点定义(如“用户”“网关”“数据库”)可以抽成公共库,渲染脚本可以做成模板。

扩展的方向也很多。比如加一个“暗色主题”,适配暗色模式的文档站点;加一个“打印主题”,优化 PDF 输出时的对比度和字号;加一个“演示主题”,放大字号和间距,适合投屏展示。

我个人的体会是,图表工程的价值会随着时间推移越来越大。刚开始可能只是几张图,但半年后可能变成几十张,覆盖系统架构、业务流程、状态机、数据模型等多个维度。这时候如果没有一套工程化的管理方式,维护成本会指数级上升。而一旦建立了这套机制,后续每加一张图的边际成本会越来越低。

最后分享一个小技巧:在项目 README 里放一张“图表索引图”,用一张总览图展示所有图表的组织和关系,每个节点链接到对应的源文件和渲染产物。这样新加入的人能快速了解整个图表体系,也方便自己找到需要的图。这个索引图本身也可以用 diagram-design 的方式生成,形成自举。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询