Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dot2WPF - A WPF Control for Viewing Dot Graphs

0.00/5 (No votes)
13 Jun 2007 4  
A fast and smooth WPF viewer for graphs layouted by GraphViz (Dot)
This article shows you how easy it is to do owner drawn graphics in WPF. The viewer of this sample also has some advantages over existing viewers.

Screenshot - dot2wpf_small.png

Screenshot - dot2wpf_zoomed.png

Introduction

I am presenting a WPF control for viewing graphs rendered by GraphViz (Dot). The DotViewer control has the usual navigation -- i.e., zoom, drag, scroll -- and supports hit testing on nodes, which is used for displaying tooltips. In the first place, this article should show you how easy it is to do "owner drawn graphics" in WPF. As it turns out, the viewer of this sample also has some advantages over existing viewers:

  • It is several times faster, probably because WPF is hardware accelerated.
  • Because WPF works vector-oriented, zooming is fast and produces smooth, good looking pictures.
  • Nodes can be found by mouse position, which makes user interactions possible (tooltips, selections).
  • You can easily integrate it into your own .NET applications.
  • It works with huge graphs; for example 350 nodes with 2600 edges -- i.e., more than 53000 Bezier points -- can be displayed and zoomed nearly delay-free.

I suggest that you now download the sample and play with it a little before continuing.

Motivation

Currently, I am working on a project that is packaged in over 500 assemblies. To understand this packaging better, I wrote a little Python script that analyses the assembly dependencies and generates a graph description for GraphViz. It uses Dot to render a GIF image and displays it with the standard windows image viewer. This works well for small graphs. The first time I tried to view the graph of the complete system, however, I got images so big -- more than 80000 pixels wide -- that it took minutes to load, display, zoom and scroll them. I tried other viewers but wasn't really satisfied due to their speed, usability and the possibility of using them in my application. Also, the printing capabilities are very limited. I found no easy way to print large graphs on several pages. So for a while, I viewed only subgraphs, arranged for my current needs and never seeing the big picture. Then I remembered TechEd 2006 in Barcelona, where I saw some really impressive WPF demos. Instantly, I knew: this was the right problem to try WPF with. If it is really so cool, as Microsoft emphasized, it should be no problem to make my own lightning-fast viewer. And lo and behold: Microsoft was right!

Background

  • GraphViz Homepage - a very good and free tool for graph visualization, used as the layout engine for this project
  • MSDN DrawingVisual sample - this gave me a quick start into using visuals for high performance drawing

Alternatives

  • QuickGraph - A graph library with additional GDI+ based GraphViz support
  • Glee - a Microsoft Research project that does the layout and rendering of graphs, also based on GDI+

Sample Application

The solution contains two projects. The Visualizing project contains the DotViewer control. The Dot2Wpf project is just a simple wrapper application that hosts the DotViewer control. It allows you to open files in the .plain format produced by Dot. Dot2Wpf contains several samples, so you don't need to install the GraphViz package if you just want to play around a bit. If you have GraphViz installed, you can create the .plain output from .dot files with this command:

dot -Tplain -o "graph.plain" "graph.dot"

You can open a file by pressing Ctrl+O or by clicking the button in the upper left. Use the mouse wheel to zoom the graph. If the graph gets too big, move it with the scrollbars or drag it with the right mouse button. You can select a node by clicking it. If you hover the mouse over a node, it will display a tooltip. Just in case you are wondering about the meaning of my graph samples:

  • The node color indicates the area from and to which the assembly is assigned.
  • The size of the node text is proportional to the code size of the assembly.
  • Orange edges indicate dependencies that are defined at compile time, but not used at runtime.

The DotViewer Control

The DotViewer control is contained in the Visualizing project. It is a simple UserControl composed of a standard ScrollViewer and some floating TextBlocks. The ScrollViewer itself contains the GraphElement, which is derived from FrameworkElement and is my host for the visuals that draw the entire graph. You can use the DotViewer in your own applications by simply adding it to a panel. If you are using XAML, you probably want to define a custom namespace that allows you to write something like the following:

<Window xmlns:r=
    "clr-namespace:Rodemeyer.Visualizing;assembly=Rodemeyer.Visualizing"

[...]

<Grid>
    <r:DotViewer x:Name="MyDotViewer"></r:DotViewer>
</Grid>

After the control has been loaded -- wait for the Loaded event of the hosting window -- you can call LoadPlain to load a .plain graph file. If you want to supply tooltips for nodes, you have to subscribe the ShowNodeTip event. NodeTipEventArgs has a Tag attribute that identifies the node. It is the nodeID from the .dot file. Assign the Content attribute your tooltip content. It can be arbitrary WPF content, but most probably, you will use a TextBlock element.

The GraphElement uses the GraphLoader class to read the .plain output of Dot and create the visuals displaying the graph. Because shapes are not very efficient when there are many of them, I am using visuals. Visuals don't support high-end stuff like data binding triggers, but they are very performant and still have the ability to do hit testing via VisualTreeHelper. The graph is represented by a DrawingVisual with children. It directly contains all edges. Every node is a child DrawingVisual, tagged with the nodeID from the original .dot file. This is necessary to distinguish between the nodes when hit testing.

Printing and Paginating

Graphs are frequently huge and if you print them on one page, the text is often unreadably small. With GraphViz, I had real problems with printing big graphs. I had to render into the PS format and used Adobe Distiller to manually distribute the output over several pages. This was a very time-consuming process. WPF uses a DocumentPaginator in its PrintDocument method and I hoped I could use this class to do my own paginating.

In reality, DocumentPaginator is just an abstract class that does nothing. But by overriding the GetPage method and the PageCount property, I was able to print my graph visual on several pages. The constructor of my GraphPaginator class gets the visual and the size of one printed page. The first problem I needed to solve was getting a copy of the original visual. I found no way to do this, so I created a new visual and used the DrawDrawing to draw the Drawing properties of each visual. My new visual could now be transformed and clipped as I wanted, without changing the original visual. All that the GetPage method now had to do was translate to the proper page position and clip everything that didn't belong to this page. Because I wanted to draw glue marks on every page, I used the same trick as before and created a new visual. I drew the glue marks on it and then the clipped part of the graph that I needed.

public override DocumentPage GetPage(int pageNumber)
{
    int x = pageNumber % pageCountX;
    int y = pageNumber / pageCountX;

    Rect view = new Rect();
    view.X = x * contentSize.Width;
    view.Y = y * contentSize.Height;
    view.Size = contentSize;

    DrawingVisual v = new DrawingVisual();
    using (DrawingContext dc = v.RenderOpen())
    {
        dc.DrawRectangle(null, framePen, frameRect);
        dc.PushTransform(
            new TranslateTransform(margin - view.X, margin - view.Y));
        dc.PushClip(new RectangleGeometry(view));
        dc.DrawDrawing(graph);
    }
    return new DocumentPage(v, PageSize, frameRect, frameRect); 
}

Points of Interest

  • I had to implement my own ToolTip service because the standard service allows only one tooltip per UIElement. Because only one UIElement (GraphElement) is responsible for rendering the complete graph, the standard ToolTipService was not suitable.
  • With WPF Bezier methods, it was extremely easy to render the Dot output, just a few dozen lines of code. In fact, the most difficult part was to draw the arrowheads. I had to normalize a vector, rotate it and then scale it a bit to get the desired effect. Thank God WPF has a Vector class at last!
  • Most WPF books tell you that you need a HostElement to display a DrawingVisual. This is true, but you need to do your own layout code in MeasureOverride and ArrangeOverride. Without doing so, WPF doesn't know how big your element is and your element probably won't behave as you expect!
  • I tried to rotate the visual before printing at 90 degrees to do my own landscape orientation. However, I got some very ugly text output as a result. Printing to the XPS printer was fine and as expected, but printing to a real (PCL) printer made garbage of my text. So I override the Page orientation, regardless of what the user selects. This also works on real printers.

Limits

Currently, DotViewer supports only the .plain output format of Dot. This means that:

  • Every node is rendered as an ellipse.
  • All edges are interpreted as arrows.
  • There are no edge labels.
  • The font is hard-coded as Verdana, so if you want another, you will have to change this in the code.

Future

I want to relax the limits of the .plain format and switch to annotated Dot. I hope that this will allow me to render any valid .dot graph.

History

  • 21st May, 2007 -- 0.2.0.0, First public release
  • 13th June, 2007 -- 0.3.0.0, Added print support

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here