« Valve Map Format Utility Library v1.0 Released | Main | Managed NovaCore SDK is up on CodePlex! »
Thursday
24Dec2009

Rendering an in-memory Graphviz image with C#

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:

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (8)

Wow!
That was REALLY helpfull :)
Gonna try it now =)

Thanks!

January 6, 2010 | Unregistered Commenteryoel shoshan

David,
Thanks for this page. It was very helpful.
A couple of minor typo's in the page I think:
< public const string LIB_GVA = "gva.dll"; > should be < public const string LIB_GVC = "gvc.dll"; >
The string dot contains a node "graph" which is not rendered, I guess because it is a reserved word. If you allchange it to "zgraph" it is rendered.
When I display this in a C# winform on XP, the graph is rendered but all the characters in the node labels come out as blocks. Any ideas? I wonder if this is to do with unicode?

January 29, 2010 | Unregistered CommenterMichael Hickey

Michael,

Thanks for pointing out the errors. I've fixed the const typo and will re-upload a new image showing the missing graph node soon.

As for your font problem, have you tried specifying a default font attribute for your graph? It could be that Graphviz is trying to render with a font that doesn't exist on your installation (I'm just throwing ideas out there, as I'm not a Graphviz expert by any means). Other than that, I don't know what the problem could be. If I get a chance, I'll spin up an XP VM and try it out.

Do you get the same results using the dot program that comes with Graphviz?

January 29, 2010 | Registered CommenterDavid Brown

Good, exactly what was required! Just make sure you have Graphviz 'bin' folder in your PATH variable on your IIS server if that's what your using this code for. You may have to restart the IIS server.

February 9, 2010 | Unregistered CommenterBen Druitt

A side question: wonder if you have any ideas if UTF8 encoded strings are supported? what i mean is passing a byte[] parameter instead of a normal C# string into the function

March 2, 2010 | Unregistered CommenterTerry Wong

@Terry: You can make an overload for agmemread that accepts a byte array. It worked fine when I tried it on my end. :)

March 3, 2010 | Registered CommenterDavid Brown

Thanks for your answer David. I am now trying to figure out how to apply different fonts into the graph, so i tried to build a string as follow:

string g = "digraph Graphviz { charset=\"utf-8\", node[shape=ellipse, fontname=\"Courier\", fontsize=8], test1->test2}"

The rendering result in empty nodes, probably due to some font mapping problem. I noticed that there is a fonts.conf file in the msi install directory \etc\fonts , however the configuration only applies on the windows executable. Wonder if you got any idea to apply the config onto the web app package?

Cheers!

March 4, 2010 | Unregistered CommenterTerry Wong

Your DOT string works fine for me. I'm not entirely sure how fontconfig works or how Graphviz is loading the configuration file, though. Looking at the source code, I think it's being left up to whichever rendering plugin you're using. I had to change the comma after the default attribute list to a semicolon to get Graphviz to output an image, by the way.

March 4, 2010 | Unregistered CommenterDavid Brown

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>