PlantUML rendering engine internals: 7 Graphviz/dot tuning knobs
Most PlantUML diagrams (component, state, object, class) are laid out by Graphviz’s dot algorithm underneath. Understanding how dot works explains many “why does the diagram auto-arrange like that?” mysteries, and how to make it look better.
What the dot algorithm is
When PlantUML renders component / class / state, its parser first converts .puml to Graphviz’s dot language, then the dot engine calculates the layout. The final SVG comes from dot’s positions plus PlantUML’s own style sheet.
This means: you can leverage all of dot’s features to influence PlantUML diagrams. PlantUML wraps them as skinparam, !pragma, etc., but understanding the layer below helps diagnose weird layouts.
Knob 1: rankdir changes direction
1 | @startuml |
Underlying dot:
1 | digraph { |
rankdir accepts: LR (left-to-right), TB (default, top-to-bottom), BT, RL.
When to use left to right:
- Sequence diagram too wide horizontally → switch to
top to bottom(default) - Deployment diagram has many zones →
left to rightlines them up
Knob 2: nodesep / ranksep control spacing
1 | @startuml |
nodesep: spacing between nodes on the same rank (same row/column). Default 40 (px). Nodes too cramped → increase.
ranksep: spacing between different ranks (rows/columns). Default 60. Hierarchy unclear → increase.
Knob 3: invisible nodes for layout control
1 | @startuml |
(hub) is an anonymous node — dot treats it as a real node for rank calculation. It pulls multiple targets onto the same rank.
Field usage: in an architecture diagram where multiple services all connect to one DB, the DB naturally drops down a level — add invisible nodes to center the DB:
1 | component "auth-svc" as auth |
Knob 4: hidden edges for alignment
1 | @startuml |
-[hidden]- is dot’s special edge type — used for layout only, doesn’t render. A and C get pulled to the same rank.
Field usage: two services with no direct relationship but need horizontal alignment:
1 | component "Web App" as web |
Web App and Admin Tool align horizontally.
Knob 5: subgraph grouping (package / node)
1 | @startuml |
dot treats package as a subgraph — automatically draws a bounding box and packs the nodes inside.
Note: subgraphs don’t affect edge direction, only visual grouping.
Knob 6: nested cluster
1 | @startuml |
Nested node produces nested clusters — classic AWS architecture diagram pattern.
Knob 7: edge weight
1 | @startuml |
In dot, edges have weights — higher weight edges are prioritized for straight alignment within the same rank, lower weight edges detour. weight 10 forces A and C onto the same rank, aligned.
Field usage: critical path goes straight, secondary path detours:
1 | component "User" as user |
User → API → DB is the straight main path; API → Cache is the side path.
How to debug weird layouts
Method 1: export the dot file
PlantUML CLI:
1 | plantuml -tlog diagram.puml |
Even more detailed:
1 | plantuml -tdot diagram.puml |
Compare plantuml output and dot output — if dot’s own layout is odd, the problem is in dot, not PlantUML.
Method 2: -stdlib to simplify
1 | plantuml -tsvg -Sstdlib=true diagram.puml |
-Sstdlib=true disables PlantUML’s stdlib themes, showing what default dot layout looks like.
Method 3: !pragma layout directive
1 | @startuml |
PlantUML 1.2023+ supports elk and neato engines. elk (Eclipse Layout Kernel) handles complex diagrams better.
Edge label position: PlantUML-only
1 | @startuml |
The <<...>> syntax controls edge label placement — PlantUML implements this itself, dot doesn’t.
Common:
<<on left>><<on right>>: left or right side<<top>><<bottom>>: above or below<<start>><<end>>: along edge start or end
Where dot struggles
1. Too many nodes (>100)
dot’s complexity is O(V log V + E) — 200+ nodes make layout visibly slow, results often chaotic.
Fix:
- Use elk engine:
!pragma layout elk - Split into multiple diagrams
- Use cluster nesting to reduce nodes at the same layout level
2. Heavy edge crossings
dot minimizes crossings but can’t guarantee global optimum.
Fix:
- Tune positions (invisible nodes / hidden edges)
- Try different
rankdirand see which has fewer crossings - Split the diagram
3. Giant sequence diagrams (50+ lifelines)
dot performs poorly on sequence diagrams — sequence diagrams use PlantUML’s built-in algorithm, not dot.
Field example: turning a messy diagram tidy
1 | @startuml |
After tuning:
1 | @startuml |
Two clearly separated ranks, A centered, nodes evenly distributed.
Summary
| Scenario | Knob |
|---|---|
| Wrong direction | left to right direction |
| Nodes too cramped | skinparam nodesep / ranksep |
| Multiple targets need alignment | invisible node (hub) |
| Two unrelated components need alignment | -[hidden]- |
| Group layout | package node |
| Main path should be straight | weight 10 |
| Debug weird diagram | -tdot export dot file |
The core idea for tuning PlantUML layout: dot is the underlying engine, PlantUML is its DSL. Use skinparam for skin, use dot concepts (hidden, invisible nodes, weight) for layout.
- Title: PlantUML rendering engine internals: 7 Graphviz/dot tuning knobs
- Author: puml.online
- Created at : 2026-07-30 17:05:00
- Updated at : 2026-08-14 21:34:29
- Link: https://puml.online/blog/plantuml-graphviz-internals-en/
- License: This work is licensed under CC BY-NC-SA 4.0.