首页 / 资讯中心 / 文章详情

diagram-design:用代码化设计系统打造专业图表

diagram-design:用代码化设计系统打造专业图表 ★ FEATURED ARTICLE
1. 从一张草图到一套系统diagram-design 到底在解决什么问题第一次看到 diagram-design 这个词很多人会以为它只是“画图”的另一种说法。但真正在项目里被图表折磨过的人知道画图本身从来不是难点难的是让图表具备设计感、可维护性和表达力。diagram-design 这个项目标题背后指向的是一套完整的图表设计方法论与工具链实践——它要解决的核心问题是如何让技术图表、流程示意、架构图、数据关系图不再停留在“能看”的层面而是达到“好看、好改、好复用”的专业水准。我在过去几年里参与过不少需要大量出图的项目从系统架构梳理到业务流程建模从数据管道可视化到产品原型中的信息图设计。最开始大家的做法都很朴素打开某个绘图工具拖几个框连几条线导出 PNG 贴进文档。结果就是每次需求一变图就得重画不同人画的图风格不统一图里的文字和配色跟整体设计语言完全脱节。diagram-design 要做的就是把这种“手工作坊式”的出图方式升级成一套有规范、有工具、有流程的工程化实践。这个内容适合谁如果你是技术文档写作者、产品经理、系统架构师、独立开发者或者任何需要频繁输出图表的人diagram-design 的思路和工具组合都能直接拿来用。它不要求你是设计师出身但会让你理解为什么有些图看起来就是“专业”而有些图总差一口气。接下来的内容我会从整体设计思路、核心工具选型、实操流程、常见问题四个维度展开把我在实际项目中踩过的坑和总结的技巧都摊开来讲。2. 整体设计思路与方案选型为什么不用传统绘图工具2.1 传统绘图工具的三大痛点在讲 diagram-design 的具体方案之前先说说为什么很多人最终放弃了传统绘图工具。我用过市面上绝大多数主流绘图软件它们的问题集中在三个地方。第一是版本管理灾难。传统绘图工具产出的文件格式往往是二进制或私有格式Git 无法有效追踪变化。你改了图里的一个标签提交到版本库后diff 显示的就是“文件已更改”根本看不出改了什么。团队协作时两个人同时改一张图合并冲突几乎无法解决。第二是样式复用困难。你精心调好了一套配色和字体想应用到下一张图发现只能手动复制样式或者依赖工具本身的“主题”功能——而主题功能往往限制多多改一个全局颜色可能把不该变的地方也变了。第三是与开发流程脱节。技术项目的文档、代码、图表本应是一体的但传统工具产出的图片是“死”的无法嵌入 CI/CD 流程无法自动生成无法根据数据动态更新。每次数据变了图就得手动重画。2.2 diagram-design 的核心思路图表即代码diagram-design 的核心思路可以用一句话概括把图表当作代码来管理和设计。这意味着图表的定义、样式、布局都以文本形式存在可以被版本控制、被代码生成、被自动化流程处理。这个思路并不是凭空来的。在软件工程领域已经有成熟的“Diagrams as Code”实践比如用特定 DSL 描述架构图、用 Graphviz 定义关系图。但 diagram-design 更进一步它不只关注“画出来”还关注“设计好”。也就是说它把设计系统的那套理念——色彩规范、字体层级、间距规则、组件复用——引入到图表领域。为什么这个思路更优因为一旦图表变成文本你就获得了几个关键能力可以用 Git 管理每一次修改可以用变量和模板批量生成风格一致的图可以把图表生成嵌入构建流程数据一变图就自动更新还可以让不同角色的人用自己熟悉的工具参与进来——设计师调样式工程师写逻辑文档作者填内容。2.3 工具选型为什么是这套组合在实际落地 diagram-design 时工具选型决定了你能不能真正把“图表即代码”跑通。我试过多种组合最终稳定下来的方案是Mermaid 负责快速表达逻辑关系D2 负责复杂布局和精细样式SVG 作为最终输出格式配合设计令牌Design Tokens统一视觉语言。Mermaid 的优势在于上手极快语法接近自然语言适合流程图、时序图、状态图这类逻辑清晰的图表。它的缺点是样式定制能力有限复杂布局容易失控。所以我把 Mermaid 定位为“草稿和逻辑验证工具”——先用它把关系理清楚确认无误后再进入精细设计阶段。D2 是我近两年重点使用的工具。它的语法比 Mermaid 更灵活支持容器、嵌套、样式覆盖、主题继承而且布局引擎可以选择 Dagre 或 ELK对复杂架构图的支持好很多。D2 还支持用变量和类来复用样式这正好契合 diagram-design 的设计系统理念。SVG 作为输出格式是因为它矢量、可编辑、可嵌入网页、可被 CSS 控制。最终产出的图表不是一张死图而是一个可以被进一步加工的设计资产。设计令牌是一组预定义的变量比如--color-primary、--font-heading、--spacing-md。把这些令牌注入到图表生成流程中就能保证所有图表跟产品的整体设计语言一致。这个做法借鉴了前端设计系统的思路但在图表领域同样有效。注意工具选型没有绝对标准。如果你的团队已经在用某套工具链优先考虑如何把 diagram-design 的理念嵌入现有流程而不是推翻重来。工具是手段一致性和可维护性才是目的。3. 核心细节解析与实操要点从语法到样式的完整拆解3.1 图表结构设计先想清楚“关系”再动手很多人画图的第一步是打开工具然后一边拖框一边想布局。这是效率最低的做法。diagram-design 要求你先用文本描述清楚图表要表达的关系再考虑视觉呈现。以一张典型的系统架构图为例。你需要先回答几个问题系统有哪些组件组件之间的调用关系是什么哪些是同步调用哪些是异步消息数据流向是单向还是双向有没有外部依赖这些问题的答案决定了图表的逻辑结构而逻辑结构决定了后续的布局和样式选择。我习惯先用 Mermaid 写一版最简描述graph LR A[客户端] -- B[API 网关] B -- C[用户服务] B -- D[订单服务] C -- E[(用户数据库)] D -- F[(订单数据库)] D -- G[消息队列] G -- H[通知服务]这一版不需要任何样式只验证逻辑是否正确。确认无误后再把它翻译成 D2 语法加入容器、样式和布局控制。3.2 样式系统设计让每张图都说同一种视觉语言diagram-design 最核心的价值体现在样式系统上。没有样式系统的图表每张图都是孤立的有了样式系统所有图表就像同一个产品里的不同页面视觉上高度统一。样式系统的第一层是色彩。我通常定义一组语义化的颜色变量主色用于核心组件辅助色用于支撑组件警告色用于异常路径中性色用于背景和边框。这些颜色不直接写死而是通过设计令牌注入。第二层是字体层级。图表里的文字分几个层级图表标题、模块标题、组件名称、注释说明。每个层级对应不同的字号、字重和颜色。D2 支持通过classes来批量应用这些样式。第三层是间距和布局规则。组件之间的水平间距、垂直间距、容器内边距都应该遵循同一套间距系统。D2 的布局引擎会自动处理大部分间距但你可以通过grid-columns、grid-rows等参数进行微调。第四层是形状语义。圆形代表什么矩形代表什么菱形代表什么圆角矩形代表什么——这些约定一旦确定就要在所有图表中保持一致。比如我用圆角矩形表示服务用圆柱体表示数据库用平行四边形表示消息队列用虚线框表示外部系统。3.3 实操要点D2 样式配置的常见陷阱D2 的样式系统很强大但有几个地方容易踩坑。第一个坑是样式继承的优先级。D2 允许在全局、容器、节点三个层级定义样式优先级从低到高。如果你在全局定义了style.fill: #f0f0f0在某个容器里又定义了不同的填充色容器内的节点会继承容器的样式而不是全局的。这个行为跟 CSS 的继承规则类似但很多人第一次用会困惑。第二个坑是类名的作用域。D2 的classes可以定义一组样式然后应用到多个节点。但类名的作用域是全局的如果你在多个图表文件中使用了同一个类名但定义了不同的样式合并时会产生冲突。我的做法是给类名加前缀比如arch-service、flow-decision避免命名冲突。第三个坑是布局引擎的选择。D2 默认使用 Dagre 布局适合大多数有向图。但如果你的图表层级很深或者有很多交叉连线Dagre 的布局可能会很乱。这时候可以切换到 ELK 布局引擎它对复杂层次结构的处理更好。切换方式是在 D2 文件开头加上direction: right和layout-engine: elk。提示D2 的样式调试建议在本地用d2 --watch模式实时预览。每次保存文件浏览器里的图表会自动刷新比反复导出图片效率高得多。3.4 设计令牌的注入方式设计令牌要真正发挥作用必须能自动注入到图表生成流程中。我的做法是维护一个tokens.json文件里面定义所有颜色、字体、间距变量。然后在生成图表前用一个简单的脚本把令牌转换成 D2 的样式定义拼接到 D2 文件头部。import json with open(tokens.json) as f: tokens json.load(f) style_header f vars: {{ d2-config: {{ theme-id: 0 }} }} classes: {{ primary-service: {{ style: {{ fill: {tokens[color][primary-light]} stroke: {tokens[color][primary]} font-color: {tokens[color][text-on-light]} border-radius: 8 }} }} }} with open(diagram.d2) as f: content f.read() with open(diagram-with-tokens.d2, w) as f: f.write(style_header content)这个脚本很粗糙但思路是清晰的令牌是唯一真相来源图表样式从令牌派生。改一个颜色所有图表重新生成后自动更新。4. 实操过程与核心环节实现一张架构图的完整诞生记录4.1 需求分析与信息收集假设我们要为某个模拟项目绘制一张系统架构图。第一步不是打开工具而是收集信息。我会找相关开发者聊清楚几个问题系统有哪些核心模块模块之间的调用协议是什么有没有同步和异步的区分数据存储用了哪些组件外部依赖有哪些这个阶段我通常用纯文本记录格式不限关键是信息完整。比如客户端通过 HTTPS 调用 API 网关API 网关根据路由转发到用户服务、订单服务、商品服务用户服务和订单服务各自有独立的数据库订单服务在创建订单后向消息队列发送事件通知服务消费消息队列的事件发送邮件和短信所有服务都依赖配置中心和日志服务4.2 逻辑结构验证把上面的信息翻译成 Mermaid 语法快速验证逻辑是否完整、关系是否正确graph TB Client[客户端] -- Gateway[API 网关] Gateway -- UserSvc[用户服务] Gateway -- OrderSvc[订单服务] Gateway -- ProductSvc[商品服务] UserSvc -- UserDB[(用户数据库)] OrderSvc -- OrderDB[(订单数据库)] ProductSvc -- ProductDB[(商品数据库)] OrderSvc -- MQ[消息队列] MQ -- NotifySvc[通知服务] NotifySvc -- Email[邮件服务] NotifySvc -- SMS[短信服务] UserSvc -- Config[配置中心] OrderSvc -- Config ProductSvc -- Config UserSvc -- Log[日志服务] OrderSvc -- Log ProductSvc -- Log这一版图能看出基本关系但有几个问题配置中心和日志服务的连线太多显得杂乱邮件和短信服务可以合并为一个外部依赖客户端和网关之间应该标注协议。这些问题在逻辑阶段就要发现而不是等到样式阶段再改。4.3 翻译为 D2 并加入设计系统确认逻辑后把 Mermaid 翻译成 D2同时引入设计令牌和样式类。以下是核心片段direction: right classes: { service: { style: { fill: #e8f0fe stroke: #1a73e8 font-color: #1a1a1a border-radius: 8 shadow: true } } database: { style: { fill: #fce8e6 stroke: #d93025 font-color: #1a1a1a border-radius: 4 } } external: { style: { fill: #f1f3f4 stroke: #5f6368 font-color: #5f6368 border-radius: 20 stroke-dash: 4 } } } client: 客户端 { class: external } gateway: API 网关 { class: service } user_svc: 用户服务 { class: service } order_svc: 订单服务 { class: service } product_svc: 商品服务 { class: service } user_db: 用户数据库 { class: database; shape: cylinder } order_db: 订单数据库 { class: database; shape: cylinder } product_db: 商品数据库 { class: database; shape: cylinder } mq: 消息队列 { class: service; shape: parallelogram } notify_svc: 通知服务 { class: service } infra: 基础设施 { config: 配置中心 { class: service } log: 日志服务 { class: service } } client - gateway: HTTPS gateway - user_svc gateway - order_svc gateway - product_svc user_svc - user_db order_svc - order_db product_svc - product_db order_svc - mq: 发布事件 mq - notify_svc: 消费事件 user_svc - infra.config order_svc - infra.config product_svc - infra.config user_svc - infra.log order_svc - infra.log product_svc - infra.log4.4 布局调优与视觉打磨D2 默认的 Dagre 布局在这个场景下会把基础设施放在底部导致连线交叉严重。我做了几个调整把基础设施容器设置为direction: right让配置中心和日志服务并排把消息队列和通知服务放在订单服务右侧形成清晰的数据流给客户端到网关的连线加上协议标注。调优后的布局逻辑是左侧是客户端和网关中间是三个核心服务及其数据库右侧是消息队列和通知服务底部是基础设施。这样数据流向从左到右符合阅读习惯。视觉打磨还包括给核心服务加上阴影让它们从背景中浮起来数据库用圆柱体形状一眼就能识别外部系统用虚线边框跟内部服务区分开连线上的标签用较小字号和灰色不抢主体内容的注意力。4.5 导出与嵌入文档最终用d2 --format svg导出 SVG 文件。SVG 的好处是可以直接嵌入 Markdown 文档也可以嵌入网页。如果文档平台支持 HTML还可以给 SVG 加上交互效果比如鼠标悬停显示组件详情。导出命令d2 --format svg --theme 0 --pad 20 architecture.d2 architecture.svg--theme 0使用默认主题--pad 20给图表周围留出 20 像素的内边距避免内容贴边。实操心得SVG 导出后建议用浏览器打开检查一遍。有时候 D2 的布局引擎会在某些分辨率下产生文字重叠提前发现比发布后再改成本低得多。5. 常见问题与排查技巧实录5.1 图表文字重叠或溢出这是 D2 和 Mermaid 都会遇到的问题。原因通常是节点文字太长或者布局引擎没有正确计算节点尺寸。解决方法有几个缩短节点标签用换行符\n手动断行调整grid-columns或grid-rows参数切换到 ELK 布局引擎或者给节点设置固定的width和height。我遇到过一个案例某个服务的名称特别长D2 自动计算的节点宽度不够文字溢出了边框。后来在节点定义里加了width: 200问题解决。但更好的做法是给这类长名称的节点定义一个专门的样式类统一设置宽度。5.2 连线交叉严重连线交叉是复杂架构图的常见问题。D2 的 Dagre 引擎在有向无环图上表现不错但遇到多层级、多交叉的场景就力不从心。我的经验是先尝试调整direction参数把right改成down或者反过来如果还不行把一些节点分组到容器里减少跨容器的连线最后才考虑换布局引擎。还有一个技巧是虚拟节点。在两个距离很远但需要连线的节点之间插入一个不可见的虚拟节点让连线经过它可以引导布局引擎产生更合理的路径。D2 支持用style.opacity: 0来隐藏节点。5.3 样式不生效或冲突样式不生效通常有三个原因类名拼写错误、样式优先级被覆盖、或者样式属性名写错了。D2 的样式属性名跟 CSS 不完全一样比如边框圆角是border-radius阴影是shadow虚线是stroke-dash。建议对照官方文档逐一核对。样式冲突则多发生在多个图表文件合并时。如果两个文件都定义了service类但样式不同后加载的会覆盖先加载的。解决办法是给类名加项目前缀或者把公共样式抽到一个单独的styles.d2文件里所有图表都引用这个文件。5.4 导出图片模糊或尺寸不对导出 PNG 时如果分辨率不够图片会模糊。D2 导出 PNG 时可以指定--scale参数比如--scale 3表示三倍分辨率。但更推荐导出 SVG因为 SVG 是矢量的无论放大多少倍都清晰。如果导出后发现图表周围空白太多或太少调整--pad参数。如果图表整体尺寸不对检查 D2 文件里有没有设置width和height这两个参数会覆盖布局引擎的自动计算。5.5 常见问题速查表问题现象可能原因排查方法解决方案文字溢出节点节点宽度不足检查节点标签长度设置width或手动断行连线交叉严重布局引擎选择不当尝试不同direction切换 ELK 引擎或分组样式不生效类名拼写错误检查类名和属性名对照文档修正样式冲突类名重复定义检查多个文件的类名加前缀或抽公共样式导出图片模糊PNG 分辨率不足检查--scale参数改用 SVG 或提高 scale图表尺寸异常手动设置了宽高检查width/height移除或调整参数避坑技巧每次修改 D2 文件后先用d2 --watch预览确认无误再导出。直接导出再打开图片检查效率低很多。另外建议把 D2 源文件和导出的 SVG 都提交到版本库这样既能追踪样式变化又能保证文档里的图片始终可用。6. 把 diagram-design 嵌入团队工作流的一些经验6.1 建立图表规范文档diagram-design 要落地光有工具不够还需要一份团队共识的图表规范。这份规范不需要很长但必须明确几个核心约定什么场景用什么类型的图每种图的标准配色和字体节点形状的语义连线的样式规则图表的命名和存放位置。我们团队的规范文档只有两页但覆盖了 90% 的日常出图场景。新成员入职时先读这份规范再看几个示例文件基本就能产出风格一致的图表。6.2 把图表生成加入构建流程如果项目已经有 CI/CD 流程可以把图表生成作为一个构建步骤。每次 D2 源文件变更自动重新生成 SVG 并部署到文档站点。这样文档里的图表永远是最新的不需要手动更新。实现方式很简单在构建脚本里加一行d2 --format svg src/diagrams/*.d2 dist/diagrams/然后把dist/diagrams/目录部署到文档服务器。如果用的是静态站点生成器还可以把 SVG 直接嵌入 Markdown。6.3 定期回顾和优化样式系统设计令牌和样式类不是定下来就不变的。随着项目演进可能会发现某些颜色对比度不够某些字体大小在移动端看不清某些形状语义容易混淆。我建议每个季度花半小时回顾一下图表样式根据实际使用中的反馈做微调。回顾时重点看几个指标图表在文档中的实际显示效果读者有没有反馈看不懂新出的图表有没有偏离规范设计令牌有没有跟产品设计系统脱节。这些检查不需要很正式但坚持做能避免样式系统慢慢腐化。6.4 处理历史图表的迁移如果团队之前已经积累了大量用传统工具画的图全部迁移到 diagram-design 成本很高。我的建议是新图一律用新流程旧图在需要修改时顺便迁移。不要为了统一而统一迁移的目的是降低维护成本如果一张图半年都不改一次迁移的优先级就很低。迁移时也不是完全重画。可以先把旧图导出为 SVG然后用 D2 的background-image功能把旧图作为背景在上面叠加新的样式元素。这样既能保留旧图的内容又能逐步统一视觉风格。6.5 一些让我少走弯路的工具和资源D2 官方文档是我查得最多的资源尤其是样式属性和布局引擎那几页。Mermaid 的在线编辑器适合快速验证逻辑。SVG 优化工具可以压缩导出文件的大小。设计令牌的格式我参考了 W3C 的设计令牌规范草案虽然还没正式定稿但思路很清晰。另外我强烈建议把常用的 D2 样式片段保存成代码片段用编辑器的 snippet 功能快速插入。比如一个标准的服务节点定义、一个数据库节点定义、一个外部系统定义每次新建图表时直接插入比从头写快得多。7. 关于图表设计的一些个人体会做了这么多图表我越来越觉得 diagram-design 的核心不是工具而是约束。没有约束的图表工具给了你无限的自由但自由往往导致混乱。设计令牌是约束样式类是约束形状语义是约束布局规则也是约束。正是这些约束让不同人、不同时间产出的图表能够保持一致。另一个体会是图表的第一读者是你自己。很多人画图时想着“别人要看”于是堆砌信息结果图变得复杂难懂。但如果你先为自己画图——用最清晰的方式表达你理解的结构——然后再考虑别人的阅读习惯做调整图的质量会高很多。最后不要追求一步到位。我最早用 D2 画的图也很粗糙样式混乱布局奇怪。但每次画完花几分钟回顾一下哪里可以改进下次就能好一点。图表设计跟写代码一样是练出来的不是学出来的。
阅读完成 · 觉得有帮助?
咨询建站