Graphiti

February 27, 2013

Back in college, I stumbled across a great tool called GraphViz. I used it to generate class diagrams from the source of a PHP application I was working on. More recently, I've found it useful again for visualizing the runtime relationships between objects. GraphViz can turn a plaintext description of relationships into a graph image. Not "graph" as in "charts and graphs", but "graph" as in "computer science graph". Nodes and edges:

simple

Today, we'll see how to easily generate images like this. Along the way, we'll see an interesting use case for anonymous objects.

The Dot File Format

The image above can be generated from the following text file, using a GraphViz tool called dot.exe. Note the use of the word 'digraph'. A graph is anything made up of nodes and edges; a digraph is a graph where the edges have direction from a source node to a destination node.

To create the image from the dot file, run the command: dot.exe Simple.dot -Tpng -o Simple.png.

The dot file format supports a lot of options. For instance, we can customize the original dot file like so:

Optional attributes appear in square brackets. Here, we give one of our edges a label, alter the colors, and change one of the nodes from an ellipse to a box:

fancy

One can imagine generating these dot files programmatically with a StringBuilder. Doing so would be a little monotonous, especially when the optional attributes like shapes and colors are included. You have to busy yourself with the dot format's quoting rules as well as C#'s quoting rules. Even when you succeed, you'll be left with some ugly code. I'd much rather generate the above two dot files like so:

Implementing the Digraph Class

First, it'll be handy to define an extension method to apply the dot quoting rules to any .NET value. The double-quote character is special in dot files and must be preceded with a slash. Of course, double-quotes and slashes are also meaningful in C# strings, so we have to double-escape these in the substitution:

Next, take a look at the way the optional parameters were supplied in the earlier code sample: new { label = 1, color="red" }. This is an anonymous object. It creates an instance of a class whose name we never see, with read-only properties named "label" and "color". We pass this to a method that doesn't know what type is being passed to it, but that's OK because we can use reflection to inspect such an object. Here's a helper that can look at these anonymous objects and turn them into an equivalent dictionary, whose ToString produces the dot syntax for attributes:

I could have instead provided a real named class with all the possible dot properties already defined. Doing so would be useful as it would give the developer statement completion suggestions, but the dot file format is actually quite extensive. In this case, it's a very reasonable tradeoff to use anonymous objects and reflection for these node and edge attributes. Even with statement completion, you'd find yourself frequently looking up details in the documentation anyway, so the return on investment would be ridiculous.

We can represent a single node in the graph like so:

An Edge is defined by the two Nodes it connects:

Armed with all of the above classes, we can finally implement the Digraph class with ease:

Criticism

Since the scope of this sample program is entirely devoted to generating the dot file format, I don't mind the fact that I mixed state (node and edge definitions) with presentation (string formatting in each ToString override). An earlier implementation put all of the string formatting in the Digraph class, but it was fairly ugly. If the requirements expanded such that we needed to use the node/edge structure for things in addition to presentation in dot format, I would want to move all string formatting out of the Node, Edge, and Digraph classes, having a dedicated class just for traversing a Digraph to produce a dot file.