/// <summary> /// Apply the appropriate layout to the specified cluster /// </summary> /// <param name="cluster">the root of the cluster hierarchy to lay out</param> /// <returns>list of edges external to the cluster</returns> void LayoutCluster(Cluster cluster) { ProgressStep(); cluster.UnsetInitialLayoutState(); FastIncrementalLayoutSettings settings = null; LayoutAlgorithmSettings s = clusterSettings(cluster); Directions layoutDirection = Directions.None; if (s is SugiyamaLayoutSettings) { var ss = s as SugiyamaLayoutSettings; settings = ss.FallbackLayoutSettings != null ? new FastIncrementalLayoutSettings((FastIncrementalLayoutSettings)ss.FallbackLayoutSettings) : new FastIncrementalLayoutSettings(); layoutDirection = LayeredLayoutEngine.GetLayoutDirection(ss); } else { settings = new FastIncrementalLayoutSettings((FastIncrementalLayoutSettings)s); } settings.ApplyForces = true; settings.MinorIterations = 10; settings.AvoidOverlaps = true; settings.InterComponentForces = false; settings.IdealEdgeLength = new IdealEdgeLengthSettings { EdgeDirectionConstraints = layoutDirection, ConstrainedEdgeSeparation = 30 }; settings.EdgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.Spline; HashSet <Node> addedNodes; if (addedNodesByCluster.TryGetValue(cluster, out addedNodes)) { // if the structure of the cluster has changed then we apply unconstrained layout first, // then introduce structural constraints, and then all constraints settings.MinConstraintLevel = 0; settings.MaxConstraintLevel = 2; } else { settings.MinConstraintLevel = 2; } GeometryGraph newGraph = GetShallowCopyGraphUnderCluster(cluster); LayoutAlgorithmHelpers.ComputeDesiredEdgeLengths(settings.IdealEdgeLength, newGraph); // orthogonal ordering constraints preserve the left-of, above-of relationships between existing nodes // (we do not apply these to the newly added nodes) GenerateOrthogonalOrderingConstraints( newGraph.Nodes.Where(v => !addedNodes.Contains(v.UserData as Node)).ToList(), settings); LayoutComponent(newGraph, settings); //LayoutAlgorithmSettings.ShowGraph(newGraph); InitialLayoutByCluster.FixOriginalGraph(newGraph, true); cluster.UpdateBoundary(newGraph.BoundingBox); }
internal void LayoutComponent(GeometryGraph component, FastIncrementalLayoutSettings settings) { // for small graphs (below 100 nodes) do extra iterations settings.MaxIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation( component.Nodes.Count, /*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 3, /*maxIterations:*/ 5); settings.MinorIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation(component.Nodes.Count, /*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 2, /*maxIterations:*/ 10); FastIncrementalLayout fil = new FastIncrementalLayout(component, settings, settings.MinConstraintLevel, anyCluster => settings); Debug.Assert(settings.Iterations == 0); foreach (var level in Enumerable.Range(settings.MinConstraintLevel, settings.MaxConstraintLevel + 1)) { if (level != fil.CurrentConstraintLevel) { fil.CurrentConstraintLevel = level; if (level == 2) { settings.MinorIterations = 1; settings.ApplyForces = false; } } do { fil.Run(); } while (!settings.IsDone); } // Pad the graph with margins so the packing will be spaced out. component.Margins = settings.ClusterMargin; component.UpdateBoundingBox(); }
public void CalculateIterationsForGraphSize() { const int LowerThreshold = 10; const int UpperThreshold = 50; const int MinIterations = 10; const int MaxIterations = 30; Assert.AreEqual( LayoutAlgorithmHelpers.NegativeLinearInterpolation(UpperThreshold + 1, LowerThreshold, UpperThreshold, MinIterations, MaxIterations), MinIterations, "When nodeCount > upperThreshold number of iterations should be minIterations"); Assert.AreEqual( LayoutAlgorithmHelpers.NegativeLinearInterpolation(UpperThreshold, LowerThreshold, UpperThreshold, MinIterations, MaxIterations), MinIterations, "When nodeCount == upperThreshold number of iterations should be minIterations"); Assert.AreEqual( LayoutAlgorithmHelpers.NegativeLinearInterpolation(LowerThreshold - 1, LowerThreshold, UpperThreshold, MinIterations, MaxIterations), MaxIterations, "When nodeCount < lowerThreshold number of iterations should be maxIterations"); Assert.AreEqual( LayoutAlgorithmHelpers.NegativeLinearInterpolation(LowerThreshold, LowerThreshold, UpperThreshold, MinIterations, MaxIterations), MaxIterations, "When nodeCount == lowerThreshold number of iterations should be maxIterations"); Assert.AreEqual( LayoutAlgorithmHelpers.NegativeLinearInterpolation((UpperThreshold + LowerThreshold) / 2, LowerThreshold, UpperThreshold, MinIterations, MaxIterations), (MinIterations + MaxIterations) / 2, "When nodeCount is mid-way between lowerThreshold and upperThreshold number of iterations should be mid-way between minIterations and maxIterations"); }
void MDSLayout(MdsLayoutSettings settings, GeometryGraph component) { LayoutAlgorithmHelpers.ComputeDesiredEdgeLengths(settings.EdgeConstraints, component); var layout = new MdsGraphLayout(settings, component); layout.Run(this.CancelToken); InitialLayoutHelpers.RouteEdges(component, settings, this.CancelToken); InitialLayoutHelpers.PlaceLabels(component, this.CancelToken); InitialLayoutHelpers.FixBoundingBox(component, settings); }
void ForceDirectedLayout(FastIncrementalLayoutSettings settings, GeometryGraph component) { LayoutAlgorithmHelpers.ComputeDesiredEdgeLengths(settings.IdealEdgeLength, component); var layout = new InitialLayout(component, settings) { SingleComponent = true }; layout.Run(this.CancelToken); InitialLayoutHelpers.RouteEdges(component, settings, this.CancelToken); InitialLayoutHelpers.PlaceLabels(component, this.CancelToken); InitialLayoutHelpers.FixBoundingBox(component, settings); }
private void LayoutComponent(GeometryGraph component) { if (component.Nodes.Count > 1 || component.RootCluster.Clusters.Any()) { // for small graphs (below 100 nodes) do extra iterations settings.MaxIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation( component.Nodes.Count, /*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 5, /*maxIterations:*/ 10); settings.MinorIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation(component.Nodes.Count, /*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 3, /*maxIterations:*/ 20); if (settings.MinConstraintLevel == 0) { // run PivotMDS with a largish Scale so that the layout comes back oversized. // subsequent incremental iterations do a better job of untangling when they're pulling it in // rather than pushing it apart. PivotMDS pivotMDS = new PivotMDS(component) { Scale = 2 }; this.RunChildAlgorithm(pivotMDS, 0.5 / componentCount); } FastIncrementalLayout fil = new FastIncrementalLayout(component, settings, settings.MinConstraintLevel, anyCluster => settings); Debug.Assert(settings.Iterations == 0); foreach (var level in GetConstraintLevels(component)) { if (level > settings.MaxConstraintLevel) { break; } if (level > settings.MinConstraintLevel) { fil.CurrentConstraintLevel = level; } do { fil.Run(); } while (!settings.IsDone); } } // Pad the graph with margins so the packing will be spaced out. component.Margins = settings.NodeSeparation; component.UpdateBoundingBox(); // Zero the graph component.Translate(-component.BoundingBox.LeftBottom); }
/// <summary> /// Create an abstraction of the graph by pairing edges with the shortest /// symmetric difference of neighbour sets. In a perfect world the result /// would have half as many nodes as the input graph. In practice we only /// process the edge list once, and don't pair edges whose ends are already /// paired so the output may have more than n/2 nodes. /// </summary> /// <param name="graph">The input graph</param> /// <param name="edgeLengthOffset">Initial edge length adjustment percent before proportional adjustments.</param> /// <param name="edgeLengthMultiplier">The percent length adjustment unit.</param> /// <returns>an abridged graph</returns> private GeometryGraph CreateAbridgedGraph(GeometryGraph graph, double edgeLengthOffset, double edgeLengthMultiplier) { LayoutAlgorithmHelpers.SetEdgeLengthsProportionalToSymmetricDifference(graph, edgeLengthOffset, edgeLengthMultiplier); var g2 = new GeometryGraph(); var nodes = new Set <Node>(graph.Nodes); var rand = new Random(1); // edges sorted by increasing weight, edges with the same weight shuffled var edges = (from e in graph.Edges let r = rand.Next() orderby e.Length, r select e).ToList(); // mapping from nodes in graph to nodes in g2 // each node in g2 may represent either 1 or 2 nodes from graph var nodeMap = new Dictionary <Node, Node>(); // populate g2 with nodes representing pairs of nodes, paired across the shortest edges first foreach (var e in edges) { if (nodes.Contains(e.Source) && nodes.Contains(e.Target)) { var pairNode = new Node { UserData = e }; nodeMap[e.Source] = pairNode; nodeMap[e.Target] = pairNode; g2.Nodes.Add(pairNode); nodes.Remove(e.Source); nodes.Remove(e.Target); } } // populate g2 with remaining singleton nodes from graph foreach (var u in nodes) { var v = new Node { UserData = u }; g2.Nodes.Add(v); nodeMap[u] = v; } // populate g2 with edges - no duplicates, reverse duplicates or self edges allowed var neighbours = new Set <KeyValuePair <Node, Node> >(); foreach (var e in edges) { Node u = nodeMap[e.Source], v = nodeMap[e.Target]; var pair = new KeyValuePair <Node, Node>(u, v); var raip = new KeyValuePair <Node, Node>(v, u); if (pair.Key != pair.Value && !neighbours.Contains(pair) && !neighbours.Contains(raip)) { var e2 = new Edge(u, v) { Length = e.Length }; neighbours.Insert(pair); g2.Edges.Add(e2); } } return(g2); }