Default tailport/headport based on direction to stop edges from floating off icon-shaped nodes
Summary
Even with splines='spline' (the safe combo from #1214), edge tails on image-shaped icon nodes do not attach at the right-center / left-center of the source/target — they attach at whichever corner of the bbox happens to be closest to the other endpoint. With direction='LR' and AWS/k8s icon nodes (which use shape=none, imagescale=true, fixedsize=true), this reads visually as arrows that begin or end in midair, several pixels above/below the icon they are supposed to point from/to.
This is the dominant remaining source of "ugly LLM-rendered architecture diagrams" once #1214's ortho advice is followed. It is fixable in diagrams itself (no upstream Graphviz dependency) by setting sensible default ports for the edge.
Reproduction
from diagrams import Diagram
from diagrams.aws.network import ELB
from diagrams.aws.compute import EC2
with Diagram("floating-tail-repro", direction="LR", show=False,
graph_attr={"splines": "spline"}):
alb = ELB("alb")
alb >> [EC2("web-1"), EC2("web-2"), EC2("web-3")]Inspect the SVG. The alb icon's bbox center is at y = -158 (for example). The three edge <path> M commands start at y = -203 (top corner of the bbox) instead of y = -158. The visible arrow tail is therefore ~45 px above the icon. The same defect occurs at the head end on multi-row fan-ins.
Observed
- Edge tail/head coordinates in the output SVG attach at top/bottom corner of the icon bbox, not the center face that the LR layout reads.
- For LR diagrams, this means "edges leaving from the top of an icon and re-entering from the top of another icon" instead of clean horizontal flow.
- Most visible on:
- fan-out / fan-in where source and N targets sit at different vertical ranks;
- any cross-cluster edge whose endpoints are at different y;
- any edge between rows in a multi-row layout (very common in real architecture diagrams).
Expected
For direction='LR', edges should leave the east face (right-center) of the source and arrive at the west face (left-center) of the target by default. Likewise 'RL' -> w/e, 'TB' -> s/n, 'BT' -> n/s.
This matches what every hand-drawn architecture diagram does and what users intuitively expect.
Root cause
Graphviz routes from the closest corner of a node's bounding box when no explicit port is given. For text-shaped nodes (the default in plain Graphviz examples) the bbox is short, so the visual error is small. For diagrams' icon nodes — shape=none, imagescale=true, fixedsize=true with _height=1.9 — the bbox is tall, so the corner-vs-center error is large and reads as a floating arrow.
Setting tailport / headport on the edge fixes it. The library currently never sets them.
Note: this only works with splines='spline' / 'polyline' / 'curved' / 'line'. With splines='ortho', Graphviz silently ignores tailport / headport (upstream graphviz #1856, trim-on-ortho), which is one more reason ortho is unsafe (see #1214).
Suggested fix
Set tailport / headport defaults on every edge based on the diagram's direction:
| direction | tailport | headport |
|---|---|---|
LR (default) |
e |
w |
RL |
w |
e |
TB |
s |
n |
BT |
n |
s |
Concretely, in diagrams/__init__.py, pick defaults at Diagram.__init__ time and merge them into Edge.connect (only when the user has not passed tailport / headport explicitly, so power users keep full control).
This is a one-screen change with no upstream dependency, fixes the defect for ~100% of LR/TB diagrams, and is fully backward compatible (any user who has set their own ports keeps them).
Workaround for current users
Until this lands, the workaround is to monkey-patch the defaults at module level:
from diagrams import Diagram
Diagram._default_edge_attrs.update({"tailport": "e", "headport": "w"})(plus splines='spline' per #1214). We are running this in production behind an LLM-rendered-diagram pipeline; it eliminates the floating-arrow class of bug entirely.
Environment
diagrams: 0.25.1graphviz(CLI): 9.0 / 12.x — defect reproduces on both- Python: 3.11
- OS: macOS 14 / Ubuntu 22.04 (OS-independent)
Closing
Companion issue to #1214 — that one was about ortho-vs-cluster routing, this one is specifically about default ports on image-shaped nodes. Happy to send a PR if the direction looks right.
Source: mingrammer/diagrams