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:

/wikiperdido/Pydot_Clusters?action=AttachFile&do=get&target=example_cluster1.png

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:

or

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:

Without further ado, here's the python code which generates the dot cluster example:

/wikiperdido/Pydot_Clusters?action=AttachFile&do=get&target=example_cluster2.png

   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()


CategoryNerd