using System.Collections.Generic; using System.Linq; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core; using Microsoft.Msagl.Core.Geometry; namespace Microsoft.Msagl.Layout.Incremental { class TNode { internal LinkedListNode stackNode; internal Node v; internal bool visited; internal List outNeighbours = new List(); internal List inNeighbours = new List(); internal TNode(Node v) { this.v = v; } } /// /// Create separation constraints between the source and target of edges not involved in cycles /// in order to better show flow /// public class EdgeConstraintGenerator { IdealEdgeLengthSettings settings; IEnumerable edges; Dictionary nodeMap = new Dictionary(); LinkedList stack = new LinkedList(); List component; AxisSolver horizontalSolver; AxisSolver verticalSolver; List> cyclicComponents = new List>(); /// /// Creates a VerticalSeparationConstraint for each edge in the given set to structural constraints, /// to require these edges to be downward pointing. Also checks for cycles, and edges involved /// in a cycle receive no VerticalSeparationConstraint, but can optionally receive a circle constraint. /// /// /// /// /// internal static void GenerateEdgeConstraints( IEnumerable edges, IdealEdgeLengthSettings settings, AxisSolver horizontalSolver, AxisSolver verticalSolver) { if (settings.EdgeDirectionConstraints == Directions.None) { return; } EdgeConstraintGenerator g = new EdgeConstraintGenerator(edges, settings, horizontalSolver, verticalSolver); g.GenerateSeparationConstraints(); // we have in the past used Circular constraints to better show cycles... it's a little experimental though // so it's not currently enabled //foreach (var c in g.CyclicComponents) { // constraints.Add(new ProcrustesCircleConstraint(c)); //} } internal EdgeConstraintGenerator( IEnumerable edges, IdealEdgeLengthSettings settings, AxisSolver horizontalSolver, AxisSolver verticalSolver) { // filter out self edges this.edges = edges.Where(e => e.Source != e.Target); this.settings = settings; this.horizontalSolver = horizontalSolver; this.verticalSolver = verticalSolver; foreach (var e in this.edges) { TNode u = CreateTNode(e.Source), v = CreateTNode(e.Target); u.outNeighbours.Add(v); v.inNeighbours.Add(u); } foreach (var v in nodeMap.Values) { if(v.stackNode==null) { DFS(v); } } while (stack.Count > 0) { component = new List(); RDFS(stack.Last.Value); if (component.Count > 1) { var cyclicComponent = new Set(); foreach (var v in component) { cyclicComponent.Insert(v.v); } cyclicComponents.Add(cyclicComponent); } } switch (settings.EdgeDirectionConstraints) { case Directions.South: this.addConstraint = this.AddSConstraint; break; case Directions.North: this.addConstraint = this.AddNConstraint; break; case Directions.West: this.addConstraint = this.AddWConstraint; break; case Directions.East: this.addConstraint = this.AddEConstraint; break; } } private delegate void AddConstraint(Node u, Node v); private AddConstraint addConstraint; private void AddSConstraint(Node u, Node v) { verticalSolver.AddStructuralConstraint( new VerticalSeparationConstraint(u, v, (u.Height + v.Height) / 2 + settings.ConstrainedEdgeSeparation)); } private void AddNConstraint(Node u, Node v) { verticalSolver.AddStructuralConstraint( new VerticalSeparationConstraint(v, u, (u.Height + v.Height) / 2 + settings.ConstrainedEdgeSeparation)); } private void AddEConstraint(Node u, Node v) { horizontalSolver.AddStructuralConstraint( new HorizontalSeparationConstraint(v, u, (u.Width + v.Width) / 2 + settings.ConstrainedEdgeSeparation)); } private void AddWConstraint(Node u, Node v) { horizontalSolver.AddStructuralConstraint( new HorizontalSeparationConstraint(u, v, (u.Width + v.Width) / 2 + settings.ConstrainedEdgeSeparation)); } /// /// For each edge not involved in a cycle create a constraint /// public void GenerateSeparationConstraints() { foreach (var e in edges) { bool edgeInCycle = false; Node u = e.Source, v = e.Target; foreach (var c in cyclicComponents) { if (c.Contains(u) && c.Contains(v)) { edgeInCycle = true; break; } } if (!edgeInCycle) { this.addConstraint(u, v); } } } /// /// Get an Enumeration of CyclicComponents /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] public IEnumerable> CyclicComponents { get { return from c in cyclicComponents select c.AsEnumerable(); } } private void DFS(TNode u) { u.visited = true; foreach (var v in u.outNeighbours) { if (!v.visited) { DFS(v); } } PushStack(u); } private void RDFS(TNode u) { component.Add(u); PopStack(u); foreach (var v in u.inNeighbours) { if (v.stackNode != null) { RDFS(v); } } } private TNode CreateTNode(Node v) { TNode tv; if (!nodeMap.ContainsKey(v)) { tv = new TNode(v); nodeMap[v] = tv; } else { tv = nodeMap[v]; } return tv; } private void PushStack(TNode v) { v.stackNode = stack.AddLast(v); } private void PopStack(TNode v) { stack.Remove(v.stackNode); v.stackNode = null; } } }