From 23ca31e7979fb428e196278b2e1d732855faf4f4 Mon Sep 17 00:00:00 2001 From: Edgardo Zoppi Date: Thu, 5 Jan 2017 20:23:36 -0300 Subject: [PATCH] Fixing SimpleTree layout algorithm by correctly aligning the vertices. (#76) Fix tree layout Adding optional gap between connected components. --- .../Tree/SimpleTreeLayoutAlgorithm.cs | 109 ++++++++++++------ .../Tree/SimpleTreeLayoutParameters.cs | 15 +++ 2 files changed, 86 insertions(+), 38 deletions(-) diff --git a/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutAlgorithm.cs b/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutAlgorithm.cs index da88c33..5d3be4b 100644 --- a/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutAlgorithm.cs +++ b/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutAlgorithm.cs @@ -43,17 +43,38 @@ namespace GraphX.PCL.Logic.Algorithms.LayoutAlgorithms _direction = 1; GenerateSpanningTree(cancellationToken); - //DoWidthAndHeightOptimization(); + //DoWidthAndHeightOptimization(); - //first layout the vertices with 0 in-edge - foreach ( var source in SpanningTree.Vertices.Where( v => SpanningTree.InDegree( v ) == 0 ) ) - CalculatePosition( source, null, 0 ); + var graph = new UndirectedBidirectionalGraph(VisitedGraph); + var scca = new QuickGraph.Algorithms.ConnectedComponents.ConnectedComponentsAlgorithm(graph); + scca.Compute(); - //then the others - foreach ( var source in SpanningTree.Vertices ) - CalculatePosition( source, null, 0 ); + // Order connected components by their vertices count + // Group vertices by connected component (they should be placed together) + // Order vertices inside each conected component by in degree first, then out dregee + // (roots should be placed in the first layer and leafs in the last layer) + var components = from e in scca.Components + group e.Key by e.Value into c + orderby c.Count() descending + select c; - AssignPositions(cancellationToken); + foreach (var c in components) + { + var firstOfComponent = true; + var vertices = from v in c + orderby VisitedGraph.InDegree(v), VisitedGraph.OutDegree(v) descending + select v; + + foreach (var source in vertices) + { + CalculatePosition(source, null, 0, firstOfComponent); + + if ( firstOfComponent ) + firstOfComponent = false; + } + } + + AssignPositions(cancellationToken); } public override void ResetGraph(IEnumerable vertices, IEnumerable edges) @@ -68,33 +89,32 @@ namespace GraphX.PCL.Logic.Algorithms.LayoutAlgorithms protected virtual void GenerateSpanningTree(CancellationToken cancellationToken) { SpanningTree = new BidirectionalGraph>( false ); - SpanningTree.AddVertexRange( VisitedGraph.Vertices ); - IQueue vb = new QuickGraph.Collections.Queue(); - vb.Enqueue( VisitedGraph.Vertices.OrderBy( v => VisitedGraph.InDegree( v ) ).First() ); - switch ( Parameters.SpanningTreeGeneration ) + SpanningTree.AddVertexRange(VisitedGraph.Vertices.OrderBy(v => VisitedGraph.InDegree(v))); + + EdgeAction action = e => + { + cancellationToken.ThrowIfCancellationRequested(); + SpanningTree.AddEdge(new Edge(e.Source, e.Target)); + }; + + switch ( Parameters.SpanningTreeGeneration ) { case SpanningTreeGeneration.BFS: - var bfsAlgo = new BreadthFirstSearchAlgorithm( VisitedGraph, vb, new Dictionary() ); - bfsAlgo.TreeEdge += e => - { - cancellationToken.ThrowIfCancellationRequested(); - SpanningTree.AddEdge(new Edge(e.Source, e.Target)); - }; - bfsAlgo.Compute(); + var bfsAlgo = new BreadthFirstSearchAlgorithm( VisitedGraph ); + bfsAlgo.TreeEdge += action; + bfsAlgo.Compute(); break; + case SpanningTreeGeneration.DFS: var dfsAlgo = new DepthFirstSearchAlgorithm( VisitedGraph ); - dfsAlgo.TreeEdge += e => - { - cancellationToken.ThrowIfCancellationRequested(); - SpanningTree.AddEdge(new Edge(e.Source, e.Target)); - }; - dfsAlgo.Compute(); + dfsAlgo.TreeEdge += action; + dfsAlgo.ForwardOrCrossEdge += action; + dfsAlgo.Compute(); break; } } - protected virtual double CalculatePosition( TVertex v, TVertex parent, int l ) + protected virtual double CalculatePosition( TVertex v, TVertex parent, int l, bool firstOfComponent ) { if ( Data.ContainsKey( v ) ) return -1; //this vertex is already layed out @@ -113,7 +133,13 @@ namespace GraphX.PCL.Logic.Algorithms.LayoutAlgorithms layer.NextPosition += Layers[l - 1].LastTranslate; Layers[l - 1].LastTranslate = 0; } - layer.Size = Math.Max( layer.Size, size.Height + Parameters.LayerGap ); + + if ( firstOfComponent ) + { + layer.NextPosition += Parameters.ComponentGap; + } + + layer.Size = Math.Max( layer.Size, size.Height ); layer.Vertices.Add( v ); if ( SpanningTree.OutDegree( v ) == 0 ) { @@ -126,20 +152,25 @@ namespace GraphX.PCL.Logic.Algorithms.LayoutAlgorithms //first put the children foreach ( var child in SpanningTree.OutEdges( v ).Select( e => e.Target ) ) { - double childPos = CalculatePosition( child, v, l + 1 ); + double childPos = CalculatePosition( child, v, l + 1, firstOfComponent ); + if ( childPos >= 0 ) { minPos = Math.Min( minPos, childPos ); maxPos = Math.Max( maxPos, childPos ); } - } + + if ( firstOfComponent ) + firstOfComponent = false; + } if ( minPos != double.MaxValue ) d.Position = ( minPos + maxPos ) / 2.0; else d.Position = layer.NextPosition; + d.Translate = Math.Max( layer.NextPosition - d.Position, 0 ); - layer.LastTranslate = d.Translate; + layer.LastTranslate = d.Translate; d.Position += d.Translate; layer.NextPosition = d.Position; } @@ -155,11 +186,11 @@ namespace GraphX.PCL.Logic.Algorithms.LayoutAlgorithms foreach ( var layer in Layers ) { - foreach ( var v in layer.Vertices ) + foreach ( var v in layer.Vertices ) { cancellationToken.ThrowIfCancellationRequested(); - Size size = Sizes[v]; + var size = Sizes[v]; var d = Data[v]; if ( d.Parent != null ) { @@ -167,13 +198,15 @@ namespace GraphX.PCL.Logic.Algorithms.LayoutAlgorithms d.Translate += Data[d.Parent].Translate; } - VertexPositions[v] = - changeCoordinates - ? new Point( _direction * ( layerSize + size.Height / 2.0 ), d.Position ) - : new Point( d.Position, _direction * ( layerSize + size.Height / 2.0 ) ); + var x = d.Position - size.Width / 2.0; + var y = _direction * (layerSize + (layer.Size - size.Height) / 2.0); + var pos = changeCoordinates ? new Point(y, x) : new Point(x, y); + + VertexPositions[v] = pos; } - layerSize += layer.Size; - } + + layerSize += layer.Size + Parameters.LayerGap; + } if ( _direction < 0 ) NormalizePositions(); diff --git a/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutParameters.cs b/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutParameters.cs index 5961595..45c13e9 100644 --- a/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutParameters.cs +++ b/GraphX.PCL.Logic/Algorithms/LayoutAlgorithms/Tree/SimpleTreeLayoutParameters.cs @@ -2,6 +2,21 @@ { public class SimpleTreeLayoutParameters : LayoutParametersBase { + private double _componentGap = 10; + /// + /// Gets or sets the gap between the connected components. + /// + public double ComponentGap + { + get { return _componentGap; } + set + { + if (_componentGap == value) return; + _componentGap = value; + NotifyPropertyChanged("ComponentGap"); + } + } + private double _vertexGap = 10; /// /// Gets or sets the gap between the vertices.