Tuesday, August 21, 2012

Using Graphviz library in .NET

GraphViz is a cool & powerful open source graphics library. In my case it might have just saved a large amount of effort, if we were to build it from scratch. So, I thought I will share the approach we followed in our project, so that it can help others..,

Installing GraphViz:

  • Download the Windows MSI file for windows from http://www.graphviz.org/Download.php
  • Backup the Path variable contents (Note: As a rule of thumb you should be doing this each time when you install a new software and the install could mess up your system variables)
  • After installing the tool, you might have to log-off and log in for the path variable change to kick in (or reboot)

Add the GraphViz interface class to your solution:

I found this class somewhere in the internet, (I forgot where I got from), and I have added few methods to make it work for my requirement.

     public static class Graphviz {
        public const string LIB_GVC = "gvc.dll";
        public const string LIB_GRAPH = "graph.dll";
        public const int SUCCESS = 0;

        /// Creates a new Graphviz context.

        public static extern IntPtr gvContext();

        /// Releases a context's resources.
        public static extern int gvFreeContext(IntPtr gvc);

        /// Reads a graph from a string.
        public static extern IntPtr agmemread(string data);

        /// Releases the resources used by a graph.
        public static extern void agclose(IntPtr g);

        /// Applies a layout to a graph using the given engine.
        public static extern int gvLayout(IntPtr gvc, IntPtr g, string engine);

        /// Releases the resources used by a layout.
        public static extern int gvFreeLayout(IntPtr gvc, IntPtr g);

        /// Renders a graph to a file.
        public static extern int gvRenderFilename(IntPtr gvc, IntPtr g,
              string format, string fileName);

        /// Renders a graph in memory.
        public static extern int gvRenderData(IntPtr gvc, IntPtr g,
              string format, out IntPtr result, out int length);

        public static Image RenderImage(string source, string layout, string format) {
              // Create a Graphviz context
              IntPtr gvc = gvContext();
              if (gvc == IntPtr.Zero)
                    throw new Exception("Failed to create Graphviz context.");

              // Load the DOT data into a graph
              IntPtr g = agmemread(source);
              if (g == IntPtr.Zero)
                    throw new Exception("Failed to create graph from source. Check for syntax errors.");

              // Apply a layout
              if (gvLayout(gvc, g, layout) != SUCCESS)
                    throw new Exception("Layout failed.");

              IntPtr result;
              int length;

              // Render the graph
              if (gvRenderData(gvc, g, format, out result, out length) != SUCCESS)
                    throw new Exception("Render failed.");

              // Create an array to hold the rendered graph
              byte[] bytes = new byte[length];

              // Copy the image from the IntPtr
              Marshal.Copy(result, bytes, 0, length);

              // Free up the resources
              gvFreeLayout(gvc, g);

              using (MemoryStream stream = new MemoryStream(bytes)) {
                    return Image.FromStream(stream);

Code to write to file:

            //Convert to byte stream
            Byte[] image = Graphviz.RenderImage(graphVizString, "dot", "png");

            currentGUIDo = Guid.NewGuid().ToString();
            strCurrentFileName = ConfigurationManager.AppSettings["ImagePath"].ToString() + currentGUIDo +".png";
            FileStream file = new FileStream(strCurrentFileName, FileMode.Create);
            //Write the image to the file
            file.Write(image, 0, image.Length);
            //close the file
        catch (Exception ex)
            //TODO:Impelement exception handling

Finally, I had a button where this will be called , and just transmit the file:

if (File.Exists(strCurrentFileName))

Sample values you could use for graphVizString:

Sample 1:
digraph {
 Entity1 -> Entity2, Entity1 -> Entity3

Entity1 [shape="circle"] , Entity2 [shape="rectangle"]

Sample 2:
digraph {
   a -> b [label=relation1],b->c[label=relation2],c->d[label=relation3],e->c [label=relation4]

a [shape=box,color=red,label="Box-A"], b[shape=triangle,color=yellow],e [shape=invtriangle],c [shape=square],d[shape=rectangle]

Produces an output:

Sample 3:

digraph {
   a -> b [label=relation1],b->c[label=relation2],c->d[label=relation3],e->c [label=relation4], e->d [label=relation5]

a [shape=box,color=red,label="Box-A"],
b [shape=triangle,color=yellow, label="Triangle-B"],
e [shape=invtriangle, label="Inverse Triangle-C"],
c [shape=square,label="Square - C"],
d [shape=rectangle,label="Rectangle - D"]


shoaib Ch said...

it looks helpful but i couldnt get the Code to write to file: section which file it is and how to set the paths

Kiquenet said...