pip install graphviz★Installs only the Python wrapper.apt/brew install graphvizrequiredThedotbinary must be on PATH or you getExecutableNotFound.import graphviz★Orfrom graphviz import Digraph, Graph, Source.graphviz.Digraph()★Directed — edges drawn as->with arrowheads.graphviz.Graph()★Undirected — edges are--. Same API; can't mix the two.graphviz.Digraph(strict=True)Collapse duplicate/parallel edges into one.
g = graphviz.Digraph("G", comment="my graph")★Create the object; comment becomes the first DOT line.g.node("A")★Add a node namedA.g.edge("A", "B")★Add an edge A→B — auto-creates A and B.g.edges(["AB", "AC"])★Shorthand for many 2-char edges at once.g.body.append("\t// raw DOT line")Inject arbitrary DOT if you need to.print(g.source)★See the DOT text built so far.
g.node("A", "King Arthur")★node(name, label)— label is what's shown.g.node("A", shape="box", color="red")★Attributes as keyword args.g.node("A", fontname="Helvetica")Re-using a name updates its attrs — no duplicate node.g.node("my node")Names with spaces/punctuation get quoted automatically — but keep IDs simple.g.node("A", tooltip="hi", URL="#a")SVG output supports tooltips & links.
g.edge("A", "B", "label")★edge(tail, head, label).g.edge("A", "B", color="blue", style="dashed")★Style with keyword attrs.g.edge("B", "L", constraint="false")Draw the edge but don't let it affect ranking.g.edge("A", "A")Self-loop.g.edge("A", "B", dir="both")Arrowheads on both ends (ornone).
g.attr(rankdir="LR", bgcolor="white")★Graph-level — no first arg = the graph itself.g.attr("node", shape="box", style="filled")★Node defaults for everything added after this.g.attr("edge", color="gray")★Edge defaults.g.node("A", shape="circle")Per-element attrs override the defaults.Digraph(node_attr={"shape": "box"})Set defaults up front via constructor (graph_attr/edge_attrtoo).order mattersSet.attr("node", …)before adding the nodes it should affect.
shape="box"★box · ellipse · circle · diamond · plaintext · record… (card 08).style="filled", fillcolor="lightblue"★fillcolor needsstyle="filled"to show.color="red"Border/outline colour (names,#rrggbb, or"red:blue").fontname="Helvetica", fontsize="10"Label typography.width="1", height="0.5", fixedsize="true"Force exact sizing (inches).peripheries="2"Double outline (e.g. accepting states).
label="yes"★Text on the edge (xlabelfor external).style="dashed"★solid · dashed · dotted · bold · invis.arrowhead="vee"normal · vee · diamond · dot · none · empty…dir="back"forward (default) · back · both · none.penwidth="2", weight="5"Thickness · pull (higher = straighter/shorter).constraint="false"Keep the edge out of rank calculation.
shape: box · ellipse · circle · diamond★The everyday shapes (ellipse is the default).shape: plaintext · none · pointNo border · invisible box · a dot.shape: cylinder · folder · component · noteDiagram semantics (DB, files, UML-ish).shape="record"★Multi-field boxes (card 16).style: rounded · filled · dashed · dotted · bold★Combine:style="rounded,filled".style="invis"Hide a node/edge but keep it in the layout.
Digraph(engine="neato")★Pick at construction, or per-render:g.render(engine="…")."dot"★Hierarchical / layered — the default, best for DAGs & trees."neato" · "fdp" · "sfdp"★Force-directed / spring — undirected, organic; sfdp scales large."circo"Circular — cyclic structures."twopi"Radial — rings around a central root."osage" · "patchwork"Clustered layout · squarified treemap.
with g.subgraph(name="cluster_0") as c:★Context-manager form — add nodes/edges toc.c.attr(label="Stage 1", style="filled")Cluster-level attrs (label, color, bgcolor).name must start "cluster"key ruleOnlycluster*-named subgraphs draw a bounding box.c.node("a"); c.edge("a", "b")Members live inside the box.g.subgraph(child_graph)Or pass a ready-made same-kind instance.
g.attr(rankdir="LR")★Direction: TB (default) · LR · BT · RL.with g.subgraph() as s: s.attr(rank="same")★Force nodes onto the same level (alsomin/max/source/sink).g.attr(ranksep="0.8", nodesep="0.4")Spacing between ranks / within a rank (inches).g.attr(splines="ortho")Edge routing: line · curved · ortho · polyline.g.attr(newrank="true")Makerank=samework across clusters.
g.render("out")★Writesout(DOT) +out.pdf; returns the output path.g.render("out", format="png")★Default format is PDF — set png/svg here or on the object.g.render("out", view=True)★Open the result in the default viewer.g.render(cleanup=True)Delete the intermediate DOT source after rendering.g.render(directory="build", engine="neato")Output folder · override engine for this render.g.save("out.gv")Write just the DOT source, no rendering.
g.format = "svg"★Set on the object; png · svg · pdf · jpg · json · ps · dot.g.pipe(format="svg")★Render to bytes in memory — no file written.svg = g.pipe(format="svg").decode()★Decode bytes → embeddable SVG text.g.pipe(format="json")Get laid-out node/edge coordinates as JSON.g.pipe(engine="neato", format="png")Override engine/format inline.
g★Just evaluate the object — it auto-renders as SVG in the notebook.graphviz.set_jupyter_format("png")Switch the inline display format globally.g._repr_mimebundle_()What the notebook calls under the hood (SVG by default).from IPython.display import display_pngdisplay_png(g)/display_svg(g)to force a format.print(g.source)★Inspect the raw DOT when a render looks wrong.
s = graphviz.Source("digraph { A -> B }")★Wrap a DOT string — then.render()/ display like any graph.graphviz.Source.from_file("g.gv")Load DOT from disk.s.render(format="svg", engine="circo")Same render/pipe API as Graph/Digraph.label="<...>" is HTML<...>= HTML-like label; plain quotes = literal text.graphviz.nohtml("a|b")Force a literal record string that contains< >.
g.node("n", "a | b | c", shape="record")★Pipe-separated fields in one box."{ a | b | c }"Braces flip the split direction (row vs column)."<f0> id | <f1> name"★Named ports for precise edge endpoints.g.edge("n:f1", "other")Connect to a specific port:node:port.label="<<table>...</table>>"Double angle brackets = rich HTML-like table labels.