Pydot Cluster Example
I've been playing around with pydot, which is a Python interface to graphviz. Graphviz is a language (dot) and a set of tools which lets me define and render graphs, as in edges and nodes. With graphviz, you can render a "cluster" of nodes. How is this done in pydot?
Hand-written Cluster Example
First, I started with this example of cluster subgraphs:
Running the dot tool on this file yields a nice subgraph diagram:
Here's the actual source code:
digraph callgraph {
//
// see http://www.adp-gmbh.ch/misc/tools/graphviz/cluster.html
node [fontname="verdana"];
fontname="Verdana";
//
// cluster subgraphs begin with the name "cluster_"
subgraph cluster_foo {
label="foo";
node [label="method_1" ] foo_method_1;
node [label="method_2" ] foo_method_2;
node [label="method_3" ] foo_method_3;
}
subgraph cluster_bar {
label="bar";
node [label="method_a" ] bar_method_a;
node [label="method_b" ] bar_method_b;
node [label="method_c" ] bar_method_c;
}
subgraph cluster_baz {
label="baz";
node [label="method_1" ] baz_method_1;
node [label="method_b" ] baz_method_b;
node [label="method_3" ] baz_method_3;
node [label="method_c" ] baz_method_c;
}
main -> bar_method_a;
bar_method_a -> bar_method_c;
bar_method_a -> foo_method_2;
foo_method_2 -> baz_method_3;
bar_method_b -> foo_method_1;
bar_method_b -> foo_method_2;
baz_method_b -> baz_method_1;
foo_method_2 -> foo_method_3;
bar_method_c -> baz_method_c;
bar_method_b -> baz_method_b;
foo_method_1 -> baz_method_c;
}
Pydot Cluster Example
The basics of dot are easy to grasp, and if you know just a little python, the pydot interface makes sense. However, I hit a roadblock when trying to render the graph-subgraph relationship. Maybe it's because I don't really understand what's going on with dot, or perhaps it's just because I'm dumb. In any case, it took some experimentation to figure out.
Looking at the pydot docs, it seems like there are two ways to define a parent graph-child graph relationship:
Graph.add_subgraph(self, sgraph) Adds an edge object to the graph. It takes a subgraph object as its only argument and returns None.
or
Subgraph.set_graph_parent(self, parent) Sets a graph and its elements to point the the parent. Any subgraph added to a parent graph receives a reference to the parent to access some common data.
The two methods look equivalent (the first adds a subgraph C as a child of parent P, the second sets graph P as the parent of child subgraph C). But it turns out that only Graph.add_graph() does what I want. Subgraph.set_graph_parent() doesn't seem to accomplish much of anything. Maybe someday I'll take a look at the pydot code and figure out what's happening.
There are a few other subtleties which I believe hold, and someday I'll go back and make sure:
Subgraphs must not change after declaring a parent: for subgraph C, don't call P.add_graph(C) until you've defined all the nodes in C.
Without further ado, here's the python code which generates the dot cluster example:
1 """
2 Try to reproduce the dot file example_cluster1.dot with pydot.
3 """
4 import pydot
5 from PIL import Image
6
7 def main():
8 callgraph = pydot.Dot(graph_type='digraph',fontname="Verdana")
9 #
10 # Use pydot.Cluster to render boundary around subgraph
11 cluster_foo=pydot.Cluster('foo',label='foo')
12 #
13 # pydot.Node(name,attrib=''')
14 # Assign unique name to each node, but labels can be arbitrary
15 cluster_foo.add_node(pydot.Node('foo_method_1',label='method_1'))
16 cluster_foo.add_node(pydot.Node('foo_method_2',label='method_2'))
17 cluster_foo.add_node(pydot.Node('foo_method_3',label='method_3'))
18
19 #
20 # in order to get node in parent graph to point to
21 # subgraph, need to use Graph.add_subgraph()
22 # calling Subgraph.add_parent() doesn't seem to do anything.
23 callgraph.add_subgraph(cluster_foo)
24
25 cluster_bar=pydot.Cluster('bar')
26 cluster_bar.add_node(pydot.Node('bar_method_a'))
27 cluster_bar.add_node(pydot.Node('bar_method_b'))
28 cluster_bar.add_node(pydot.Node('bar_method_c'))
29 callgraph.add_subgraph(cluster_bar)
30
31 cluster_baz=pydot.Cluster('baz')
32 cluster_baz.add_node(pydot.Node('baz_method_1'))
33 cluster_baz.add_node(pydot.Node('baz_method_b'))
34 cluster_baz.add_node(pydot.Node('baz_method_3'))
35 cluster_baz.add_node(pydot.Node('baz_method_c'))
36 callgraph.add_subgraph(cluster_baz)
37
38 # create edge between two main nodes:
39 # when creating edges, don't need to
40 # predefine the nodes
41 #
42 callgraph.add_edge(pydot.Edge("main","sub"))
43
44 #
45 # create edge to subgraph
46 callgraph.add_edge(pydot.Edge("main","bar_method_a"))
47
48 callgraph.add_edge(pydot.Edge("bar_method_a","bar_method_c"))
49 callgraph.add_edge(pydot.Edge("bar_method_a","foo_method_2"))
50
51 callgraph.add_edge(pydot.Edge("foo_method_2","baz_method_3"))
52
53 callgraph.add_edge(pydot.Edge("bar_method_b","foo_method_1"))
54 callgraph.add_edge(pydot.Edge("bar_method_b","foo_method_2"))
55 callgraph.add_edge(pydot.Edge("baz_method_b","baz_method_1"))
56
57 callgraph.add_edge(pydot.Edge("foo_method_2","foo_method_3"))
58 callgraph.add_edge(pydot.Edge("bar_method_c","baz_method_c"))
59 callgraph.add_edge(pydot.Edge("bar_method_b","baz_method_b"))
60
61 #
62 # output:
63 # write dot file, then render as png
64 callgraph.write_raw('example_cluster2.dot')
65 print "wrote example_cluster2.dot"
66
67 callgraph.write_png('example_cluster2.png')
68 print "wrote example_cluster2.png"
69
70 im=Image.open('example_cluster2.png')
71 im.show()
72
73
74 if __name__=="__main__":
75 main()


