I've been working on a little side-project that involves a lot of graphing. Graphviz has been the most complete solution, but existing options for using it with .NET were rather scarce. WinGraphviz is based on an older version of Graphviz and hasn't been improved since 2004. Plus, it's a COM component, which is yet another thing you have to install on the end-user's machine.
It turns out, however, that the Graphviz library is fairly simple to understand. Once I got the correct DLL configuration copied to my application's directory, the rest of the P/Invoke stuff was easy. On top of it all, Graphviz includes the ability to render an image in-memory, so there's no need to write to the disk and read the result back again. Writing to the disk was the biggest performance bottleneck for my application, so the in-memory rendering feature was a huge relief.
To get started, we need a class to hold our P/Invoke methods and a few helper constants.
public class Graphviz {
public const string LIB_GVC = "gvc.dll";
public const string LIB_GRAPH = "graph.dll";
public const int SUCCESS = 0;
}
To use Graphviz as a library, we first have to create a Graphviz context, which the other methods will use. Add these to our Graphviz class.
/// <summary>
/// Creates a new Graphviz context.
/// </summary>
[DllImport(LIB_GVC)]
public static extern IntPtr gvContext();
/// <summary>
/// Releases a context's resources.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeContext(IntPtr gvc);
Now we need some methods to load and close a graph. These are found in graph.dll.
/// <summary>
/// Reads a graph from a string.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern IntPtr agmemread(string data);
/// <summary>
/// Releases the resources used by a graph.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern void agclose(IntPtr g);
Once we have our graph loaded, we need to apply a layout.
/// <summary>
/// Applies a layout to a graph using the given engine.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvLayout(IntPtr gvc, IntPtr g, string engine);
/// <summary>
/// Releases the resources used by a layout.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeLayout(IntPtr gvc, IntPtr g);
Once the graph has a layout, we can render it.
/// <summary>
/// Renders a graph to a file.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderFilename(IntPtr gvc, IntPtr g,
string format, string fileName);
/// <summary>
/// Renders a graph in memory.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderData(IntPtr gvc, IntPtr g,
string format, out IntPtr result, out int length);
Now, you may be thinking that you can just copy gvc.dll and graph.dll over and all will be well. But, sadly, that's not the case. Those DLLs have a few dependencies. You'll need all of the following from your Graphviz installation's /bin directory:
Graphviz loads layout and rendering plugins dynamically using a configuration file, which must also be included alongside your application. If you installed Graphviz using the MSI, there should already be a configuration file in the /bin directory. Mine was called config6, but I'm not sure if the name varies. If you don't have a configuration file, you can generate one with dot -c.
Now that we have the necessary DLLs, we can actually do some rendering!
/// <summary>
/// Renders a DOT string to the specified image format.
/// </summary>
public static byte[] RenderDot(string dot, string format) {
// Create a Graphviz context
IntPtr gvc = gvContext();
// Load the DOT data into a graph
IntPtr g = agmemread(dot);
// Apply a layout
gvLayout(gvc, g, "dot");
IntPtr result;
int length;
// Render the graph
gvRender(gvc, g, format, out result, out length);
// Create an array to hold the image
byte[] image = new byte[length];
// Copy the image from the IntPtr
Marshal.Copy(result, image, 0, length);
// Free up the resources
gvFreeLayout(gvc, g);
agclose(g);
gvFreeContext(gvc);
return image;
}
Here's the code in action, rendering a graph of the Graphviz DLL dependencies:
string dot = @"digraph Graphviz {
cdt -> msvcr80, cdt -> kernel32,
graph -> cdt, graph -> msvcr80, graph -> kernel32,
gvc -> libexpat, gvc -> ltdl, gvc -> zlib1, gvc -> cdt,
gvc -> pathplan, gvc -> graph, gvc -> msvcr80, gvc -> kernel32,
ltdl -> msvcrt, ltdl -> kernel32,
pathplan -> msvcr80, pathplan -> kernel32,
zlib1 -> msvcrt, zlib1 -> kernel32,
libexpat -> kernel32
}";
byte[] image = Graphviz.RenderDot(dot, "png");
And the result: