User Guide


graphviz provides a simple pure-Python interface for the Graphviz graph-drawing software. It runs under Python 3.6+. To install it with pip, run the following:

$ pip install graphviz

For a system-wide install, this typically requires administrator access. For an isolated install, you can run the same inside a venv or a virtualenv.

The only dependency is a working installation of Graphviz (download page, archived versions, installation procedure for Windows).

After installing Graphviz, make sure that its bin/ subdirectory containing the layout commands for rendering graph descriptions (dot, circo, neato, etc.) is on your systems’ path: On the command-line, dot -V should print the version of your Graphiz installation.


Windows users might want to check the status of known issues (gvedit.exe, sfdp, commands) and consider trying an older archived version as a workaround (e.g. graphviz-2.38.msi).

Anaconda: see the conda-forge package conda-forge/python-graphviz (feedstock), which should automatically conda install conda-forge/graphviz (feedstock) as dependency.

Basic usage

The graphviz module provides two classes: Graph and Digraph. They create graph descriptions in the DOT language for undirected and directed graphs respectively. They have the same API.

Create a graph by instantiating a new Graph or Digraph object:

>>> import graphviz

>>> dot = graphviz.Digraph(comment='The Round Table')

>>> dot  
< object at 0x...>

Their constructors allow to set the graph’s name, the filename for the DOT source and the rendered graph, a comment for the first source code line, etc.

Add nodes and edges to the graph object using its node() and edge()- or edges()-methods:

>>> dot.node('A', 'King Arthur')
>>> dot.node('B', 'Sir Bedevere the Wise')
>>> dot.node('L', 'Sir Lancelot the Brave')

>>> dot.edges(['AB', 'AL'])
>>> dot.edge('B', 'L', constraint='false')

The node()-method takes a name identifier as first argument and an optional label. The edge()-method takes the names of start node and end node, while edges() takes an iterable of name pairs. Keyword arguments are turned into (node and edge) attributes (see Graphviz docs on available attributes).

Check the generated source code:

>>> print(dot.source)  
// The Round Table
digraph {
    A [label="King Arthur"]
    B [label="Sir Bedevere the Wise"]
    L [label="Sir Lancelot the Brave"]
    A -> B
    A -> L
    B -> L [constraint=false]

Use the render()-method to save the source code and render it with the default layout program (dot, see below for using other layout commands).

>>> dot.render('test-output/round-table.gv', view=True)  

Passing view=True will automatically open the resulting (PDF, PNG, SVG, etc.) file with your system’s default viewer application for the file type.


To use a different output file format than the default PDF, use the format argument when creating your Graph or Digraph object:

>>> g = graphviz.Graph(format='png')

You can also change the format attribute on an existing graph object:

>>> dot.format = 'svg'

>>> dot.render()  

Piped output

To directly access the results from the Graphviz rendering command (e.g. dot) as binary data string from within Python instead of writing to a file, use the pipe()-method of your Graph or Digraph object:

>>> h = graphviz.Graph('hello', format='svg')

>>> h.edge('Hello', 'World')

>>> print(h.pipe().decode('utf-8'))  
<?xml version="1.0" encoding="UTF-8" standalone="no"?>

Note that pipe() returns the raw stdout from the rendering command (str on Python 2, bytes on Python 3): When piping into plain-text formats like 'svg' or 'plain', you usually want to decode the return value as shown above.


The output for pipe() is buffered in memory, so do not use this method if the data size is large.

Jupyter notebooks

Graph and Digraph objects have a _repr_svg_()-method so they can be rendered and displayed directly inside a Jupyter notebook. For an example, check the examples/graphviz-notebook.ipynb file in the source repository/distribution (or the same rendered within nbviewer).

This also allows direct displaying within the Jupyter Qt Console (e.g. the one inside Spyder IDE):



Use the graph_attr, node_attr, and edge_attr arguments to change the default appearance of your graph, nodes, and edges.

>>> ps = graphviz.Digraph(name='pet-shop', node_attr={'shape': 'plaintext'})

>>> ps.node('parrot')
>>> ps.node('dead')
>>> ps.edge('parrot', 'dead')

After creation, they can be edited on the graph object:

>>> ps.graph_attr['rankdir'] = 'LR'
>>> ps.edge_attr.update(arrowhead='vee', arrowsize='2')

>>> print(ps.source)  
digraph "pet-shop" {
    graph [rankdir=LR]
    node [shape=plaintext]
    edge [arrowhead=vee arrowsize=2]
    parrot -> dead


To directly add attritbute statements (affecting all following graph, node, or edge items within the same (sub-)graph), use the attr()-method with the target as first argument:

>>> ni = graphviz.Graph('ni')

>>> ni.attr('node', shape='rarrow')
>>> ni.node('1', 'Ni!')
>>> ni.node('2', 'Ni!')

>>> ni.node('3', 'Ni!', shape='egg')

>>> ni.attr('node', shape='star')
>>> ni.node('4', 'Ni!')
>>> ni.node('5', 'Ni!')

By omitting its first argument, you can use it to set arbitrary attributes as key-value pairs targeting the current (sub-)graph (e.g. for rankdir, label, or setting rank='same' within a subgraph context, example):

>>> ni.attr(rankdir='LR')

>>> ni.edges(['12', '23', '34', '45'])

>>> print(ni.source)  
graph ni {
    node [shape=rarrow]
    1 [label="Ni!"]
    2 [label="Ni!"]
    3 [label="Ni!" shape=egg]
    node [shape=star]
    4 [label="Ni!"]
    5 [label="Ni!"]
    1 -- 2
    2 -- 3
    3 -- 4
    4 -- 5

Node ports & compass

The edge()- and edges()-methods use the colon-separated format node[:port[:compass]] for tail and head nodes. This allows to specify an optional node port plus an optional compass point the edge should aim at for the given tail or head node (example).

As colons are used to indicate port and compass, node names with literal colon(s) (:) are not supported. Note that there is no such restriction for the label argument, so you can work around by choosing a colon-free name together with the wanted label:

>>> cpp = graphviz.Digraph('C++')

>>> cpp.node('A', 'std::string')
>>> cpp.node('B', '"spam"')

>>> cpp.edge('A', 'B')

>>> print(cpp.source)  
digraph "C++" {
    A [label="std::string"]
    B [label="\"spam\""]
    A -> B

Backslash escapes

The Graphviz layout engine supports a number of escape sequences such as \n, \l, \r (for multi-line labels: centered, left-justified, right-justified) and \N, \G, \L (expanded to the current node name, graph name, object label). To be able to use them from this library (e.g. for labels), strings with backslashes are passed on as is. This means that literal backslashes need to be escaped (doubled) by the user. As the backslash is also special in Python string literals, a second level of doubling is needed (e.g. label='\\\\'). This kind of doubling can be avoided by using raw string literals (r'...') instead (same solution as proposed for the stdlib re module):

>>> e = graphviz.Digraph()

>>> e.node('backslash', label=r'\\')
>>> e.node('multi_line', label=r'centered\nleft\lright\r')

>>> print(e.source)  
digraph {
    backslash [label="\\"]
    multi_line [label="centered\nleft\lright\r"]

To disable any special character meaning in a string (e.g. from user input to be rendered literally), use the escape() function (cf. the re.escape() function):

>>> bs = graphviz.Digraph()

>>> bs.node(graphviz.escape('\\'))

>>> print(bs.source)  
digraph {


To prevent breaking the internal quoting mechanism, the special meaning of \" as a backslash-escaped quote has been disabled since version 0.14. E.g. both label='"' and label='\\"' now produce the same DOT source [label="\""] (a label that renders as a literal quote).

Quoting and HTML-like labels

The graph-building methods of Graph and Digraph objects automatically take care of quoting (and escaping quotes) where needed (whitespace, keywords, double quotes, etc.):

>>> q = graphviz.Digraph()

>>> q.edge('spam', 'eggs eggs')
>>> q.edge('node', '"here\'s a quote"')

>>> print(q.source)  
digraph {
    spam -> "eggs eggs"
    "node" -> "\"here's a quote\""

If a string starts with '<' and ends with '>', it is passed on as is, without quoting/escaping: The content between the angle brackets is treated by the engine as special HTML string that can be used for HTML-like labels:

>>> h = graphviz.Graph('html_table')

>>> h.node('tab', label='''<<TABLE>
...  <TR>
...    <TD>left</TD>
...    <TD>right</TD>
...  </TR>
... </TABLE>>''')

For strings that should literally begin with '<' and end with '>', use the nohtml() function to disable the special meaning of angled parenthesis and apply normal quoting/escaping (before 0.8.2, the only workaround was to add leading or trailing space, e.g. label=' <>'):

>>> d = graphviz.Digraph(format='svg')

>>> d.node('diamond', label=graphviz.nohtml('<>'))

>>> print(d.source)  
digraph {
    diamond [label="<>"]

Subgraphs & clusters

Graph and Digraph objects have a subgraph()-method for adding a subgraph to an instance.

There are two ways to use it: Either with a ready-made graph object of the same kind as the only argument (whose content is added as a subgraph) or omitting the graph argument (returning a context manager for defining the subgraph content more elegantly within a with-block).

First usage option, with graph as the only argument:

>>> p = graphviz.Graph(name='parent')
>>> p.edge('spam', 'eggs')

>>> c = graphviz.Graph(name='child', node_attr={'shape': 'box'})
>>> c.edge('foo', 'bar')

>>> p.subgraph(c)

Second usage, with a with-block (omitting the graph argument):

>>> p = graphviz.Graph(name='parent')
>>> p.edge('spam', 'eggs')

>>> with p.subgraph(name='child', node_attr={'shape': 'box'}) as c:
...    c.edge('foo', 'bar')

Both produce the same result:

>>> print(p.source)  
graph parent {
    spam -- eggs
    subgraph child {
        node [shape=box]
        foo -- bar


If the name of a subgraph begins with 'cluster' (all lowercase) the layout engine will treat it as a special cluster subgraph (example). Also see the Subgraphs and Clusters section of the DOT language documentation.

When subgraph() is used as a context manager, the new graph instance is created with strict=None and the parent graph’s values for directory, format, engine, and encoding. Note that these attributes are only relevant when rendering the subgraph independently (i.e. as a stand-alone graph) from within the with-block:

>>> p = graphviz.Graph(name='parent')

>>> with p.subgraph(name='child') as c:
...    c.edge('bacon', 'eggs')
...    c.render()  


To use a different layout command than the default dot when rendering your graph, use the engine argument when creating your graph.

>>> g = graphviz.Graph(engine='neato')

You can also change the engine attribute of an existing instance:

>>> dot.engine = 'circo'


To prepocess the DOT source of a Graph or Digraph with the unflatten preprocessor (PDF), use the unflatten()-method.

>>> w = graphviz.Digraph()

>>> w.edges(('0', str(i)) for i in range(1, 10))

>>> w.view()  

unflatten is used to improve the aspect ratio of graphs having many leaves or disconnected nodes.

>>> u = w.unflatten(stagger=3)

>>> u.view()  

The method returns a Source object that you can render(), view(), etc. with the same API (minus modification, see details below).

>>> u = w.unflatten(stagger=2)

>>> u.view()  

Custom DOT statements

To add arbitrary statements to the created DOT source, use the body attribute of the Graph or Digraph object. It holds the verbatim list of lines to be written to the source file. Use its append()- or extend()-method:

>>> rt = graphviz.Digraph(comment='The Round Table')

>>> rt.body.append('\t"King Arthur" -> {\n\t\t"Sir Bedevere", "Sir Lancelot"\n\t}')
>>> rt.edge('Sir Bedevere', 'Sir Lancelot', constraint='false')

>>> print(rt.source)  
// The Round Table
digraph {
    "King Arthur" -> {
        "Sir Bedevere", "Sir Lancelot"
    "Sir Bedevere" -> "Sir Lancelot" [constraint=false]

Note that you might need to correctly quote/escape identifiers and strings containing whitespace or other special characters when using this method.

Using raw DOT

To render a ready-made DOT source code string (instead of assembling one with the higher-level interface of Graph or Digraph), create a Source object holding your DOT string:

>>> src = graphviz.Source('digraph "the holy hand grenade" { rankdir=LR; 1 -> 2 -> 3 -> lob }')

>>> src  
<graphviz.files.Source object at 0x...>

Use the render()-method to save and render it:

>>> src.render('test-output/holy-grenade.gv', view=True)  

Apart from the missing editing methods, Source objects are the same as the higher-level graph objects (pipe()-method, format, engine, Jupyter notebook repr, etc.), see above.

Existing files

To directly render an existing DOT source file (e.g. created with other tools), you can use the graphviz.render() function.

>>> graphviz.render('dot', 'png', 'test-output/holy-grenade.gv')  

To directly display the graph of an existing DOT source file inside a Jupyter notebook or Qt Console, you can use the Source.from_file()-classmethod (alternate constructor):


Note that if you call render() or view() on the returned Source object, it will still save() as usual (i.e. write the content read into source back into the file). You can use graphviz.render() and graphiz.view() to directly work on files in case you need to avoid this round-trip.

Integration with viewers

On platforms such as Windows, viewer programs opened by rendering with view=True or the view()-method might lock the (PDF, PNG, etc.) file for as long as the viewer is open (blocking re-rendering it with a Permission denied error). You can use the mktemp() function from the stdlib tempfile module to render to a different file for each invocation to avoid needing to close the viewer window each time within such an incremental workflow (and also preserve its intermediate steps):

>>> import tempfile

>>> g = graphviz.Graph()

>>> g.node('spam')

>>> g.view(tempfile.mktemp('.gv'))  

>>> g.view(tempfile.mktemp('.gv'))  

Other options are viewers that support live updates or using the Jupyter notebook or Qt Console to display the current version of the rendered graph in repeated add/render/view cycles.