Drawing Directed Acyclic Graphs: Minimizing edge crossing?
Asked Answered
E

2

18

Laying out the verticies in a DAG in a tree form (i.e. verticies with no in-edges on top, verticies dependent only on those on the next level, etc.) is rather simple without graph drawing algorithms such as Efficient Sugiyama. However, is there a simple algorithm to do this that minimizes edge crossing? (For some graphs, it may be impossible to completely eliminate edge crossing.) A picture says a thousand words, so is there an algorithm that would suggest something without crossing edges. (compared to this).

EDIT: The result

I've accepted Senthil's suggesting graphviz/dot -- a quick look at the docs confirms that it's very easy to use it as a library or external tool, and the output format is surprisingly easy to parse. However, I ended up choosing to use GraphSharp instead since I'm already using .NET, etc (though it's definitely not as powerful as dot). The result is "good enough", and could be made a lot better with a little edge routing and tweaking (the blurry text is because of 3.5 WPF).

Automatically layed-out graph http://public.blu.livefilestore.com/y1pEY8I95GtlzcxZzhDMhhKoUyejT_sVVZ4jlsDK2fdl6XAR4WV4-yuSesY6chXokmAZxdJXZ4Bv674TqwpT1-fOg/dag3.gif

Here's the complete C# code (this is all the code that references either QuickGraph or GraphSharp -- yeah; it was that easy):

internal static class LayoutManager
{
    private const string ALGORITHM_NAME = "EfficientSugiyama";
    private const bool MINIMIZE_EDGE_LENGTH = true;
    private const double VERTEX_DISTANCE = 25;
    private const double LAYER_DISTANCE = 25;
    private const double MIN_CANVAS_OFFSET = 20;

    public static void doLayout(GraphCanvas canvas)
    {
        // TODO use a background thread
        // TODO add comments
        canvas.IsEnabled = false;
        canvas.Cursor = Cursors.Wait;
        var graph = new BidirectionalGraph<GraphNode, LayoutEdge>();
        var positions = new Dictionary<GraphNode, Point>();
        var sizes = new Dictionary<GraphNode, Size>();
        foreach(var node in canvas.nodes)
        {
            var size = node.RenderSize;
            graph.AddVertex(node);
            positions.Add(node, new Point(node.left + size.Width / 2, node.top + size.Height / 2));
            sizes.Add(node, size);
        }
        foreach(var edge in canvas.edges)
        {
            graph.AddEdge(new LayoutEdge(edge));
        }

        var context = new LayoutContext<GraphNode, LayoutEdge, BidirectionalGraph<GraphNode, LayoutEdge>>(graph, positions, sizes, LayoutMode.Simple);
        var parameters = new EfficientSugiyamaLayoutParameters();
        parameters.VertexDistance = VERTEX_DISTANCE;
        parameters.MinimizeEdgeLength = MINIMIZE_EDGE_LENGTH;
        parameters.LayerDistance = LAYER_DISTANCE;
        var factory = new StandardLayoutAlgorithmFactory<GraphNode, LayoutEdge, BidirectionalGraph<GraphNode, LayoutEdge>>();
        var algorithm = factory.CreateAlgorithm(ALGORITHM_NAME, context, parameters);
        algorithm.Compute();
        canvas.deselectAll();

        var minx = algorithm.VertexPositions.Select(kvp => kvp.Value.X - (kvp.Key.RenderSize.Width / 2)).Aggregate(Math.Min);
        var miny = algorithm.VertexPositions.Select(kvp => kvp.Value.Y - (kvp.Key.RenderSize.Height / 2)).Aggregate(Math.Min);
        minx -= MIN_CANVAS_OFFSET;
        miny -= MIN_CANVAS_OFFSET;
        minx = minx < 0 ? -minx : 0;
        miny = miny < 0 ? -miny : 0;
        foreach(var kvp in algorithm.VertexPositions)
        {
            var node = kvp.Key;
            var pos = kvp.Value;
            node.left = (pos.X - (node.RenderSize.Width / 2)) + minx;
            node.top = (pos.Y - (node.RenderSize.Height / 2)) + miny;
        }
        canvas.Cursor = Cursors.Arrow;
        canvas.IsEnabled = true;
    }

    private sealed class LayoutEdge : IEdge<GraphNode>
    {
        private readonly ConnectingEdge _edge;
        public LayoutEdge(ConnectingEdge edge) { _edge = edge; }
        public GraphNode Source { get { return _edge.output.node; } }
        public GraphNode Target { get { return _edge.input.node; } }
    }
Enrapture answered 17/5, 2010 at 21:47 Comment(2)
I thought the Sugiyama method is supposed to minimize edge crossings? Maybe the "Efficient" version you mentioned disregards that step.Lituus
@Cory Larson - Yup; I could use a layout algorithm like that, but I was wondering if there were any that could take advantage of the acyclic properties of the graph (and would always place vertexes above those dependent upon them)Enrapture
B
9

Dot seems like it would fit the bill:

dot - ``hierarchical'' or layered drawings of directed graphs. The layout algorithm aims edges in the same direction (top to bottom, or left to right) and then attempts to avoid edge crossings and reduce edge length.

https://docs.google.com/viewer?url=http://www.graphviz.org/pdf/dotguide.pdf

Barnwell answered 18/5, 2010 at 3:42 Comment(4)
Awesome; it looks perfect -- Hmmm... how to integrate graphviz with WPF control positioning (other than rendering a bitmap then scanning the pixels).Enrapture
@Robert, look at graphviz.org, there are numerous implementations of the dot algorithm, maybe one implementation will play well with the dotNET environment. Alternatively you can run the dot.exe program, and have it output a set of coordinates which you can use to place controls in WPF.Jot
@Elazar -- Awesome, thanks! It looks like the "plain-ext" output format is very easy to parse, and .dot files are simple to generate (but I ended up going with a different solution; see updated answer).Enrapture
I know that I am almost three years late, but dot is not good in avoiding edges crossing. In medium to large size graphs, specially if you do use clusters, it is very easy to get horrible drawings with many line crossings that could easily be eliminated by hand. Further, dot layouts are highly unstable: if you add or remove a edge or node, or even if you change just edge labels or node labels, dot may render you graph in a completely different way, turning useless all that hacks that you were forced to use to try to convince it do the right thing before.Finnougrian
E
5

You could try using Topological Sorting. In a first step you can determine the levels (top to bottom) of the layout by performing a topological sort and always grouping independent nodes in a single layer. This will always succeed for directed acyclic graphs.

Then you could maybe try to perform a topological sort of each layer (left to right) taking the location of the input and output ports and probably adjacent layers into account. My image of this step is bit blurry but I can imagine that it is doable for graphs like your example.

Etsukoetta answered 17/5, 2010 at 22:44 Comment(2)
It's blurry because it's hard. Consider an example with only two layers. That's a bipartite graph, and you'd like to minimise the number of crossings between them by changing the order of the vertices in each layer. You could try a greedy algorithm: find a pair to swap which reduces the number of crossings. But in general you'll get stuck in a local minimum: at some point there will not be any more swaps of two vertices that reduce the number of crossings, but the answer won't be optimal: i.e. permuting three or more vertices will give you a better answer.Orobanchaceous
The problem of reducing bipartite crossing number to a minimum is NP-hard. See page 2 of Newton et al. 2005 New Exact Results and Bounds for Bipartite Crossing Numbers of Meshes (Click on "Look Inside" to see the first 2 pages.) link.springer.com/chapter/10.1007%2F978-3-540-31843-9_36Orobanchaceous

© 2022 - 2024 — McMap. All rights reserved.