用代码生成图表:SVG+Mermaid+AI驱动的diagram-design实践

发布时间:2026/9/13 9:09:57
用代码生成图表:SVG+Mermaid+AI驱动的diagram-design实践 1. 项目概述从“diagram-design”这个词开始我们到底在设计什么“diagram-design”不是某个具体软件的代号也不是某家公司的产品名它是一个正在快速凝聚共识的技术动作——用代码生成结构化、可复用、可嵌入、可交互的图表。我第一次在团队晨会上听到这个词是前端同事甩出一张 SVG 流程图说“这个图不用设计师切图了我们用 Mermaid 写完CI 自动转成 SVG直接扔进文档站和管理后台。”那一刻我才意识到所谓 diagram-design本质是一场从“截图交付”到“声明式建模”的范式迁移。它解决的核心问题非常朴素传统图表协作链路太长。产品经理画个草图 → UI 设计师用 Figma 做高保真 → 开发手动重写 HTML/CSS → 运维部署时发现样式错位 → 运营改文案又要返工。整个过程里图是静态的、离散的、不可追溯的。而 diagram-design 的目标是让图变成一种“活文档”一次编写多端渲染网页、PDF、VS Code 预览、Typora 内联版本可控Git 跟踪 diff逻辑可验证比如检查流程图是否闭环、ER 图外键是否缺失甚至能动态注入数据把后端返回的拓扑结构 JSON 直接喂给 Mermaid 渲染器。关键词里反复出现的SVG、HTML、Mermaid、Claude Code其实构成了三层能力栈最底层是SVG—— 它不是“图片”而是基于 XML 的矢量绘图语言支持 CSS 样式、JS 事件、无障碍语义浏览器原生支持缩放不失真Cesium 加载地图标注图标、LeaferJS 导出可编辑图形、PyQt5 嵌入 WebEngine 显示全靠它撑底中间层是Mermaid—— 它不是绘图工具而是“图表 DSL领域专用语言”用极简文本描述逻辑关系graph TD; A -- B; B -- C再由 JS 渲染器实时编译为 SVG顶层是Claude Code这类 AI 编程助手 —— 它不画图但能听懂你一句“生成一个 pelican 骑自行车的 SVG”自动补全path d...贝塞尔曲线或把一段模糊需求“画个学校教务系统的 ER 图含学生、课程、教师、选课四个实体”精准翻译成可运行的 Mermaid 代码。这三者叠加才让 diagram-design 从概念落地为日常开发中的真实生产力。适合谁来关注不是只有架构师或可视化工程师。如果你是写技术文档的工程师Mermaid 一行代码就能生成带超链接的序列图比截图粘贴强十倍如果你是做教学系统的老师用 HTML SVG 手写一个可拖拽的电路元件连线图学生点开网页就能实操如果你是运维把 Prometheus 告警规则树用 Mermaid 自动生成每次配置变更都能看到拓扑变化。它不挑人只挑思维——你愿不愿意把“画图”这件事当成写代码一样去思考、去版本化、去自动化。2. 核心思路拆解为什么必须用代码驱动图表而不是继续用 draw.io 或 Figma很多人第一反应是“我用 draw.io 不香吗拖拖拽拽实时预览还能导出 SVG。”这话没错但 draw.io 解决的是“单次作图”而 diagram-design 解决的是“持续演进”。我拿自己踩过的一个坑来说明去年做微服务治理平台需要展示 37 个服务之间的调用链路。初期用 draw.io 画了一张巨图发给各业务方确认。结果两周后支付组加了两个新服务订单组下线了一个旧模块运维又调整了网关路由策略。我打开 draw.io 文件发现图上 23 处连线颜色不对、4 个节点位置偏移、还有 3 个服务名拼写和最新 API 文档不一致。重画耗时半天截图更新下游文档立刻脱节。最后我花了 3 小时把所有服务元数据名称、所属域、健康状态、依赖列表整理成 YAML用 Python 脚本生成 Mermaidgraph LR代码再用 GitHub Actions 每日凌晨自动构建并推送到文档站。从此图永远和代码库保持同步。这就是核心思路差异draw.io 是“所见即所得”Mermaid 是“所思即所得”。前者让你控制像素后者让你控制逻辑。Mermaid 的语法设计极度克制没有“左对齐/右对齐”这种排版指令只有TD自上而下、LR自左而右两种布局方向节点间关系用--、、-.-等符号表达语义实线箭头同步调用虚线箭头异步消息粗箭头关键路径。这种克制不是缺陷而是刻意为之——它强制你先想清楚“系统里谁依赖谁”而不是纠结“这个框该放在第几行第几列”。就像写 SQL 时不会去想“SELECT 字体该用几号”因为数据库关心的是数据关系不是显示样式。至于为什么选 SVG 而非 Canvas 或 PNG三个硬指标可访问性a11y、可搜索性searchable、可样式化styling。SVG 是文本格式屏幕阅读器能逐字读出text x100 y50用户服务/textCtrlF 能直接搜到“订单服务”CSS 可以全局控制所有.node rect的圆角半径或者给.edgePath path添加 hover 发光效果。而 PNG 是位图Canvas 是命令式绘图两者都无法被搜索引擎索引也无法用 CSS 批量修改样式。Cesium 加载 SVG 标注时能直接用getElementById获取某个城市图标并绑定点击事件LeaferJS 导出 SVG 后设计师用 Inkscape 打开还能继续编辑路径节点——这些能力都建立在 SVG 的文本可读性基础上。Claude Code 在其中的角色是“语义翻译器”。它不替代 Mermaid而是降低使用门槛。比如你记不住 Mermaid 的 ER 图语法entity Student { * id : int }直接问它“用 Mermaid 画学生-课程-选课的三表 ER 图主键用 * 标记外键用 o 标记”它秒回完整代码。更实用的是上下文理解你把一段 Spring Boot 的Entity类代码粘贴过去让它“生成对应的 Mermaid ER 图”它能准确识别ManyToOne注解并转换为o--||关系线。这不是魔法是它对 Java JPA 和 Mermaid 语法的双重训练结果。但要注意Claude Code 生成的代码必须人工校验——我见过它把OneToMany(mappedBy student)错译成||--o应为o--||导致关系方向完全颠倒。所以它的定位很清晰加速初稿生成不替代逻辑审查。3. 核心细节解析Mermaid 语法精要、SVG 手写要点与 HTML 集成实战Mermaid 看似简单但真正用好需要吃透三类细节语法边界、渲染控制、错误溯源。先说语法边界。很多人以为graph TD; A -- B; B -- C就是全部其实 Mermaid 有 12 种图表类型每种都有隐含约束。比如流程图flowchart TD中节点 ID 不能含空格或中文A -- 用户服务会报错必须写成A -- UserService或用双引号包裹用户服务而时序图sequenceDiagram中参与者participant名称可以是中文但激活条activate必须用英文变量名。最易踩坑的是 ER 图entity Student { * id : int }中的冒号:前后不能有空格否则解析失败字段类型int必须小写写成INT会被忽略。这些不是 bug是语法解析器的严格设计——它假设你已将业务模型抽象为标准命名规范而非容忍随意输入。渲染控制的关键在于理解 Mermaid 的“两阶段编译”第一阶段是文本解析parsing把 Mermaid 代码转成内部 JSON AST第二阶段是渲染rendering把 AST 转成 SVG 元素。我们能干预的主要是第二阶段。比如默认流程图节点是矩形但你可以用classDef定义样式类graph TD A[开始] -- B{判断} B --|是| C[执行] B --|否| D[结束] classDef green fill:#9f6,stroke:#333; class C,D green;这里classDef green定义了一个 CSS 类class C,D green将其应用到节点 C 和 D。注意fill:#9f6是简写等价于#99ff66但 Mermaid 渲染器会把它转成 SVG 的fill#99ff66属性。如果你想让所有节点文字居中不能写styletext-align:center而要用%%{init: {themeVariables: { fontSize: 14px, fontFamily: sans-serif}}}%%这种初始化块——因为 Mermaid 的样式系统是主题驱动的不是内联 CSS。SVG 手写要点常被低估。很多人以为“Mermaid 导出 SVG 就完事了”但实际项目中往往需要手写 SVG 来实现 Mermaid 不支持的效果。比如那个“pelican 骑自行车”的热搜需求Mermaid 根本无法生成具象图形必须手写path。关键技巧有三个路径简化、坐标系理解、语义化命名。先说路径简化用在线工具如 SVGOMG压缩原始 Illustrator 导出的 SVG把M 10.5,20.3 L 15.7,25.8 ...这种浮点数坐标转为整数并合并重复指令。再看坐标系SVG 默认viewBox0 0 100 100但浏览器渲染时会按容器宽高拉伸。若要在 HTML 中固定大小必须同时设置width和height属性svg width200 height100 viewBox0 0 100 100否则在不同屏幕下变形。最后是语义化命名不要用idpath1而用idpelican-beak这样后续用 JS 控制喙部开合动画时代码可读性极高。HTML 集成是落地最后一环。常见误区是直接img srcdiagram.svg这会导致 SVG 失去交互能力无法绑定 click 事件、无法用 CSS 修改颜色。正确做法是内联 SVG把 SVG 代码直接复制到 HTML 的body中。但手动复制太傻要用自动化方案。我推荐两种Webpack 插件用svg-inline-loader在 JS 中import Diagram from ./flowchart.svg然后document.body.innerHTML Diagram服务端注入用 Node.js 的fs.readFileSync(diagram.svg, utf8)读取文件内容插入模板字符串。更进一步可以封装一个diagram-viewer自定义元素diagram-viewer srcmermaid-flowchart.mmd typeflowchart/diagram-viewer其内部逻辑是用fetch读取.mmd文件 → 调用 Mermaid 的mermaid.render()API 渲染为 SVG → 将 SVG 字符串插入 Shadow DOM。这样既保持 HTML 干净又获得完整控制权。我实测过在 100 节点的拓扑图中这种方式比iframe加载快 3 倍且支持无障碍导航svg aria-labelledbytitletitle idtitle服务拓扑图/title。提示Mermaid 的securityLevel配置必须设为loose才能渲染内联 SVG否则会过滤script标签即使你没写 script。这是安全机制不是 bug。4. 实操全流程从零搭建一个可维护的 diagram-design 工作流现在我们把所有碎片组装成一条可复用的工作流。目标任何成员提交一个.mmd文件CI 自动构建为 SVG并发布到文档站同时生成 PNG 供 PPT 使用。整个流程不依赖本地环境全部在 GitHub Actions 中完成。4.1 环境准备轻量级 Mermaid CLI 与 VS Code 配置Mermaid 官方提供mermaid-js/mermaid-cli但直接npm install -g会污染全局环境。更稳妥的做法是在项目根目录创建package.json仅安装 CLI 为 devDependency{ devDependencies: { mermaid-js/mermaid-cli: ^10.9.0 } }然后在package.json的scripts中添加scripts: { build:diagrams: mmdc -i docs/diagrams/*.mmd -o docs/svg/ -t default -w 1200, watch:diagrams: mmdc -i docs/diagrams/*.mmd -o docs/svg/ -t default -w 1200 -p }-w 1200指定输出宽度为 1200px适配文档站容器-p启用监听模式文件保存即重绘。VS Code 用户需安装两个插件Mermaid Preview实时预览和Prettier格式化.mmd文件。特别注意Prettier 默认不支持 Mermaid需在.prettierrc中添加{ plugins: [prettier-plugin-mermaid], mermaid: { printWidth: 100, tabWidth: 2 } }这样按ShiftAltF就能自动对齐--符号大幅提升可读性。4.2 文件结构设计让图表像代码一样可管理混乱的文件结构是 diagram-design 最大敌人。我见过团队把 50 个.mmd文件全丢在/docs根目录结果找“用户登录流程图”要翻 10 分钟。正确结构必须分层/docs /diagrams # Mermaid 源码人类可读 /architecture # 架构图系统边界、服务划分 api-gateway.mmd service-mesh.mmd /process # 业务流程用户旅程、审批流 user-login.mmd order-approval.mmd /data # 数据模型ER 图、状态机 er-school.mmd state-payment.mmd /svg # 自动生成的 SVG机器产物.gitignore /png # 自动生成的 PNG用于 PPT.gitignore /assets /icons # 手写 SVG 图标如 pelican-bike.svg关键原则源码唯一产物隔离。.mmd文件是唯一真相源SVG/PNG 全由 CI 生成绝不手动编辑。这样git blame能精准定位谁在何时修改了哪个流程逻辑。4.3 GitHub Actions 自动化三步构建发布流水线在.github/workflows/diagram-build.yml中定义工作流name: Build Diagrams on: push: paths: - docs/diagrams/** - .github/workflows/diagram-build.yml jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 18 - name: Install dependencies run: npm ci - name: Generate SVG and PNG run: | npm run build:diagrams # 将 SVG 转 PNG用 puppeteer npx puppeteer-render --input docs/svg/*.svg --output docs/png/ --width 1200 --height 800 - name: Deploy to Docs Site uses: peaceiris/actions-gh-pagesv3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs这里有个隐藏技巧puppeteer-render是我封装的轻量工具它用 Puppeteer 启动无头 Chrome加载 SVG 文件再用page.screenshot()截图。为什么不用 ImageMagick因为 SVG 中的 CSS 样式如filter: drop-shadow()在 ImageMagick 中无法渲染而 Chrome 完全支持。实测 20 个 SVG 转 PNG总耗时 8 秒。4.4 文档站集成让图表成为活文档的一部分以 VuePress 为例在/docs/.vuepress/config.js中添加module.exports { plugins: [ [ vuepress/plugin-medium-zoom, { selector: svg.diagram img, // 放大 SVG 内嵌图 }, ], ], }并在 Markdown 文档中这样引用!-- docs/guide/architecture.md -- ## 系统架构 以下是核心服务拓扑 div classdiagram svg xmlnshttp://www.w3.org/2000/svg viewBox0 0 800 400 !-- 此处粘贴 docs/svg/architecture/api-gateway.svg 的内容 -- /svg /div 提示点击图表可放大查看细节。关键点div classdiagram触发 zoom 插件viewBox属性确保响应式缩放SVG 内联避免跨域问题。更高级的玩法是用 Vue 组件动态加载template div v-htmlsvgContent/div /template script export default { data() { return { svgContent: } }, async mounted() { const res await fetch(/svg/api-gateway.svg) this.svgContent await res.text() } } /script这样图表加载失败时Vue 的errorCaptured钩子能捕获并提示“图表加载异常”比静态 HTML 更健壮。5. 常见问题与排查技巧实录那些官方文档不会写的坑5.1 Mermaid 渲染空白先查这三件事Mermaid 报错极其安静——页面一片空白控制台却无任何错误。我总结出 90% 的空白问题源于以下三点脚本加载顺序错误必须先加载 Mermaid JS再调用mermaid.initialize()最后执行mermaid.render()。常见错误是把mermaid.render()放在script标签顶部此时 DOM 还未解析找不到目标容器。解决方案用DOMContentLoaded事件包装document.addEventListener(DOMContentLoaded, () { mermaid.initialize({ startOnLoad: true }); });ID 冲突mermaid.render(id1, graph TD; A--B)中的id1必须是页面中唯一存在的 DOM 元素 ID。如果多个组件都用diagram作为 ID后渲染的会覆盖前一个。我的做法是生成随机 IDconst id mermaid- Math.random().toString(36).substr(2, 9); const container document.createElement(div); container.id id; document.body.appendChild(container); mermaid.render(id, code);CSS 重置干扰某些 UI 框架如 Bootstrap会全局设置* { box-sizing: border-box; }而 Mermaid 的节点计算依赖默认content-box。结果就是节点文字被截断。临时修复给 Mermaid 容器加 CSS.mermaid svg { box-sizing: content-box !important; }5.2 SVG 导出后字体乱码根源在 font-familyMermaid 默认用trebuchet ms, verdana, sans-serif但某些 Linux 服务器缺少trebuchet ms字体渲染时 fallback 到DejaVu Sans而 DejaVu Sans 对中文支持极差。现象是本地预览正常CI 构建的 SVG 中中文全成方块。解决方案有二方案一推荐在 Mermaid 初始化时指定 web-safe 字体mermaid.initialize({ theme: default, fontFamily: Microsoft YaHei, Noto Sans CJK SC, sans-serif });注意字体名含空格必须用引号包裹且Noto Sans CJK SC是 Google 开源字体Linux 服务器可通过apt-get install fonts-noto-cjk安装。方案二终极将文字转为路径。Mermaid CLI 支持--svg-fonts参数但更可靠的是用svg-text-to-path工具npx svg-text-to-path --input docs/svg/*.svg --output docs/svg/它会把text标签替换为path dM10,20 L15,20 ...彻底摆脱字体依赖。代价是 SVG 体积增大 3~5 倍但换来 100% 一致性。5.3 如何调试一个“看起来正确但逻辑错误”的 Mermaid 图最典型的是 ER 图关系线画反了。比如Student ||--o Course表示“一个学生对应多个课程”但业务实际是“一门课程被多个学生选”应为Course ||--o Student。肉眼难辨需用工具验证。我的方法是用 Mermaid Live Editor 的“Source”面板粘贴代码后点击右上角{}图标查看生成的 JSON AST。找type: relation的节点检查fromId和toId是否符合业务语义。写单元测试用 Jest jsdom 模拟渲染test(ER diagram has correct foreign key direction, () { const code entity Student { * id } entity Course { * id } Student ||--o Course; const { svg } mermaid.render(test, code); expect(svg).toContain(Student); expect(svg).toContain(Course); // 检查 path 元素的 class 是否含 relation expect(svg).toMatch(/path.*?class.*?relation.*?/); });这样每次 PR 提交CI 都会跑测试防止关系线被误改。人工走查清单我打印了一份 A4 纸 checklist贴在显示器边[ ] 所有--箭头是否指向数据流向非操作流向[ ] ER 图中||一端是否总在主键侧[ ] 流程图中菱形节点判断是否都有“是/否”两条出口[ ] 所有节点 ID 是否在项目命名规范内全小写、下划线分隔5.4 Claude Code 生成的 Mermaid 代码如何安全落地AI 生成代码必须过三关语法关用mermaid-cli的--validate模式检查npx mmdc --input temp.mmd --validate # 输出 Valid mermaid source 或具体错误行号语义关人工核对业务逻辑。例如Claude 生成的时序图中Actor - System: login()后紧接System -- DB: SELECT * FROM users但实际系统应先校验 token 再查 DB。这时要加一行System - System: verify token。安全关禁用click交互指令。Mermaid 支持click A callback触发 JS 函数但若 AI 生成的代码含click UserCard openProfile()而openProfile是未定义函数页面会报错。我的做法是在 CI 中用正则扫描grep -r click docs/diagrams/ | grep -v ^\s*# # 排除注释行发现即 fail强制人工审核。注意Claude Code 在中国境内可用性受网络环境影响若提示 “not available in your country”请勿尝试绕过限制。可改用国内大模型如通义千问、Kimi的 Mermaid 插件它们对中文语义理解更准且无需额外配置。6. 进阶场景拓展当 diagram-design 遇上真实业务复杂度6.1 动态图表用 JavaScript 驱动 Mermaid 实时更新静态图解决不了“数据驱动”的需求。比如监控大屏需要每 30 秒刷新服务健康状态并实时变色节点。Mermaid 本身不支持动态更新但我们可以 hack用mermaid.render()重新渲染整个图。关键优化点是增量更新——只修改变化的部分而非重绘全部。我做过一个 Kubernetes 集群拓扑图节点状态来自 Prometheus API// 获取实时状态 async function fetchClusterStatus() { const res await fetch(/api/prometheus/metrics); return res.json(); // 返回 { api-server: up, etcd: down } } // 渲染函数接收状态对象 function renderTopology(status) { let code graph TD\n; Object.entries(status).forEach(([service, state]) { const color state up ? green : red; code ${service}[${service}]:::${color}\n; }); // 添加关系线这部分不变可缓存 code api-server -- etcd\n api-server -- kubelet; // 仅重绘不重建容器 mermaid.render(topology, code, (svgCode) { document.getElementById(topology-container).innerHTML svgCode; }); } // 每30秒刷新 setInterval(async () { const status await fetchClusterStatus(); renderTopology(status); }, 30000);这里:::green是 Mermaid 的样式类语法classDef green fill:#0f0;需提前定义。实测在 50 节点的图中重绘耗时 120ms完全满足大屏需求。6.2 SVG 与 WebGL 协同在 Cesium 地图上叠加可交互 SVG 标注Cesium 加载 SVG 的标准方式是Entity.billboard.image但这只能显示静态图标。要实现“点击 SVG 标注弹出详情窗”必须用Entity.polylineSVG路径。我的方案是用 D3.js 读取 SVG 路径数据path dM10,20 L15,20 ...将路径坐标转为地理坐标WGS84创建PolylineGraphics用material: new ColorMaterialProperty(Cesium.Color.RED)设置颜色绑定viewer.screenSpaceEventHandler.setInputAction监听点击。难点在于坐标转换。SVG 的(0,0)是左上角Cesium 的经纬度是球面坐标。我写了一个转换函数function svgToCartesian(svgPath, boundingBox, centerLonLat) { // boundingBox { minX: 0, minY: 0, width: 100, height: 100 } // centerLonLat [116.4, 39.9] 北京中心点 const points parseSvgPath(svgPath); // 解析出所有 (x,y) 坐标 return points.map(([x, y]) { const lon centerLonLat[0] (x - boundingBox.minX) / boundingBox.width * 0.1; const lat centerLonLat[1] - (y - boundingBox.minY) / boundingBox.height * 0.1; return Cesium.Cartographic.toCartesian( new Cesium.Cartographic(Cesium.Math.toRadians(lon), Cesium.Math.toRadians(lat)) ); }); }0.1 是经验系数代表 0.1 度经度范围约 11km可根据实际地图缩放级别调整。这样SVG 的“自行车图标”就能精准落在北京国贸的地理坐标上且支持鼠标悬停高亮。6.3 从 diagram-design 到 design-system沉淀可复用的图表组件库当团队积累 200 个.mmd文件后复用成为刚需。我推动建立了company/diagram-systemNPM 包包含预设主题theme-dark.js深色背景适配、theme-print.js移除阴影适配 PDF 打印原子组件ServiceNode.vue带健康状态指示灯的服务节点、DatabaseIcon.vue可配置类型的数据库图标代码生成器CLI 工具diagram-gen输入 JSON Schema自动生成 ER 图代码。例如定义一个服务节点!-- ServiceNode.vue -- template div :class[service-node, status] slot{{ name }}/slot /div /template script export default { props: [name, status] // status: up | down | warning } /script style scoped .service-node { padding: 8px 12px; border-radius: 4px; font-size: 14px; } .service-node.up { background: #d4edda; color: #155724; } .service-node.down { background: #f8d7da; color: #721c24; } /style然后在 Mermaid 中这样用graph TD A[div classservice-node upAPI Gateway/div] -- B[div classservice-node downPayment Service/div]Mermaid 会原样渲染 HTML 片段CSS 由 Vue 组件提供。这样UI 设计师改一个 CSS 变量所有图表节点风格同步更新。我个人在实际使用中发现diagram-design 的最大价值不在“画得有多美”而在“改得有多快”。上周业务方临时要求增加“灰度发布”环节到部署流程图我只改了 3 行 Mermaid 代码10 秒后文档站、PPT、Confluence 全部更新完毕。而隔壁组还在为 draw.io 文件版本冲突吵架。技术选型没有银弹但当你需要频繁修改、多人协作、长期维护图表时“用代码定义图表”不是炫技而是生存必需。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询