PlantUML 渲染引擎内幕:Graphviz/dot 算法的 7 个调优点

puml.online

PlantUML 大多数图(组件、状态、对象、类)底层都走 Graphviz dot 布局算法。理解 dot 的工作机制能解释很多「图为什么自动排成这样」的疑问,以及怎么改才更好看。

dot 算法是什么

PlantUUM 渲染 component class state 时,先用内置解析器把 .puml 转成 Graphviz 的 dot 语言,再用 dot 引擎算布局。最终输出的 SVG 是 dot 算出来的位置 + PlantUML 自己的样式表。

这意味着:你能用 dot 的所有特性影响 PlantUML 图。PlantUML 把它们包装成 skinparam !pragma 等指令,但理解底层有助于诊断奇怪布局。

调优点 1:rankdir 改方向

1
2
3
4
5
6
@startuml
left to right direction
component A
component B
A --> B
@enduml

底层 dot:

1
2
3
4
digraph {
rankdir=LR
A -> B
}

rankdir 可选值:LR(左到右)、TB(默认,上到下)、BTRL

何时用 left to right:

  • 时序图横向太长 → 改 top to bottom(默认)
  • 部署图区域多 → left to right 把多个 zone 横排

调优点 2:nodesep / ranksep 控制间距

1
2
3
4
5
6
7
8
9
@startuml
skinparam nodesep 30
skinparam ranksep 50
component A
component B
component C
A --> B
B --> C
@enduml

nodesep:同一 rank(同一行/列)节点之间的间距。默认 40(像素)。节点太挤 → 加大。

ranksep:不同 rank(行/列)之间的间距。默认 60。节点层级不清楚 → 加大。

调优点 3:隐形节点控布局

1
2
3
4
5
6
7
8
9
10
11
12
@startuml
component A
component B
component C
A --> B
A --> C

' 隐形节点强制 B 和 C 在同一 rank
A --> (hub)
(hub) --> B
(hub) --> C
@enduml

(hub) 是匿名节点,dot 把它当真实节点算 rank。把多个目标拉到同一 rank。

实战用法:架构图里多个 service 都连同一个 DB,DB 自然下沉一级——加隐形节点让 DB 居中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
component "auth-svc" as auth
component "order-svc" as order
component "pay-svc" as pay

database "PostgreSQL" as pg

auth --> (auth_pg) : SQL
order --> (order_pg) : SQL
pay --> (pay_pg) : SQL

(auth_pg) --> pg
(order_pg) --> pg
(pay_pg) --> pg
@enduml

调优点 4:hidden 边对齐

1
2
3
4
5
6
7
@startuml
component A
component B
component C
A --> B
A -[hidden]- C
@enduml

-[hidden]- 是 dot 的特殊边类型,只用于布局,不影响图渲染——A 和 C 会被 dot 拉到同一 rank。

实战:组件图里两个 service 没有直接关系但要横排:

1
2
3
4
5
6
7
8
component "Web App" as web
component "API Gateway" as gw
component "Admin Tool" as admin

web --> gw
admin --> gw
web -[hidden]- admin
@enduml

Web App 和 Admin Tool 横排对齐。

调优点 5:子图分组(package / node)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@startuml
package "Frontend" {
component "Web" as web
component "Mobile" as mob
}

package "Backend" {
component "API" as api
component "Worker" as wkr
}

web --> api
mob --> api
api --> wkr
@enduml

dot 把 packagesubgraph 处理,自动绘制包围盒 + 内部节点紧凑布局

注意:子图不影响连线方向,只是视觉分组。

调优点 6:cluster 嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
@startuml
node "VPC" {
node "Public Subnet" {
component "ALB" as alb
}
node "Private Subnet" {
component "App" as app
database "DB" as db
}
}
alb --> app
app --> db
@enduml

嵌套 node 生成嵌套 cluster,AWS 架构图经典用法

调优点 7:边的权重 weight

1
2
3
4
5
6
7
8
9
10
@startuml
component A
component B
component C
component D

A --> B : "1 (default)"
A --> C : "weight 10"
A --> D : "weight 5"
@enduml

dot 算法中边有「权重」——权重高的边优先直连(同 rank 内对齐),权重低的边绕路。weight 10 强制 A 和 C 同 rank 对齐。

实战:架构图里关键路径走直线,辅助路径绕路:

1
2
3
4
5
6
7
8
9
component "User" as user
component "API" as api
component "Cache" as cache
component "DB" as db

user --> api : "weight 10"
api --> db : "weight 10"
api --> cache : "weight 1"
@enduml

User → API → DB 主路径走直线,API → Cache 支路径绕开。

怎么 debug 奇怪布局

方法 1:导出 dot 文件手动看

PlantUML CLI:

1
2
plantuml -tlog diagram.puml
# 输出 INFO 输出实际生成的 dot 代码

更详细:

1
2
3
plantuml -tdot diagram.puml
# 输出 .dot 文件,可用 Graphviz 直接渲染
dot -Tsvg diagram.dot -o diagram-from-dot.svg

对比 plantuml 输出和 dot 输出——如果 dot 自己布局怪,问题在 dot 算法不在 PlantUML。

方法 2:-stdlib 简化

1
plantuml -tsvg -Sstdlib=true diagram.puml

-Sstdlib=true 关掉 PlantUML 的 stdlib 主题,看默认 dot 布局什么样。

方法 3:加 !pragma layout 指令

1
2
3
4
@startuml
!pragma layout elk
' 或 !pragma layout neato
@enduml

PlantUUM 1.2023+ 支持 elkneato 引擎。elk(Eclipse Layout Kernel)对复杂图布局更好。

边标签位置:PlantUML 独有

1
2
3
4
5
@startuml
A --> B : 上方
A --> B : <<右上方>> on right
A --> B : <<下方>> on bottom
@enduml

<<...>> 语法控制边标签位置,PlantUML 自己实现,dot 没有。

常用:

  • <<on left>> <<on right>>:左右两侧
  • <<top>> <<bottom>>:上下方
  • <<start>> <<end>>:沿边起点/终点

dot 不擅长的场景

1. 节点太多(>100)

dot 的算法复杂度是 O(V log V + E),200+ 节点时布局明显变慢,布局结果经常乱

修法:

  • 用 elk 引擎:!pragma layout elk
  • 拆成多张图
  • 用 cluster 嵌套减少同一 layout level 的节点数

2. 连线交叉严重

dot 尽力最小化连线交叉,但不能保证全局最优

修法:

  • 调节点位置(隐形节点 / hidden 边)
  • 改 rankdir 看哪种方向交叉少
  • 拆图

3. 巨型时序图(50+ lifeline)

dot 在时序图表现一般——时序图用 PlantUML 自带的算法,走 dot。

实战:让一个乱图变好看

1
2
3
4
5
6
7
8
9
10
11
@startuml
' 原始图——A 在中心,周围连 8 个节点,挤在一起
A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
A --> I
@enduml

调优后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@startuml
skinparam nodesep 30
skinparam ranksep 40

' 4 个一排,4 个另一排
A --> B
A --> C
A --> D
A --> E

A --> F
A --> G
A --> H
A --> I

' 强制 B-E 一排,F-I 一排
B -[hidden]- C
C -[hidden]- D
D -[hidden]- E
F -[hidden]- G
G -[hidden]- H
H -[hidden]- I
@enduml

两个 ranksep 的间距清晰,A 居中、节点均匀分布

总结

场景 调优点
图方向不对 left to right direction
节点太挤 skinparam nodesep / ranksep
多个目标要对齐 隐形节点 (hub)
两个无关系组件要对齐 -[hidden]-
分组布局 package node
主路径要直连 weight 10
调试怪图 -tdot 导出 dot 文件

调 PlantUML 布局的核心思路:理解 dot 是底层引擎,PlantUML 是它的 DSL。skinparam 调皮肤,用 dot 概念(hidden、隐形节点、weight)调布局

  • 标题: PlantUML 渲染引擎内幕:Graphviz/dot 算法的 7 个调优点
  • 作者: puml.online
  • 创建于 : 2026-07-30 17:05:00
  • 更新于 : 2026-08-14 21:34:29
  • 链接: https://puml.online/blog/plantuml-graphviz-internals/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。