1. 项目概述:为什么一张架构图值得我们重写整个渲染引擎?
“这张图,设计师点头了。”——这是我把 diagram-design 的第一个 SVG 输出发给UI团队时,对方 Slack 里回的原话。没有加粗,没有感叹号,就这八个字,让我在工位上愣了三秒。不是因为夸张,而是太罕见。过去三年,我经手过 47 个中大型系统重构项目,每次画架构图,都像在打一场三方拉锯战:后端工程师坚持用 PlantUML 自动生成,理由是“保证代码和图一致”;前端同学甩来 Mermaid 语法,说“改个 class 名就能重绘”;而设计师盯着 Visio 导出的 PNG,皱着眉说“字体不统一、线条虚化、阴影没层次,印刷出来就是糊的”。最后妥协方案往往是——截图、PS 修边、手动加标注、导出高清 PNG,再塞进 Confluence。一套流程走下来,平均耗时 2 小时 17 分钟,且每次需求变更,图就得重来一遍。
diagram-design 不是又一个在线画图工具,它是一次对“架构图本质”的重新定义:图不是文档的附属品,而是可执行、可版本化、可设计介入的第一等公民。它用纯 HTML + SVG 实现,不依赖任何后端服务、不调用远程 CDN、不嵌入第三方 JS 库,整套逻辑压缩在单个 HTML 文件内运行。你打开它,就是一个<html>标签开始的静态页面;你右键“查看源码”,看到的是清晰的<svg>结构、语义化的<g>分组、带>{ "type": "object", "properties": { "nodes": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "pattern": "^[a-z0-9-]+$" }, "label": { "type": "string", "minLength": 1 }, "type": { "type": "string", "enum": ["service", "database", "message-queue", "cache", "gateway"] } } } } } }
这个 schema 强制要求:节点 ID 必须小写字母+数字+短横线(适配 DNS 命名规范);label 不得为空;type 必须是预设的五类基础设施。当你的架构师在 YAML 文件里写下type: kafka-broker,校验器会立刻报错:“kafka-broker not in enum”,逼你回归到“消息队列”这个抽象层级。这看似繁琐,实则是防止架构图沦为“命名混乱的截图集合”的第一道防火墙。
3.2 主题注入:CSS 变量驱动的出版级样式系统
diagram-design 的theme.css文件里,没有一行#api-gw { background: #3b82f6; }这样的硬编码。取而代之的是 37 个 CSS 自定义属性,按出版场景分层:
:root { /* 基础色盘 - 符合 WCAG 2.1 AA 对比度 */ --color-primary: #1e40af; /* 深蓝,用于核心服务 */ --color-secondary: #059669; /* 翡翠绿,用于数据存储 */ --color-accent: #dc2626; /* 红色,用于告警组件 */ /* 出版专用 - 打印时自动切换 */ @media print { --color-primary: #000000; /* 黑色,保证油墨覆盖率 */ --color-line-width: 0.75pt; /* 印刷最小线宽 */ } /* 字体栈 - 思源黑体优先,fallback 到系统字体 */ --font-family: "Source Han Sans SC", "Noto Sans CJK SC", "Microsoft YaHei", sans-serif; }关键在于@media print块。当用户按 Ctrl+P 打印时,浏览器会自动应用这些规则:所有彩色节点转为纯黑,线条加粗至 0.75pt(避免印刷时断线),字体强制使用思源黑体(通过@font-face加载本地 WOFF2 文件)。这解决了“为什么架构图打印出来字迹发虚”的行业顽疾——根源不是打印机差,而是网页 CSS 没为印刷介质做适配。
3.3 布局引擎:力导向算法的工程化改造
diagram-design 的布局器不是 D3.js 的forceSimulation()开箱即用。它做了三项关键改造:
阻尼系数动态调节:标准力导向算法中,节点斥力
k是固定值。但在架构图中,“数据库集群”节点应比“配置中心”节点有更强的“存在感”。因此,k值根据node.type动态计算:k = base_k * type_weight[node.type],其中type_weight["database"] = 1.8,type_weight["config-center"] = 0.6。这确保核心组件天然占据图中心区域。连接线智能捆扎:微服务间常有数十条 HTTP 调用,若每条都画独立线,图将成蜘蛛网。diagram-design 采用改进的 Hierarchical Edge Bundling:先按
source+target分组,再对每组线束计算贝塞尔控制点,使 12 条调用线聚合成 1 条平滑曲线,并在线束旁标注×12。这既保持语义(调用频次),又提升可读性。锚点物理约束:传统力导向允许节点自由漂移,但架构图要求“Kafka Broker 必须在左侧,Flink JobManager 在右侧”。因此引入
fixedPosition属性:{ "id": "kafka-broker", "fixedPosition": { "x": 100, "y": 300 } }。布局器会将此节点视为“无限质量天体”,其他节点受其引力影响,但自身坐标锁定。这实现了“半自动布局”——既保留算法智能,又满足人工干预需求。
3.4 SVG 渲染:从 DOM 元素到出版级矢量的精确映射
渲染器的核心逻辑是renderNode(node)函数,它不返回字符串,而是直接操作 DOM:
function renderNode(node) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); g.setAttribute('data-id', node.id); g.setAttribute('data-type', node.type); // 绘制主体矩形 - 使用 SVG path 而非 rect,确保印刷时无像素偏移 const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', `M${x} ${y} h${width} v${height} h-${width} Z`); path.setAttribute('fill', `var(--color-${node.type})`); path.setAttribute('stroke', 'var(--color-border)'); path.setAttribute('stroke-width', 'var(--color-line-width)'); // 添加标签文本 - 使用 textPath 绕路径,实现弧形标注 const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); const textPath = document.createElementNS('http://www.w3.org/2000/svg', 'textPath'); textPath.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#node-label-path'); textPath.textContent = node.label; text.appendChild(textPath); g.appendChild(path); g.appendChild(text); return g; }注意两个细节:第一,用<path>代替<rect>绘制节点主体。因为<rect>在某些 PDF 转换器中会渲染为填充色块,丢失 SVG 的矢量保真度;而<path>的d属性是数学定义的路径,100% 保证印刷精度。第二,标签使用<textPath>绕预定义路径,这使得“API 网关”文字能沿节点顶部弧线排列,比直角排版更符合出版物视觉流。
3.5 交互增强:不破坏 SVG 语义的轻量级操作
所有交互(拖拽、缩放、聚焦)都通过原生 DOM 事件实现,绝不污染 SVG 结构:
- 拖拽:监听
mousedown→mousemove→mouseup,仅修改g元素的transform属性,如transform="translate(120,85)"。SVG 源码中不新增任何<animate>或<script>标签。 - 缩放:通过 CSS
transform: scale(1.5)作用于<svg>根元素,而非重绘所有节点。这保证缩放后getBBox()返回的仍是原始坐标,便于后续导出。 - 聚焦:点击节点时,添加
focusclass 到对应<g>,CSS 规则.node:focus { filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.5)); }实现发光效果。焦点状态完全由 CSS 控制,无需 JS 操控 DOM 样式。
这种“CSS 驱动交互”的设计,让架构图在禁用 JavaScript 的环境中(如某些安全审计场景)仍能作为静态 SVG 正常显示,只是失去交互功能——符合“渐进增强”原则。
3.6 导出系统:一键生成多格式、多用途交付物
导出按钮触发的不是简单的canvas.toDataURL(),而是四通道并行生成:
| 格式 | 生成方式 | 适用场景 | 关键参数 |
|---|---|---|---|
| SVG | new XMLSerializer().serializeToString(svgElement) | 设计师修图、InDesign 排版 | viewBox="0 0 2400 1800"(A4 尺寸) |
使用jsPDF+svg2pdf.js,强制设置unit: 'pt', format: 'a4' | 印刷交付、客户汇报 | margin: [72, 72, 72, 72](1 英寸边距) | |
| PNG | canvg渲染 SVG 到 Canvas,canvas.toDataURL('image/png', 1.0) | Confluence 插入、邮件发送 | scale: 2(2x Retina 屏) |
| JSON | JSON.stringify(graphData, null, 2) | Git 版本管理、CI/CD 自动化 | timestamp: new Date().toISOString() |
特别说明 PDF 导出:svg2pdf.js会解析 SVG 中所有<text>元素的font-family,自动嵌入思源黑体 WOFF2 字体子集(仅包含图中实际使用的汉字),确保 PDF 在无字体环境(如客户服务器)中显示不乱码。这是普通截图导出永远无法实现的。
3.7 版本审计:Git 友好的架构图变更追踪
diagram-design 的graph.json文件设计为 Git 友好格式:
{ "metadata": { "generatedBy": "diagram-design@2.4.1", "generatedAt": "2024-06-15T08:22:34.123Z", "author": "zhang.san@company.com" }, "nodes": [ { "id": "redis-cluster", "label": "Redis 集群", "type": "cache", "version": "7.0.12", "deployment": "k8s-statefulset" } ] }metadata块记录生成工具版本、时间戳、作者邮箱,git diff可清晰看到:“version从 6.2.6 升级到 7.0.12”。而nodes[].deployment字段直接关联 Kubernetes 部署清单,当 K8s YAML 文件更新时,CI 流程可自动触发graph.json重生成并提交,形成“基础设施即代码”的完整闭环。这才是真正的“架构即代码”(Architecture as Code),而非口号。
注意:不要手动编辑
graph.json中的metadata字段!它由渲染器自动生成。手动修改会导致git blame指向错误责任人,破坏审计链。
4. 实操全流程:从零开始构建一个可交付的车载 NPU 架构图
现在,让我们亲手完成一个真实项目:为高通 SA8295P 车载芯片的 NPU(神经网络处理器)子系统,生成一份符合 ISO 26262 ASIL-B 等级要求的架构图。这个案例覆盖了 diagram-design 90% 的高频使用场景,所有步骤均可在 10 分钟内完成。
4.1 环境准备:零依赖启动
diagram-design 的最大优势是“开箱即用”。你不需要 Node.js、npm、Webpack——只需要一个浏览器和一个文本编辑器。
- 访问 GitHub Release 页面:https://github.com/diagram-design/diagram-design/releases
- 下载最新版
diagram-design-v2.4.1.zip(约 1.2MB) - 解压后,双击
index.html—— 没有服务器,没有端口,没有弹窗,直接进入编辑界面
实操心得:别用 VS Code Live Server 插件打开!它会注入
http://127.0.0.1:5500/前缀,导致@font-face加载本地字体失败。必须用file:///协议直接打开,这是保证出版级字体渲染正确的前提。
4.2 数据建模:用 JSON 定义 NPU 架构语义
SA8295P NPU 子系统包含:Tensor Core(张量计算单元)、DMA Engine(内存搬运引擎)、L2 Cache(二级缓存)、System Interconnect(片上总线)。我们新建npu-arch.json:
{ "metadata": { "title": "SA8295P NPU 子系统架构图", "subtitle": "符合 ISO 26262 ASIL-B 等级要求", "version": "1.0" }, "nodes": [ { "id": "tensor-core", "label": "Tensor Core", "type": "compute", "description": "支持 INT8/FP16 混合精度计算,峰值算力 32 TOPS", "asild": "B" }, { "id": "dma-engine", "label": "DMA Engine", "type": "io", "description": "支持 128-bit AXI 总线,带宽 102.4 GB/s", "asild": "B" }, { "id": "l2-cache", "label": "L2 Cache", "type": "memory", "description": "8MB 共享缓存,16-way associative", "asild": "B" }, { "id": "system-interconnect", "label": "System Interconnect", "type": "bus", "description": "AMBA CHI 协议,支持 QoS 优先级调度", "asild": "B" } ], "links": [ { "source": "tensor-core", "target": "l2-cache", "label": "Cache Coherency", "type": "coherent" }, { "source": "dma-engine", "target": "l2-cache", "label": "Memory Access", "type": "burst" }, { "source": "l2-cache", "target": "system-interconnect", "label": "System Bus", "type": "chi" } ] }关键点解析:
type字段使用compute/io/memory/bus四类,对应 diagram-design 内置的type_weight(compute=2.0,bus=1.5),确保 Tensor Core 自动居中;asild字段为每个节点标注安全等级,渲染器会自动添加红色边框(--color-asild-b: #dc2626);description字段内容不会显示在图上,但会写入 SVG 的<title>元素,供屏幕阅读器读取,满足无障碍访问要求。
4.3 主题定制:为车载芯片设计专属出版主题
车载芯片文档有特殊要求:必须使用等宽字体保证寄存器地址对齐,主色采用高通品牌蓝(#1E40AF),且需在 PDF 中嵌入字体。我们修改theme.css:
:root { --color-primary: #1E40AF; --color-secondary: #059669; --color-asild-b: #dc2626; --font-family: "JetBrains Mono", "Source Code Pro", monospace; --font-size-base: 14px; } /* 为 Tensor Core 节点定制样式 */ .node[data-type="compute"] { --color-fill: var(--color-primary); --color-stroke: #0c2d1e; } /* ASIL-B 节点边框 */ .node[data-asild="B"] { stroke-width: 2.5px !important; stroke: var(--color-asild-b) !important; } /* 打印时强制等宽字体,避免 PDF 字体替换 */ @media print { .node-label, .link-label { font-family: "JetBrains Mono" !important; } }实操心得:
JetBrains Mono是开源等宽字体,下载其 WOFF2 文件放入fonts/目录,并在index.html中添加:<style> @font-face { font-family: "JetBrains Mono"; src: url("fonts/JetBrainsMono-Regular.woff2") format("woff2"); font-weight: normal; font-style: normal; } </style>这样 PDF 导出时,
svg2pdf.js会自动嵌入该字体,彻底解决“客户 PDF 显示为宋体”的尴尬。
4.4 渲染与布局:让架构图自己“长”成专业模样
将npu-arch.json内容粘贴到 diagram-design 编辑器的 JSON 输入框,点击“Load Graph”。此时你会看到四个节点随机分布——别急,这是力导向算法的初始状态。
首次自动布局:点击右上角“Layout”按钮,算法开始迭代。观察控制台:
Iteration 127: Energy = 0.0032,当能量值低于0.005时自动停止。此时 Tensor Core(compute类型)已自然居中,System Interconnect(bus类型)位于底部,形成“计算-存储-总线”的垂直逻辑流。人工微调:拖拽
tensor-core节点至(300,200),dma-engine至(150,400),l2-cache至(450,400),system-interconnect至(300,600)。注意:拖拽后,节点 DOM 的transform属性会更新,但graph.json中的坐标不变——这正是“半自动布局”的精妙之处:算法定骨架,人工调细节。连接线优化:选中
tensor-core→l2-cache连线,点击“Edit Link”,将curvature从默认0.3调至0.6,使连线呈优雅弧线,避免与其他线交叉。所有调整实时反映在 SVG 源码中,<path d="M300,250 C350,300 400,300 450,350">。
4.5 出版级导出:生成可直接交付的交付物
点击“Export”按钮,选择四种格式:
SVG 导出:命名为
SA8295P-NPU-Architecture.svg,导入 Adobe Illustrator 后,可进一步添加车规级认证徽标、页眉页脚。SVG 中所有文字仍是可编辑文本,设计师可直接修改“Tensor Core”为“AI Accelerator”而不失真。PDF 导出:选择“A4 Landscape”,勾选“Embed Fonts”,生成
SA8295P-NPU-Architecture.pdf。用 Acrobat 打开,检查“文件 > 属性 > 字体”,确认JetBrainsMono-Regular已嵌入,且所有文字为“Embedded Subset”。PNG 导出:设置
Scale: 2x,Background: White,生成SA8295P-NPU-Architecture.png(3840×2160)。插入 Confluence 时,选择“Original Size”,确保 Retina 屏用户看到 1:1 像素。JSON 导出:保存为
npu-arch-v1.0.json,提交到公司 Git 仓库/architectures/npu/目录。CI 流程监听此目录,一旦有新 commit,自动触发npm run generate-pdf生成最新 PDF 并上传至内部 Wiki。
4.6 版本协同:用 Git 管理架构图演进
在团队协作中,npu-arch.json就是架构的“源代码”。我们模拟一次真实变更:
- 架构师提出:NPU 需增加
Safety Monitor模块,用于实时检测计算异常。 - 开发者编辑
npu-arch.json,在nodes数组末尾添加:{ "id": "safety-monitor", "label": "Safety Monitor", "type": "monitor", "description": "ASIL-D 等级监控模块,独立于 NPU 核心", "asild": "D" } - 添加连接:
"links": [{ "source": "safety-monitor", "target": "tensor-core", "label": "Watchdog" }] git add npu-arch.json && git commit -m "feat(npu): add ASIL-D safety monitor module"- CI 流程自动运行,生成新 PDF 并更新 Wiki。
此时,git log --oneline显示:
a1b2c3d feat(npu): add ASIL-D safety monitor module e4f5g6h chore(npu): update tensor core description for 32TOPS spec架构演进历史,比代码还清晰。
5. 常见问题与排查技巧实录:那些官方文档不会写的坑
在 17 个客户项目中,我们总结出 diagram-design 最常被问及的 6 类问题。这些问题的答案,往往藏在浏览器 DevTools 的一个隐藏面板里,或源于对 SVG 规范的细微误解。以下是真实排查记录,附带可复制的解决方案。
5.1 问题:中文标签显示为方块,但字体文件已正确加载
现象:index.html中@font-face指向fonts/SourceHanSansSC-Regular.woff2,文件存在且网络面板显示 200,但 SVG 中<text>仍显示“□□□”。
排查路径:
- 打开 DevTools → Elements 面板,找到
<text>元素 - 右键 → “Break on” → “attribute modifications”
- 刷新页面,断点停在
text.setAttribute('font-family', ...)行 - 查看
computedStyle:发现font-family计算值为"Source Han Sans SC", sans-serif,但font-family的引号被浏览器解析为字面量,而非字体名分隔符
根因:CSS 规范要求多词字体名必须用引号包裹,但 diagram-design 的渲染器在设置text.setAttribute('font-family', ...)时,未对字体名加引号。
解决方案:在theme.css中强制覆盖:
.node-label, .link-label { font-family: "Source Han Sans SC" !important; }注意:必须用
!important,因为渲染器内联样式优先级更高。这是 SVG 渲染器的一个已知限制,已在 v2.4.2 版本修复。
5.2 问题:导出 PDF 后,文字边缘有灰色晕影
现象:PDF 中所有文字周围出现 1px 灰色模糊边,像被 Photoshop 的“羽化”过。
排查路径:
- 用 Acrobat 打开 PDF → “视图 > 显示/隐藏 > 导航窗格 > 透明度”
- 发现文字图层启用了
Blend Mode: Normal,但底层有白色背景图层 - 检查
svg2pdf.js源码,发现其默认为<text>元素添加opacity: 0.99(规避某些 PDF 渲染器 bug)
根因:opacity: 0.99导致文字与白色背景混合,产生灰边。
解决方案:在导出前,临时修改 SVG:
// 导出前执行 const texts = document.querySelectorAll('text'); texts.forEach(t => t.style.opacity = '1');或在theme.css中全局重置:
text { opacity: 1 !important; }5.3 问题:力导向布局后,节点重叠严重,energy值不下降
现象:点击 Layout 后,控制台显示Iteration 1000: Energy = 12.456,节点挤成一团,算法不收敛。
排查路径:
- 检查
graph.json中nodes[].id:发现两个节点 ID 均为"npu-core"(复制粘贴错误) - diagram-design 的力导向算法将相同 ID 视为同一节点,导致位置冲突
根因:ID 重复违反 JSON Schema 的pattern: "^[a-z0-9-]+$",但校验器未开启严格模式。
解决方案:
- 启用严格校验:在
index.html中取消注释<!-- <script src="validator.js"></script> --> - 或手动检查:`grep '"id":' npu-arch.json | sort | uniq -d