diagrams 中 Custom 节点深度实践:用本地图标与远程图标构建自定义架构图
本文为 diagrams 项目中「Custom 自定义节点」能力的技术指南,基于官方文档 docs/nodes/custom.md 并结合 diagrams/custom/init.py 的源码实现展开。读完本文,你将掌握如何在 diagrams 中引用本地或远程下载的图片作为节点图标,理解 Custom 类如何覆写图标加载逻辑并注入 Graphviz 节点属性,以及如何在 Cluster、列表扇出等组合模式下产出可复现的自定义架构图。
Custom 节点是什么:源码视角的能力定位
Custom 是 diagrams 提供的一个基础节点类,其设计目标在模块 docstring 中写得很明确:允许加载一张图片作为节点呈现,从而弥补内置云厂商节点图标库没有覆盖到的组件。完整实现见 diagrams/custom/init.py:
"""
Custom provides the possibility of load an image to be presented as a node.
"""
from diagrams import Node
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)
与内置节点(如 AWS、GCP 的节点)相比,Custom 有两处关键差异:
-
图标路径直接透传。基类
Node._load_icon()会把图标拼接到包内的resources/资源目录下(见 diagrams/init.py):def _load_icon(self): basedir = Path(os.path.abspath(os.path.dirname(__file__))) return os.path.join(basedir.parent, self._icon_dir, self._icon)而
Custom覆写了该方法,直接返回构造时传入的icon_path(return self._icon),并将_icon_dir置为None。因此你传给Custom的任意本地路径(相对或绝对)都会被原样交给 Graphviz 使用,这也是本地图标必须相对于「脚本运行目录」存在的原因。 -
节点属性注入。在
Node.__init__中(diagrams/init.py),只要节点带有图标,就会附加如下属性:# If a node has an icon, increase the height slightly to avoid # that label being spanned between icon image and white space. # 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 {}节点基础高度为
_height = 1.9,标签中每出现一个换行\n高度额外增加 0.4,避免图标与文字重叠——这就是为什么文档示例中的标签普遍使用"\n"换行(如"Adaptations must be shared\n under the same terms")。
此外,Node.__init__ 会在 diagrams/init.py 中检查全局 Diagram 上下文,Custom 同样必须创建在 with Diagram(...) 块内,否则抛出 EnvironmentError("Global diagrams context not set up")。连接能力则来自 Node 上重载的操作符(diagrams/init.py):
node >> other:前向有向边(forward=True);node - other:无方向边;- 右侧为列表时(如
cc_heart >> non_commercial)会对列表中每个节点分别建边,即「一对多扇出」; - 列表左侧同理支持
non_commercial >> x的一对一聚合。
边(Edge)的方向属性由 attrs 属性映射为 Graphviz 的 dir(forward/back/both/none),见 diagrams/init.py。
模式一:使用本地图片图标(Custom with local icons)
官方文档给出的本地图标场景是一个「Creative Commons 许可证关系图」。假设工作目录结构如下(与 docs/nodes/custom.md 一致):
.
├── custom_local.py
├── my_resources
│ ├── cc_heart.black.png
│ ├── cc_attribution.png
│ ├──...
custom_local.py 的完整内容为:
from diagrams import Diagram, Cluster
from diagrams.custom import Custom
with Diagram("Custom with local icons\n Can be downloaded here: \nhttps://creativecommons.org/about/downloads/", show=False, filename="custom_local", direction="LR"):
cc_heart = Custom("Creative Commons", "./my_resources/cc_heart.black.png")
cc_attribution = Custom("Credit must be given to the creator", "./my_resources/cc_attribution.png")
cc_sa = Custom("Adaptations must be shared\n under the same terms", "./my_resources/cc_sa.png")
cc_nd = Custom("No derivatives or adaptations\n of the work are permitted", "./my_resources/cc_nd.png")
cc_zero = Custom("Public Domain Dedication", "./my_resources/cc_zero.png")
with Cluster("Non Commercial"):
non_commercial = [Custom("Y", "./my_resources/cc_nc-jp.png") - Custom("E", "./my_resources/cc_nc-eu.png") - Custom("S", "./my_resources/cc_nc.png")]
cc_heart >> cc_attribution
cc_heart >> non_commercial
cc_heart >> cc_sa
cc_heart >> cc_nd
cc_heart >> cc_zero
要点解析:
Diagram关键参数:show=False表示渲染后只保存不打开预览;filename="custom_local"指定输出文件名为custom_local.png(不写扩展名,默认格式为 png);direction="LR"指定从左到右的布局方向。合法取值在 diagrams/init.py 中定义:方向为TB/BT/LR/RL,输出格式为png/jpg/svg/pdf/dot,非法值会抛出ValueError(diagrams/init.py)。- 图标题即
name:示例把一段含 URL 的多行文本作为图标题,说明name只是 Graphviz 的图级label(见 diagrams/init.py),可承载任何提示文本。 Custom(label, icon_path)签名:第一个参数是节点标签,第二个参数是图片路径;由源码可见路径会原样写入节点image属性,因此必须保证 Graphviz 渲染时该文件可达。- Cluster 内链式节点:
with Cluster("Non Commercial"):块内用-将三个单字母节点串联,并用列表变量non_commercial承接,外层再通过cc_heart >> non_commercial一条语句完成扇出。Cluster在退出with块时以 subgraph 形式挂到父图上(diagrams/init.py)。 - 渲染时机:图片并不在创建节点时读取,而是在
Diagram.__exit__中调用render()交给 Graphviz 生成图片(diagrams/init.py)。所以只要图片文件在with块结束前存在于磁盘上即可。
运行 python custom_local.py 后即得到文档中展示的渲染结果:
模式二:使用远程图片图标(Custom with remote icons)
当图标托管在网络上、且在生成图表时可访问时,可以在脚本中先把图片下载到本地再交给 Custom。文档给出的完整示例(节选自 docs/nodes/custom.md):
from diagrams import Diagram, Cluster
from diagrams.custom import Custom
from urllib.request import urlretrieve
with Diagram("Custom with remote icons", show=False, filename="custom_remote", direction="LR"):
# download the icon image file
diagrams_url = "https://github.com/mingrammer/diagrams/raw/master/assets/img/diagrams.png"
diagrams_icon = "diagrams.png"
urlretrieve(diagrams_url, diagrams_icon)
diagrams = Custom("Diagrams", diagrams_icon)
with Cluster("Some Providers"):
openstack_url = "https://github.com/mingrammer/diagrams/raw/master/resources/openstack/openstack.png"
openstack_icon = "openstack.png"
urlretrieve(openstack_url, openstack_icon)
openstack = Custom("OpenStack", openstack_icon)
elastic_url = "https://github.com/mingrammer/diagrams/raw/master/resources/elastic/saas/elastic.png"
elastic_icon = "elastic.png"
urlretrieve(elastic_url, elastic_icon)
elastic = Custom("Elastic", elastic_icon)
diagrams >> openstack
diagrams >> elastic
该模式有几个值得注意的实现细节:
- 下载必须在渲染之前完成。从源码看,
urlretrieve写在with Diagram(...)块内、render()触发之前,此时磁盘文件已就位,Graphviz 才能通过节点image属性找到它。若在with块结束后才下载,渲染时文件尚不存在,输出会缺少图标。 - 下载的只是「本地化」手段。
Custom本身并不发起网络请求——它只是把urlretrieve保存下来的本地文件名传给 Graphviz。这一结论可直接从 diagrams/custom/init.py 得到:类中没有任何网络相关代码。 - 混合嵌套:示例把两个远程图标节点放入
Cluster("Some Providers"),并让簇外的diagrams节点分别指向它们,展示了簇内/簇间连线的组合方式(Node.connect始终通过全局 Diagram 上下文建边,见 diagrams/init.py)。
渲染结果如下:
进阶组合:Custom 节点与内置节点混用
官方文档末尾指向的另一个示例(现位于仓库内 docs/getting-started/examples.md 的 "RabbitMQ Consumers with Custom Nodes" 一节)演示了 Custom 与 Kubernetes、AWS 内置节点混用的典型做法:
from urllib.request import urlretrieve
from diagrams import Cluster, Diagram
from diagrams.aws.database import Aurora
from diagrams.custom import Custom
from diagrams.k8s.compute import Pod
# Download an image to be used into a Custom Node class
rabbitmq_url = "https://jpadilla.github.io/rabbitmqapp/assets/img/icon.png"
rabbitmq_icon = "rabbitmq.png"
urlretrieve(rabbitmq_url, rabbitmq_icon)
with Diagram("Broker Consumers", show=False):
with Cluster("Consumers"):
consumers = [
Pod("worker"),
Pod("worker"),
Pod("worker")]
queue = Custom("Message queue", rabbitmq_icon)
queue >> consumers >> Aurora("Database")
这个例子说明了三件实事:其一,Custom 与 Pod、Aurora 一样继承自 Node,可以无缝参与 >>、- 等运算符链;其二,queue >> consumers 借助列表扇出一次连出三条边,consumers >> Aurora 再聚合连回数据库,一条表达式完成「扇出-聚合」拓扑;其三,urlretrieve 也可以在 with 块之外提前下载,只要文件在渲染前就位即可。对应的渲染效果可参考仓库中的 rabbitmq_consumers_diagram.png。
环境前提、参数速查与常见问题
运行环境。按 docs/getting-started/installation.md 的说明,diagrams 需要 Python 3.7 以上(当前仓库 pyproject.toml 中 Poetry 声明为 python = "^3.9",且依赖 graphviz >=0.13.2,<0.21.0、jinja2),并且必须先安装 Graphviz 系统程序(macOS 可用 Homebrew、Windows 可用 Chocolatey 安装),随后通过 pip install diagrams 或 poetry add diagrams 安装库本体。本文所有示例均以「Graphviz 已安装 + 库已安装」为前提。
Diagram 与 Custom 相关参数速查:
| 参数 | 位置 | 取值/默认值 | 说明 |
|---|---|---|---|
name |
Diagram |
字符串,默认 "" |
图标题;未给 filename 时会用它生成文件名(空格转下划线、转小写,见 diagrams/init.py) |
filename |
Diagram |
无扩展名,默认由 name 生成 |
输出文件名,如 custom_local 生成 custom_local.png |
direction |
Diagram |
TB/BT/LR/RL,默认 LR |
数据流方向,映射为 Graphviz rankdir |
show |
Diagram |
默认 True |
False 时仅保存不打开图像 |
outformat |
Diagram |
png/jpg/svg/pdf/dot,默认 png,支持列表 |
多格式输出时逐个渲染(diagrams/init.py) |
label |
Custom |
字符串 | 节点标签,支持 \n 换行,每换行一次节点高度 +0.4 |
icon_path |
Custom |
本地图片路径 | 原样作为 Graphviz 节点 image 属性,相对路径相对于运行目录 |
常见问题排查:
- 图标不显示:先确认
icon_path相对于「脚本执行目录」而非脚本文件位置是否可达(Custom不会做任何路径修正,直接透传给 Graphviz)。 - 渲染报
EnvironmentError: Global diagrams context not set up:Custom(...)必须在with Diagram(...)块内创建。 - 布局方向报错
ValueError: "xxx" is not a valid direction:direction只接受TB/BT/LR/RL四种取值。 - 生成后目录中找不到
.dot文件:属预期行为,Diagram.__exit__在渲染完成后会删除中间 Graphviz 文件,仅保留图片(diagrams/init.py)。
小结
Custom 节点是 diagrams「Diagram as Code」体系中扩展图标库的标准入口:它通过覆写 _load_icon 把用户提供的图片路径直接注入 Graphviz 节点,配合 Node 的操作符语法与 Cluster 子图机制,既能绘制纯自定义组件关系图(本地图标模式),也能把网络上托管的图标临时下载后混入标准云架构图(远程图标模式)。实现入口在 diagrams/custom/init.py,上下文、节点与边的核心逻辑在 diagrams/init.py,更多组合用法可继续参考 docs/getting-started/examples.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

