☰
diagrams Custom 节点完全指南:从本地图标到远程图标的源码级拆解
2026/10/4 14:54:16 网站建设 项目流程

diagrams Custom 节点完全指南:从本地图标到远程图标的源码级拆解

【免费下载链接】diagrams:art: Diagram as Code for prototyping cloud system architectures项目地址: https://gitcode.com/GitHub_Trending/di/diagrams

内置图标库没有你要画的组件——自研网关、第三方消息队列——怎么把一张本地 PNG 塞进 diagrams 节点?答案就是diagrams Custom 节点,让你用自己的图标素材画出自定义架构图。本文把图标从传入到渲染的完整链路按源码拆一遍,看完可以放心使用。

三十秒跑通:一个最小可运行的 Custom 节点示例

先在运行目录下的icons/里放两张任意 PNG,再写脚本:

from diagrams import Diagram from diagrams.custom import Custom with Diagram("Local Custom Arch", show=False, filename="local_custom"): gw = Custom("API Gateway", "./icons/gateway.png") svc = Custom("Payment Service", "./icons/payment.png") gw >> svc

执行python local_custom.py,当前目录会生成local_custom.png。节点的唯一要求就是:把图片路径作为Custom的第二个参数传进去,路径相对于脚本运行目录。下图是官方本地图标示例(Creative Commons 许可证关系图)的渲染结果,完整脚本见 docs/nodes/custom.md,观感与上面脚本一致:每个节点一张图标加一行标签,箭头表示流向。

图标从传入到渲染的完整链路

Custom节点的图标要经过四站才落到最终 png 里:构造透传 →_load_icon原样返回 → 节点属性注入 →render()烘焙。按这个数据流方向走一遍。

构造阶段:icon_path 原样透传

Custom全类只有 20 行:

class Custom(Node): _provider = "custom" _type = "custom" _icon_dir = None fontcolor = "#ffffff" def _load_icon(self): return self._icon def __init__(self, label, icon_path, *args, **kwargs): self._icon = icon_path super().__init__(label, *args, **kwargs)

源码见 diagrams/custom/init.py。因为__init__只是把icon_path存进self._icon然后交给基类,路径没有任何复制和解析,所以你传什么它就用什么:相对路径最终由 Graphviz 按运行目录解释,文件不在则图标静默丢失。

_load_icon 覆写:路径不再进资源目录

内置节点(AWS、K8s 等)的图标全路径由框架从包内资源目录拼出来:

def _load_icon(self): basedir = Path(os.path.abspath(os.path.dirname(__file__))) return os.path.join(basedir.parent, self._icon_dir, self._icon)

见 diagrams/init.py,EC2之类的图标在类上只是EC2.png短名,靠basedir + _icon_dir拼全。而Custom把这个方法覆写成return self._icon,并把_icon_dir置为None。所以Custom节点返回的图标路径就是用户原样输入的值,可以指向磁盘上任意本地文件——这正是"自定义"二字的来源。

节点属性注入:图标变成 Graphviz 的 image 属性

基类Node.__init__里做了两件事。第一件是全局上下文检查(diagrams/init.py):

self._diagram = getdiagram() if self._diagram is None: raise EnvironmentError("Global diagrams context not set up")

这就是"节点必须建在with Diagram(...)块内"规则的出处。第二件是有图标时注入 Graphviz 节点属性(diagrams/init.py):

# Increase the height by the number of new lines included in the label. padding = 0.4 * (self.label.count('\n')) self._attrs = { "shape": "none", "height": str(self._height + padding), "image": self._load_icon(), } if self._icon else {} self._attrs.update(attrs)

逐条读:因为shape是none,节点没有边框,图片即节点本身;因为image的取值来自_load_icon(),图标路径在这一刻被固定并写进 dot 文件;因为节点基础高度是_height = 1.9(第 294 行),标签每出现一个\n高度再加 0.4,所以两行标签的节点会画得更高,文字不会压在图标上。

渲染阶段:render() 把图片烘进最终 png

图片文件并不是在构造节点时被读取的,真正"读图"发生在with Diagram(...)块退出时(diagrams/init.py):

def __exit__(self, exc_type, exc_value, traceback): self.render() # Remove the graphviz file leaving only the image. os.remove(self.filename) setdiagram(None)

render()按outformat调用 graphviz 绑定完成布局(第 194-199 行),Graphviz 此时才从 dot 文件里读出节点的image属性、加载本地 PNG 并烘进最终图片。所以图标只有一个硬性要求:with 块退出的那一刻,文件必须已经存在于磁盘上。

本地与远程图标的选型对比

维度本地图标远程图标适用场景
图标来源工作目录里现成的图片文件网络地址,先urlretrieve下载为本地文件再传给节点图标随仓库管理、离线开发
网络依赖无,纯本地文件读取下载时必须可达;Custom自身零网络代码图标托管在线上、临时引用
文件就位时点with块退出(render()触发)前存在即可必须在with块内、渲染前完成下载—
典型报错图标缺失:相对路径按运行目录解析不到文件文件不存在 / 下载失败:下载位置或时点不对—

两种模式的差别只在文件从哪来。因为Custom只认本地路径,所谓 diagrams 远程图标下载模式,本质是"先下载再走本地图标节点的路子",所有网络代码都在脚本侧。

本地图标的精简用法(路径相对、绝对均可):

# 离线场景:图标文件提前放进仓库或运行目录 Custom("离线组件", "/data/icons/offline.png") >> Custom("下游", "./icons/down.png")

远程图标的精简用法(下载必须发生在with块内、渲染前):

from urllib.request import urlretrieve urlretrieve("https://your-icon-host/icon.png", "icon.png") # 渲染前完成下载 Custom("在线组件", "icon.png")

官方远程示例(下载 OpenStack、Elastic 图标后绘制)的渲染效果:

Cluster 子图、列表扇出与内置节点混用

理解了加载链路,Custom在拓扑层面没有任何特殊之处——它和内置节点共享同一套Node运算符重载。下面四种组合是实际项目里最常用的。

用 Cluster 给一组 Custom 节点加框

意图:把几个自研组件在视觉上归为一组。

with Cluster("Consumers"): consumers = [Custom("w1", "./icons/w.png"), Custom("w2", "./icons/w.png")]

为什么这样连:节点构造时Node.__init__通过getcluster()拿到当前簇上下文,把节点写进簇的 dot(第 331-337 行);with块退出时Cluster.__exit__再调用subgraph()把整个子图挂到父图上(第 267-272 行)。框是"事后"画上去的,块内建的节点自动入框,块外引用变量照常连线。

一对多扇出:>> 右侧是列表

意图:一个队列喂多个 worker。

queue = Custom("queue", "./icons/queue.png") queue >> [Custom("w1", "./icons/w.png"), Custom("w2", "./icons/w.png"), Custom("w3", "./icons/w.png")]

为什么一条语句产生三条边:Node.__rshift__发现右操作数是列表时逐元素调用connect(),每个都带forward=True(第 366-369 行);返回值就是列表本身,所以链式表达还能继续往下接。列表扇出是 diagrams 里表达"一对多"的标准语法。

多对一聚合:>> 左侧是列表

意图:所有 worker 的结果汇入同一个数据库。

workers = [Custom("w1", "./icons/w.png"), Custom("w2", "./icons/w.png")] workers >> Custom("db", "./icons/db.png")

为什么能连上:列表自身没有>>运算符,Python 回退到Node.__rrshift__(第 389-397 行),对列表每个元素分别向右侧节点建边。扇出与聚合对称,queue >> workers >> db一个表达式就能表达"扇出-聚合"完整拓扑。

Custom 与 Pod / Aurora 混用

意图:broker 是自研组件没有内置图标,消费端和数据库用标准节点。

from diagrams.aws.database import Aurora from diagrams.k8s.compute import Pod queue = Custom("Message queue", "rabbitmq.png") queue >> [Pod("worker"), Pod("worker")] >> Aurora("Database")

为什么能随便混:Custom、Pod、Aurora都继承自Node,connect()统一通过全局 Diagram 上下文建边(第 414-427 行),边并不关心节点属于哪个 provider。这个拓扑加上Cluster包裹的完整版本在 docs/getting-started/examples.md 的 "R

【免费下载链接】diagrams:art: Diagram as Code for prototyping cloud system architectures项目地址: https://gitcode.com/GitHub_Trending/di/diagrams

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询