Click here to Skip to main content
Click here to Skip to main content

Dot2WPF - a WPF control for viewing Dot graphs

, 13 Jun 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
A fast and smooth WPF viewer for graphs layouted by GraphViz (Dot)

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 were 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
  • 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

  • May 21, 2007 -- 0.2.0.0, first public release
  • June 13, 2007 -- 0.3.0.0, added print support

License

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

Share

About the Author

Christian Rodemeyer
Architect Kabel Deutschland
Germany Germany
I'm developing for fun since 1985, starting with UCSD Pascal on some old machines (no harddisk, but four floppies!), then moving quickly on to assembler on the famous C64 and Amiga. During university I started professional development for Windows/Unix/Linux, using a myriad of languages (Pi, 386/486, Cobol, Modula2, OML, C, C++, VB, Prolog, Eiffel, Delphi, Perl, Pascal, Assembler). Currently my favorite languages are C# 3.0 and Python.

Comments and Discussions

 
Questionbackground map Pinmemberjianyunli20-Mar-14 9:58 
QuestionFork created on Blade.Net PinmemberMember 408378310-Feb-14 2:02 
GeneralMy vote of 5 Pinmembermmike7429-Dec-10 7:08 
Generalarrow head directions PinmemberHHick123453-Jun-10 23:12 
Generalno scroll wheel on laptops PinmemberHHick1234531-May-10 6:30 
Generalplain file compatibility PinmemberHHick1234531-May-10 6:03 
GeneralGreat work!! PinmemberHHick1234511-May-10 0:31 
GeneralRe: Great work!! PinmemberHHick123453-Jun-10 22:54 
Generalanother quick question.. PinmemberMansur Ehmad15-Oct-09 0:52 
GeneralRe: another quick question.. PinmemberChristian Rodemeyer18-Oct-09 12:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141216.1 | Last Updated 13 Jun 2007
Article Copyright 2007 by Christian Rodemeyer
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid