Quick Reference · describe graphs in Python, render with DOT

graphviz cheat sheet

You don't draw — you describe. Create a Graph (undirected) or Digraph (directed), add nodes and edges with attributes, and the object accumulates DOT source (a text description). A layout engine (dot, neato, circo…) then computes positions and .render() produces a PNG/SVG/PDF. Same graph, different engine → a very different picture. Needs the system Graphviz binaries installed — pip gives you only the wrapper.

setup · classes · Source nodes & edges attributes shapes · clusters · ranks engines · formats render · output · Jupyter gotcha most common

Distilled & cross-checked against: graphviz.readthedocs.io (API · User Guide, v0.21) · graphviz.org (DOT language · attrs) · verified by running the graphviz 0.21 package + dot 2.43 binary end-to-end

Python → DOT → engine → image · and the same graph across engines
THE TWO-STAGE PIPELINE — you write Python, an engine draws 1 · Python object Digraph() · .node · .edge .attr(...) · .subgraph() 2 · DOT source digraph { A -> B } read it with .source 3 · layout engine dot · neato · fdp · circo computes node positions 4 · rendered file PNG · SVG · PDF · JSON .render() · .pipe() emit lay out render ONE GRAPH, FOUR ENGINES — set engine= to change the whole picture dot hierarchical · layered (default) a b c d e ranks flow top → bottom neato / fdp force-directed · organic a b d e c edges as springs, no ranks circo circular a b c d e nodes on a ring twopi radial · rings around a root a b c e d root at centre, rings outward
quickstart.py — build a directed graph, inspect its DOT, render an SVG
import graphviz

dot = graphviz.Digraph("round_table", comment="demo", format="svg")
dot.attr(rankdir="LR")                          # graph-level: left-to-right
dot.attr("node", shape="box", style="filled", fillcolor="#eef")  # node defaults

dot.node("A", "King Arthur")                    # node(name, label, **attrs)
dot.node("B", "Sir Bedevere")
dot.edge("A", "B", label="advises")             # edge(tail, head, label)
dot.edges(["AC", "CB"])                        # shorthand for A->C, C->B

print(dot.source)                              # the DOT text it built
dot.render("round_table", view=True, cleanup=True)  # → round_table.svg
01Setup & the Two Classeswrapper + binaries
02Build: Nodes & Edgesassemble the graph
03Nodesidentity = the name
04Edgestail → head
05Attributes: 3 Levelsdefaults vs per-element
06Node Attributesthe common knobs
07Edge Attributesthe common knobs
08Shapes & Stylesthe vocabulary
09Layout Enginesset engine=
10Subgraphs & Clustersgroup with a box
11Ranks & Layout Controlsteer the dot engine
12Render to FileDOT → image
13Formats & Pipingbytes, not files
14Jupyter & Displayinline rendering
15Raw DOT & Sourcebring your own DOT
16Records, Ports & HTMLstructured nodes

Four ideas worth a picture

The two-stage pipeline, the shape vocabulary, the direction knob, and how clusters box things up.

Python → DOT → image

Your calls build DOT text; the engine turns that text into a drawing. Two stages, always.

YOU WRITE g.node("A"); g.node("B") g.edge("A", "B", label="x") g.source DOT SOURCE digraph { A -> B [label=x] } dot engine renders x A B

The node-shape vocabulary

Set shape= — a handful cover almost everything you'll draw.

boxbox ellipseellipse (default) circcircle diadiamond abc record cylinder point plainplaintext style adds the finish: rounded · filled · dashed · dotted · bold · invis style="rounded,filled" combines several

rankdir — same graph, turned

The dot engine stacks ranks top-to-bottom by default; rankdir="LR" turns it sideways.

rankdir="TB" A B C D rankdir="LR" A B C D

Clusters box up subgraphs

A subgraph whose name starts with cluster gets a labelled bounding box; edges can still cross between them.

cluster_0 cluster_1 cross-cluster edge a b c d with g.subgraph(name="cluster_0") as c: ...

Worth memorizing

graphviz ≠ pygraphvizthis package builds DOT text; pygraphviz wraps the C lib (AGraph)
binaries requiredpip installs the wrapper; you still need system Graphviz (dot) on PATH
default format = PDFset format="png"/"svg" on the object or in render()
Graph vs Digraph-- vs -> — chosen at construction, can't be mixed
nodes keyed by namere-adding a name updates its attributes; no duplicate node
edge auto-createsedge("a","b") makes nodes a and b if absent
attr order matters.attr("node",…) only affects nodes added after it
cluster prefixa subgraph draws a box only if its name starts with cluster
rank=samemust sit inside a subgraph: with g.subgraph() as s: s.attr(rank="same")
fillcolor needs stylefillcolor shows only with style="filled"
.pipe vs .renderpipe() returns bytes in memory; render() writes a file
<...> is HTMLangle-bracket labels are HTML-like; quotes are literal text
engine = dotthe default is hierarchical; try neato/fdp/circo for non-trees
render writes source tooit also drops the .gv file — pass cleanup=True to remove it