1. 项目缘起为什么做 diagram-design大概每个开发者桌面都堆过这样的文件架构图_final_v3.png、网络拓扑图_最终版2.png、流程示意图_new_new.png。你问同事某个模块怎么交互他甩过来一张快照图上还有几条线条和文本重叠在一起。更麻烦的是一周后需求变了原图早就丢了源文件只能重新画。diagram-design 这个项目就是想解决“画图一时爽改图火葬场”的问题。它定位于一张可复现、可维护、可版本化的图表设计系统只要一份代码就能生成结构清晰的架构图、流程图、时序图、部署拓扑图并且任何细节修改都在文本中完成再也不会出现“找不到源文件”的尴尬。这个项目适合谁三类人最需要它后端开发接口联调时画时序图给前端同事讲调用链。运维与架构师交付机器部署拓扑标注服务依赖关系。技术文档写作者把复杂的业务链路转化为可读性强的图形直接嵌入文档或幻灯片。我最初写这套设计系统纯属被一次惨痛经历逼出来的某次架构评审我花了两个小时在画图工具里拖拽连线评审现场数据流画错了三条当场被问住。后来我意识到用文本描述图再用程序自动布局才是真正可靠的路。这也正是 diagram-design 的核心思路。2. 方案选型为什么放弃拖拽绘图2.1 拖拽绘图的两大致命伤很多人的第一反应是现成画图工具不香吗有免费版模板也多为什么还要自己写一套我这些年用过不少主流绘图工具它们都有两个绕不开的痛点。第一是排版成本高一个节点一个节点拖一条线一条线拉遇到复杂系统动辄上百个节点光是摆位置就耗掉半天。第二是协作和版本跟踪几乎为零图存在云端改了之后没有 diff没有代码评审两个人同时编辑还容易互相覆盖。我在某个系统交付时客户要求把部署拓扑图改八个版本。前三次我还耐心拖拽到第五次我直接把桌面上的鼠标甩了。那一刻我下定决心必须用文本来描述图。2.2 文本描述图的优势这里要理解一个类比图片就像编译后的二进制程序而文本代码才是源代码。任何图表本质上都是“节点 关系 样式”的集合。只要用结构化文本描述这些要素再交给渲染器执行布局算法图就自动生成。diagram-design 选型遵循四条原则纯文本源文件Git 可以 diff、可以回滚、可以多人合并。自动布局为主手工微调为辅绝大多数场景不需要操心坐标。渲染独立可以输出 PNG、SVG 甚至终端字符画便于嵌入各类平台。领域中立既能画技术架构图也能画业务流程图、组织架构图、泳道图。语言选型上我用了 Python。原因是生态成熟绘图库丰富而且团队里大多数人都会 Python后续要接入 CI 生成文档图非常方便。底层渲染我封装了一个轻量布局引擎重点实现树形布局、分层布局和力导向布局三种模式。2.3 和主流方案的对比我用一张表把常见方案和 diagram-design 做了对比你会明白差异点在哪里方案源文件管理自动布局二次开发适合场景传统画图工具二进制难对比弱手动为主不开放快速画个草图Markdown 内嵌图文本可管理无有限简单时序图专业绘图软件私有格式中受限复杂美工图diagram-design结构化文本强内置多种算法开放 Python API工程图、持续集成出图从表里能看出diagram-design 不是要替代精美的手工绘图工具而是主攻工程场景图要被频繁修改、要走评审流程、要自动生成、要可靠复现。这几个诉求恰恰是拖拽工具最薄弱的环节。3. 核心设计语言怎么定义一张图3.1 节点、关系和样式的抽象一套图表设计系统的核心首先是定义领域语言。diagram-design 把一切图表元素抽象成三层第一层是节点。节点可以是矩形、圆角矩形、圆形、菱形对应着不同的语义。比如菱形表示判断圆角矩形表示子系统圆形表示外部实体。每个节点要有唯一 ID这样关系才能指向它。第二层是关系。关系描述节点之间的连接包括方向、线型、标注文字。比如实线表示数据流虚线表示依赖粗线表示主链路线上还可以附加请求参数、返回结果等文字。第三层是样式。样式是节点的视觉属性包括填充色、边框、字体、字号、图标。样式需要支持继承和覆盖否则每个节点都写一遍颜色会把人逼疯。我用一段配置来展示这三层如何组合node gw { label: API Gateway shape: rounded_box style: primary } node auth { label: Auth Service shape: rect style: secondary } edge gw - auth { label: POST /verify line: solid style: data_flow }这段代码定义了两个节点和一条边。渲染器读入后会自动计算出合理的位置画出网关指向认证服务的箭头并带上标签文字。3.2 布局引擎自动排版的底层逻辑定义好节点和关系只是第一步真正让使用者“少操心坐标”的是布局引擎。diagram-design 内置了三种常用布局算法。树形布局用于组织架构、继承体系、目录结构。它的逻辑是确定根节点位置然后按层递归放置子节点。核心计算是每一层节点的高度累加和宽度累加确保同一层节点水平间隔一致。实际算法里我会对每个子树计算轮廓宽度然后通过回溯调整父节点位置使其居于子节点正中间。分层布局用于流程、有向无环图。它先把节点按拓扑排序分配到不同层级再在同一层内调整顺序减少边交叉。这个算法是工程绘图里最常用的。分层流程中最容易踩坑的点是环路我处理的方式是检测环并报错因为流程图中出现环往往意味着逻辑死循环应该先修复业务语义再说。力导向布局用于系统拓扑、关系网。原理是把节点当作带电粒子方向相同的力排斥连边当作弹簧相互吸引经过若干轮迭代系统趋于稳定。这种布局适合没有明确层级的关系展示比如微服务调用链、集群节点间的关系。三种布局代码上的层次关系可以这样理解我封装了一个布局基类每种布局都接收节点列表和边列表最后产出一个坐标字典。渲染时不做任何布局计算纯粹消费坐标这就保证了后续如果引入更酷的布局算法渲染代码完全不用动。3.3 样式系统与主题化的辩证关系一开始我发现一个尴尬的问题代码生成的图不好看总有一种“一眼程序员的丑”。后来我意识到问题出在样式设计缺失。diagram-design 采用主题分层方式。基础主题定义一套默认的字体、配色、间距、圆角例如浅色主题和深色主题。每个节点可用命名样式引用主题内的变量比如style: primary就自动取主题的主色和强调边框。如果某个节点需要特殊颜色再局部覆盖。这里我建议使用者在项目初期就定好主题规范。最有效的做法是底色不超过三种、边框统一粗细、同一类节点形状一致。这和前端设计系统的道理完全相通颜色泛滥是工程图丑化的头号原因。举个例子某个数据中心拓扑图里我规定了四类颜色核心交换机用主色、防火墙用警示色、应用服务器用中性色、外部用户用浅灰色。一张图看起来立刻有层次感评审会上大家注意力在逻辑上而不是被花里胡哨的装饰分散掉。4. 实操过程从零到完整架构图4.1 环境准备与安装diagram-design 作为一个 Python 项目安装非常简单。建议在虚拟环境里进行避免污染全局 Python 环境。python3 -m venv venv source venv/bin/activate pip install diagram-design安装完成后可以用命令行工具初始化一个空白项目diagram init my_project cd my_project这个命令会生成标准的目录结构我解释一下各部分作用my_project/ ├── diagrams/ # 放置 .dd 源文件 ├── styles/ # 主题与样式文件 ├── output/ # 生成图片输出 └── config.yaml # 全局配置项目落地后把源文件放进diagrams目录在config.yaml指定输出格式和布局算法就能完成第一次出图。4.2 画一张真实的订单系统架构图理论讲多了容易飘直接上手画一张图。我以电商订单系统为例整个流程包含前端、网关、订单服务、库存服务、支付服务和消息队列。先定义节点node web { label: Web Client shape: rect } node gw { label: API Gateway shape: rounded_box } node order_svc { label: Order Service shape: rounded_box } node stock_svc { label: Stock Service shape: rect } node pay_svc { label: Payment Service shape: rect } node mq { label: Message Queue shape: cylinder }然后定义关系edge web - gw { label: HTTPS } edge gw - order_svc { label: Create Order } edge order_svc - stock_svc { label: Deduct Stock } edge order_svc - pay_svc { label: Request Pay } edge order_svc - mq { label: Publish Event }默认配置下这张图会采用分层布局。用户访问从左边开始网关在中间层三个业务服务和消息队列在同一列。整个图的结构一眼就能看清订单服务是核心它同时依赖库存、支付和消息队列三个下游。如果想要不同的视觉重点可以启用不同布局layout: layered direction: LR theme: lightdirection: LR表示从左到右渲染TB则改为从上到下。我个人的经验是涉及外部用户访问、网关、后端链路时用 LR一眼能看到数据流的路径涉及组织层级、分解结构时用 TB 更舒服。4.3 生成与导出源文件写好后执行diagram render diagrams/order_system.dd -o output/order_system.png系统会先解析文本构建内部的对象模型再执行布局算法最后调用渲染后端生成图片。如果画布上有节点位置重叠渲染器会自动做碰撞检测并微调防止文字互相遮挡。除了 PNG还支持 SVG。SVG 的额外好处是保留文本信息可以嵌入网页后选中文字甚至为之做动画。文档系统里我推荐输出 SVG而对外分享或打印时输出高分辨率 PNG。有一个小功能非常实用--watch模式。加上这个参数后源文件一旦保存变更图片自动重出。写文档时开着diagram render diagrams/xxx.dd -o output/xxx.svg --watch一边改一边看到效果体验上接近拖拽工具的即时反馈但源文件始终是文本。4.4 自定义主题与样式刚才提过样式系统这里给一个可复用的自定义主题示例。在styles/custom.yaml中写入theme: name: company_blue background: #ffffff font_family: Noto Sans SC font_size: 14 colors: primary: #1e56a0 secondary: #c6d7eb warning: #d9534f neutral: #f0f0f0 shape: rect: fill: {colors.neutral} border: {colors.primary} border_width: 2 corner_radius: 4然后在config.yaml中引用theme_file: styles/custom.yaml这样全局所有节点都会套用该主题不需要在每一个节点上重复写样式。实际项目中我将公司 Logo 的蓝白色固定为主题出图后直接用于对外文档观感统一严重提高了汇报材料的专业度。5. 实战案例绘制微服务调用链拓扑5.1 问题背景与拆解一个微服务系统服务数量一旦超过十几个人脑几乎无法靠文档记住调用链路。某次故障排查时我发现线上服务 A 调用服务 BB 又回调 A形成了循环依赖而架构文档上完全看不出来。这种问题的解法是从注册中心和链路追踪系统拉取真实调用关系自动生成拓扑图周期性更新。diagram-design 在这里的价值不只是画图而是把可视化变成一种监控手段。我从监控平台导出了 CSV 格式的服务调用数据字段包括调用方、被调用方、调用次数、平均耗时。预处理之后变成内部关系列表。这里的关键步骤是数据清洗。由于监控数据量极大很多调用是低频噪声我会设置一个阈值每分钟低于 5 次、耗时低于 10ms 的调用忽略不计。这样可以排除健康检查、心跳探测这类干扰边让图更干净。5.2 自动生成拓扑图清洗后的数据直接传入 diagram-design 的 Python APIfrom diagram_design import Diagram, Edge, Node dia Diagram() for service in services: dia.add_node(Node(service[name], shaperect)) for call in calls: dia.add_edge( Edge(call[from], call[to], labelf{call[count]}次 {call[avg_ms]}ms) ) dia.render(output/microservice_topology.png, layoutforce)采用力导向布局因为服务调用关系没有天然层级力导向能把调用频繁的服务聚在一起形成自然的簇。这样从图中一眼能看出哪些服务是核心枢纽哪些服务是边缘模块。渲染完成后我在原图上做了两处标注把调用量超过 1000 次/分钟的边加粗标红把存在循环依赖的两组服务用虚线红圈框起来。这份图直接附在故障复盘报告里比写一千字文字说明都直观。5.3 定时任务与持续更新由于服务架构会持续演进手动重新出图显然不够。我加了一个定时任务每天凌晨从监控接口拉取数据重新绘图然后推送到内部文档站。图始终保持当天的最新状态。这里推荐使用 CI 的定时任务而不要依赖开发机。开发机一关图就断了CI 则能保证稳定执行。实践中我设计了一个三步流水线# 第一步拉取服务调用数据 python scripts/fetch_call_data.py # 第二步生成拓扑图 python scripts/render_topology.py # 第三步推送到文档站 python scripts/upload_docs.py整个过程没有任何人工干预。后来团队反馈这份自动更新的拓扑图成了排查线上问题时的第一参考。6. 常见问题与排查技巧实录6.1 节点重叠或文字遮挡这是自动布局中遇到最多的视觉效果问题。文字比节点本身还宽边上的 label 又挤在中间。排查步骤先从布局算法入手。如果是分层布局改为 TB 方向后能显著改善多层节点的横向拥挤如果节点个数特别多可以调大一倍画布尺寸canvas: scale: 2.0另外节点内的文字长度是决定性因素。我把所有节点 label 控制在 20 个字符以内超出部分换行或用缩写。这个约束从源头上避免了很多布局问题。如果确实要表达很长的描述不要放在节点内部用note元素挂在节点旁边node order_svc { note: 订单服务负责创建订单、价格计算、状态流转 }note会渲染在节点右侧的小卡片上不影响主体布局。6.2 中文字体显示成方块在 Linux 服务器环境里经常出现这个情况根源是系统没有安装中文字体。最省事的解决方案apt install fonts-noto-cjk或者把字体文件统一放到~/.fonts目录并执行fc-cache -f。配置文件中指定字体族font_family: Noto Sans CJK SC这个坑几乎每个在 CI 环境用过的人都会踩到提前在部署检查清单上加上“字体依赖”这一项能省很多折腾时间。6.3 生成物出现多余的空白区域画布尺寸自动计算时如果布局算法中最后一个节点的坐标偏大画布会被拉伸得很空旷。解决办法是限制画布最大尺寸或者手动设置输出区域。canvas: width: 1920 height: 1080 fit: truefit: true会让渲染器自动把所有节点缩放到画布合适范围内不至于因为个别节点位置偏移而出现大量留白。6.4 关系横跨两个区域导致乱线分层布局中两个相距很远的节点之间存在边就会画出很长的连线把整张图搅乱。这种情况先检查边界分层是否合理其次可以设置忽略低频边render: max_edge_cross_depth: 3这一步限制长距离边的显示层级超出阈值的边隐藏并用汇总节点表示。实践中大幅提高了大图的整洁度。6.5 源文件写错导致解析报错语法报错信息如果不够友好会让人烦躁。diagram-design 的解析器会在报错时给出行列号并高亮当前 token。我建议配合编辑器插件做好语法提示。以下是常见的三种错误Edge 引用了不存在的 Node ID检查边的起点和终点在节点定义中是否存在ID 区分大小写。Shape 类型不支持查看当前版本支持的 shape 列表不要写成rounded_rectangle_very_smooth直接使用rounded_box。样式文件缺失变量自定义主题引用了未定义的变量比如{colors.primary}但你的colors段里没有primary键。检查 YAML 缩进和变量名。7. 进阶技巧让图从“能看”到“好用”7.1 分组与泳道当图包含多个子系统或团队职责边界时分组元素非常关键。diagram-design 支持group关键字group Frontend { node web node app } group Backend { node order_svc node pay_svc }渲染时分组会画一个浅色的背景容器把子节点包在里面。这比单纯给节点加前缀更直观因为视觉上的“归属感”更强。泳道图则适合业务流转场景。可以把不同部门或不同处理阶段放在垂直泳道中节点按照所属泳道摆放关系线在泳道之间穿梭一眼看出流程交给谁了。7.2 条件分支与判断标注流程图中难免有判断逻辑。diagram-design 提供菱形节点和边标签组合node is_vip { label: VIP用户? shape: diamond } edge is_vip - normal_queue { label: 否 } edge is_vip - vip_queue { label: 是 }有了“是/否”标签分支的语义非常清晰。我建议所有分支边都必须写标签否则看的人还要猜失去了流程图的意义。7.3 与文档系统集成实际使用中文档系统集成是高频场景。我通常的做法是CI 里生成 SVG然后直接嵌入 Markdown![订单系统架构图](output/order_system.svg)SVG 在大多数现代文档站都能正常显示而且无限放大不模糊。更进阶的方案是配置自动生成目录所有diagrams/*.dd文件有变更后自动更新文档页长期维护时永远不用担心“文档上的图过时了”。7.4 错误图诊断把图当作调试工具我曾经接到一个现场反馈接口偶发超时但日志没有明显报错。我用 diagram-design 画出了请求链路上每个中间件的连接关系然后标出每一跳的平均耗时。当耗时标注出现在图上后问题立刻定位到一个老旧的网关转发逻辑。这个案例说明了 diagram-design 的另一种用法把数据映射到图上图就是诊断仪表盘。它集合了关系、数据、异常标注视觉化后的信息密度远高于表格。8. 项目沉淀与后续扩展8.1 实际使用中的体会diagram-design 用了一段时间后我对“画图”这件事有了新的理解。一个成熟的制图流程应该像写代码一样具备可测试性。过去我们评审图只能盯着像素看现在则可以对源文件做代码评审。节点命名是否规范、边标签是否清晰、是否有孤立节点这些都可以作为评审项进入团队的质量门禁。有一次评审会上新同学提交的架构图有 37 个节点但有三条边完全没有标签被我在评审意见里直接打回。这种级别的纠错在传统绘图工具下几乎无法自动化实现。8.2 遇过的坑与改进方向这套系统不是没有问题。最让我头疼的是复杂场景布局质量还不够智能。遇到上百个节点且关系密集的图力导向布局容易震荡需要手工调数次参数才能稳定。后续我打算引入更现代的图布局算法并把社区流行的图形规范吸收进来争取做到更少的人工干预。另外交互方面目前只做到了“快速预览”还没有做到画布上的点击选中、拖拽微调。对部分追求像素级控制的用户来说还是希望看到图之后能用鼠标手动拉一拉节点位置。8.3 给初学者的建议如果你想基于这个思路做一套自己的图设计系统我的建议是先不要着急扩展语法。把最核心的节点、关系、样式、布局四件事做好就足以覆盖大多数业务场景。每新增一个特性都会增加解析器和渲染器的复杂度维护成本是指数上涨的。最后分享一个小技巧演示之前永远先渲染一次。很多时候我们在终端里看着源文件信心满满结果演示的时候图片还停留在上一次的缓存。把这个动作固化为肌肉记忆进入文档目录、执行渲染命令、再打开图片。这套流程顺了一切都会顺手很多。