Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Using Graphviz in your project to create graphs from dot files

4.36/5 (11 votes)
5 Jan 2017CPOL4 min read 55.9K   1.8K  
Graphviz graphs from dot specification

Introduction

This article describes how Graphviz can be incorporated in your project to show graphs describing your state machine / process for example.

Image 1

Background

The input for Graphviz is a dot file. this simple text file describes the graph layout in a textual manner with it's own grammar: https://en.wikipedia.org/wiki/DOT_(graph_description_language)

Using the code

Basically there are two ways to incorporate the graphviz functionality:

  1. Calling the external program dot.exe supplied by Graphviz and perform data transfer over the file system
  2. Wrapping the libraries of Graphviz and perform in memory data transfer

I will show both methods based on Graphviz 2.38.

Download  and extract the Graphviz windows release

Download the release here: file

This file contains the libraries and dot.exe a command line program to generate from a dot graph files an image file.

After download extract the folder: /release/bin. This folder contains dot.exe and related libraries running on windows. This is important because these libraries are build against the right font directories which is of importance when you want to have an aligned and nice looking graph in the end.

Examples of problems that occur when using not the right libraries which can be found in the folder: /release/lib/release/dll. These libraries are build not against the right font folders.

Image 2

From the /release/bin folder extract the following files:

Image 3

These are needed to run dot.exe and generate the images from dot graphs.

Testing graph vizualisation

Now we are going to test is everything is working fine

First open a command window in the folder and run from the command line: dot.exe –c

This will cause dot.exe to update the config6 file which contains the administration of available libraries which can be used by GraphViz. If this file is not available of not up to date, then GraphViz (gvc.dll) will malfunction and cause exceptions.

Now we can test dot.exe on the command line by creating a dot file in notepad with the following contents:

C++
digraph g{
A->B
label ="Graph";
labelloc = top;
labeljust = left;
}

And save it as testgraph.dt (use .dt instead of calling it .dot since it will cause windows to recognize it as an office template).

Now run from the command line: dot.exe –Tjpg –O testgraph.dt

When everything works fine a jpg file is created: testgraph.dt.jpg (so .dt is not really needed)

Image 4

If this works fine than we can proceed.

Adding the files to your visual studio project

In both cases we have to add the needed external (Graphviz files) to the visual studio project.

To do this, do the following:

  1. Create a map in you visual studio project and call it ‘external’
  2. Then via file explorer copy all files mentioned above including dot.exe and config6
  3. Next in visual studio go to the map and right click to add existing items
  4. Go to the folder external and change the file type from ‘Visual C# files’ to all files (*.*)

    Image 5

                Now the files will be shown, select all (including dot.exe and config6) and select add.

The needed external files are now added to your project and we can continue to work out both methods.

Option 1, using the external dot.exe program

Visual studio has added the needed external files to the project, but they won’t be copied during built. Therefore select them all including dot.exe and set the properties to ‘copy always’

Image 6

Now with each built the support files including dot.exe are copies to the output folder under /external.

(dot.exe can be run from the command line)

Basically we have an input:

C++
String graphVizString = @" digraph g{ label=""Graph""; labelloc=top;labeljust=left;}";

And we want to end with a bitmap. So we have to make a class called ‘FileDotEnginewhich looks as:

C#
public static class FileDotEngine
    {
        public static Bitmap Run(string dot)
        {
            string executable = @".\external\dot.exe";
            string output = @".\external\tempgraph";
            File.WriteAllText(output, dot);

            System.Diagnostics.Process process = new System.Diagnostics.Process();

            // Stop the process from opening a new window
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.CreateNoWindow = true;

            // Setup executable and parameters
            process.StartInfo.FileName = executable;
            process.StartInfo.Arguments = string.Format(@"{0} -Tjpg -O", output);

            // Go
            process.Start();
            // and wait dot.exe to complete and exit
            process.WaitForExit();
            Bitmap bitmap = null; ;
            using (Stream bmpStream = System.IO.File.Open(output + ".jpg", System.IO.FileMode.Open))
            {
                Image image = Image.FromStream(bmpStream);
                bitmap = new Bitmap(image);
            }
            File.Delete(output);
            File.Delete(output + ".jpg");
            return bitmap;
        }
    }

On the calling side only the following code is needed to use it as a bitmap:

C#
Bitmap bm = FileDotEngine.Run(graphVizString);

When calling FileDotEngine.Run your application will start an external process (dot.exe) pass the string as input file to it. Then dot.exe will create the image on disk which is read afterwards by your application again and imported as bitmap.

Option 2, using wrapper and call the libraries directly

Visual studio has added the needed external files to the project, but they won’t be copied during built. Therefore select them all except for dot.exe and set the properties to ‘copy always’

Image 7

Now with each built the support files excluding dot.exe are copies to the output folder under /external.

(dot.exe can't be run from the command line)

Now create a new class and call it: ‘Graphviz’ with the following content:

C#
    public static class Graphviz
    {
        public const string LIB_GVC = @".\external\gvc.dll";
        public const string LIB_GRAPH = @".\external\cgraph.dll";
        public const int SUCCESS = 0;

        ///
        /// Creates a new Graphviz context.
        ///
        [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr gvContext();

        ///
        /// Releases a context's resources.
        ///
        [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)]
        public static extern int gvFreeContext(IntPtr gvc);

        ///
        /// Reads a graph from a string.
        ///
        [DllImport(LIB_GRAPH, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr agmemread(string data);

        ///
        /// Releases the resources used by a graph.
        ///
        [DllImport(LIB_GRAPH, CallingConvention = CallingConvention.Cdecl)]
        public static extern void agclose(IntPtr g);

        ///
        /// Applies a layout to a graph using the given engine.
        ///
        [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)]
        public static extern int gvLayout(IntPtr gvc, IntPtr g, string engine);

        ///
        /// Releases the resources used by a layout.
        ///
        [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)]
        public static extern int gvFreeLayout(IntPtr gvc, IntPtr g);

        ///
        /// Renders a graph to a file.
        ///
        [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)]
        public static extern int gvRenderFilename(IntPtr gvc, IntPtr g,
              string format, string fileName);

        ///
        /// Renders a graph in memory.
        ///
        [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)]
        public static extern int gvRenderData(IntPtr gvc, IntPtr g,
             string format, out IntPtr result, out int length);

        ///
        /// Release render resources.
        ///
        [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)]
        public static extern int gvFreeRenderData(IntPtr result);


        public static Image RenderImage(string source, 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, "dot") != 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);
            agclose(g);
            gvFreeContext(gvc);
            gvFreeRenderData(result);
            using (MemoryStream stream = new MemoryStream(bytes))
            {
                return Image.FromStream(stream);
            }
        }
    }

This content has been found (so i'm not the author of this piece of code) on the internet and completed/corrected to do what is needed. It is a wrapper around the graphviz libraries (gvc and cgraph) which do the work.

On the calling side only the following code is needed to use it as a bitmap:

C#
Bitmap bm = new Bitmap(Graphviz.RenderImage(graphVizString, "jpg"));

To my opinion the second option is a better solution since the file system is not being used, but debugging is worse since dot.exe (option 1) can give you more insights.

Using this code gives you the following result for an example:

Image 8

>

Points of Interest

The issue with the fonts (misalignment of the text) were a nightmare to find out the issue. Nothing is/has been written on the internet so far.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)