`graphviz`

moduleThere are several Python modules for dealing with Graphviz. The most popular one seems to be `pygraphviz`

. Unfortunately it isn't available for Python 3. So we will use the `graphviz`

module instead.

Installation is simple:

pip install graphviz

First we need to import the graphviz module:

import graphviz as gv

Now we can create the graph object `g1`

and add two nodes `A`

and `B`

as well as an edge to connect the two.

g1 = gv.Graph(format='svg') g1.node('A') g1.node('B') g1.edge('A', 'B')

Let's have a look at the the `dot`

code that this will generate behind the scenes:

print(g1.source)

graph { A B A -- B }

That looks about right. Now it's time to save the graph to disk:

filename = g1.render(filename='img/g1') print filename

img/g1.svg

As you can see, the graphviz module takes care of appending the correct file extension.

Let's have a look at the result:

To create a directed version of the previous graph we only need to replace the `Graph`

object with a `Digraph`

.

g2 = gv.Digraph(format='svg') g2.node('A') g2.node('B') g2.edge('A', 'B') g2.render('img/g2')

The above approach is fine for small graphs but for larger ones we should create a few helper functions. Since we will stick to SVG as our output format there is no need to explicity specifiy it for every graph.

import functools graph = functools.partial(gv.Graph, format='svg') digraph = functools.partial(gv.Digraph, format='svg')

Now creating a new graph is as easy as calling `graph()`

g3 = graph()

Next, we would like to specify the nodes and edges as simple data structures instead typing loads of method calls.

nodes = ['A', 'B', ('C', {})]

Every node is represented by either a string or a tuple. In the latter case the second element of the tuple holds all attributes of that node as we will see later. The represenation of edges is quite similar:

edges = [ ('A', 'B'), ('B', 'C'), (('A', 'C'), {}), ]

To actually create nodes and edges from these data structures we implement the functions `add_nodes`

and `add_edges`

. Note that both functions return the updated graph which will come in handy in just a second.

def add_nodes(graph, nodes): for n in nodes: if isinstance(n, tuple): graph.node(n[0], **n[1]) else: graph.node(n) return graph def add_edges(graph, edges): for e in edges: if isinstance(e[0], tuple): graph.edge(*e[0], **e[1]) else: graph.edge(*e) return graph

Now we can create graphs in a much more concise way:

add_edges( add_nodes(digraph(), ['A', 'B', 'C']), [('A', 'B'), ('A', 'C'), ('B', 'C')] ).render('img/g4')

Labeling nodes and edges is as easy as adding an entry to their dictionaries of attributes.

add_edges( add_nodes(digraph(), [ ('A', {'label': 'Node A'}), ('B', {'label': 'Node B'}), 'C' ]), [ (('A', 'B'), {'label': 'Edge 1'}), (('A', 'C'), {'label': 'Edge 2'}), ('B', 'C') ] ).render('img/g5')

Graphviz let's you style graphs extensively by setting specific attributes. The following example will showcase a few of these attributes.

For a complete list of attributes please refer to http://www.graphviz.org/doc/info/attrs.html.

First, let's create an unstyled graph:

g6 = add_edges( add_nodes(digraph(), [ ('A', {'label': 'Node A'}), ('B', {'label': 'Node B'}), 'C' ]), [ (('A', 'B'), {'label': 'Edge 1'}), (('A', 'C'), {'label': 'Edge 2'}), ('B', 'C') ] )

We will use a simple dictionary to specifiy the attributes that we want to change:

styles = { 'graph': { 'label': 'A Fancy Graph', 'fontsize': '16', 'fontcolor': 'white', 'bgcolor': '#333333', 'rankdir': 'BT', }, 'nodes': { 'fontname': 'Helvetica', 'shape': 'hexagon', 'fontcolor': 'white', 'color': 'white', 'style': 'filled', 'fillcolor': '#006699', }, 'edges': { 'style': 'dashed', 'color': 'white', 'arrowhead': 'open', 'fontname': 'Courier', 'fontsize': '12', 'fontcolor': 'white', } }

Now we need another helper function `apply_styles`

to actually apply those attributes to the graph:

def apply_styles(graph, styles): graph.graph_attr.update( ('graph' in styles and styles['graph']) or {} ) graph.node_attr.update( ('nodes' in styles and styles['nodes']) or {} ) graph.edge_attr.update( ('edges' in styles and styles['edges']) or {} ) return graph

Let's try it out!

g6 = apply_styles(g6, styles) g6.render('img/g6')

Lastly, we will have a look a subgraphs. First, let's create two seperate graphs `g7`

and `g8`

, each with their own styling.

g7 = add_edges( add_nodes(digraph(), [ ('A', {'label': 'Node A'}), ('B', {'label': 'Node B'}), 'C' ]), [ (('A', 'B'), {'label': 'Edge 1'}), (('A', 'C'), {'label': 'Edge 2'}), ('B', 'C') ] ) g8 = apply_styles( add_edges( add_nodes(digraph(), [ ('D', {'label': 'Node D'}), ('E', {'label': 'Node E'}), 'F' ]), [ (('D', 'E'), {'label': 'Edge 3'}), (('D', 'F'), {'label': 'Edge 4'}), ('E', 'F') ] ), { 'nodes': { 'shape': 'square', 'style': 'filled', 'fillcolor': '#cccccc', } } )

Now we combine the two graphs by making `g8`

a subgraph of `g7`

and adding an extra edge to connect the two:

g7.subgraph(g8) g7.edge('B', 'E', color='red', weight='2')

Let's have a look at the result:

g7.render('img/g7')

- Head over to GitHub to get the complete source code.
- Improve or extend the script and let me know what you've come up with.
- Learn more about Graphviz and the Dot language: http://www.graphviz.org/pdf/dotguide.pdf.