PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/GraphLayout/MSAGL/Layout/Incremental/Relayout.cs

https://gitlab.com/hoseinyeganloo/automatic-graph-layout
C# | 294 lines | 211 code | 37 blank | 46 comment | 38 complexity | eb8b7807f11c35fa5107f419fc7c1a5f MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Linq;
  6. using Microsoft.Msagl.Core;
  7. using Microsoft.Msagl.Core.DataStructures;
  8. using Microsoft.Msagl.Core.Geometry;
  9. using Microsoft.Msagl.Core.Layout;
  10. using Microsoft.Msagl.Core.Routing;
  11. using Microsoft.Msagl.Layout.Incremental;
  12. using Microsoft.Msagl.Layout.Layered;
  13. using Microsoft.Msagl.Layout.MDS;
  14. using Microsoft.Msagl.Miscellaneous;
  15. using Microsoft.Msagl.Routing;
  16. using Microsoft.Msagl.Routing.Rectilinear;
  17. using System.Threading.Tasks;
  18. #if TEST_MSAGL
  19. using Microsoft.Msagl.DebugHelpers;
  20. #endif
  21. namespace Microsoft.Msagl.Layout.Initial {
  22. /// <summary>
  23. /// todo:
  24. /// find a way to compact disconnected components - incremental packing?
  25. /// animate transitions
  26. /// </summary>
  27. public class Relayout : AlgorithmBase {
  28. readonly GeometryGraph graph;
  29. readonly IEnumerable<Node> modifiedNodes;
  30. readonly Func<Cluster, LayoutAlgorithmSettings> clusterSettings;
  31. readonly Set<Cluster> ancestorsOfModifiedNodes;
  32. readonly Dictionary<Cluster, HashSet<Node>> addedNodesByCluster = new Dictionary<Cluster, HashSet<Node>>();
  33. /// <summary>
  34. /// Recursively lay out the given clusters using the specified settings for each cluster, or if none is given for a particular
  35. /// cluster then inherit from the cluster's ancestor - or from the specifed defaultSettings.
  36. /// Clusters (other than the root) will be translated (together with their descendants) such that their
  37. /// bottom-left point of their new boundaries are the same as the bottom-left of their old boundaries
  38. /// (i.e. clusters are laid-out in place).
  39. /// </summary>
  40. /// <param name="graph">The graph being operated on.</param>
  41. /// <param name="modifiedNodes">The nodes whose bounds are modified.</param>
  42. /// <param name="addedNodes">Nodes added to the graph - a new initial position will be found for these nodes close to their neighbors</param>
  43. /// <param name="clusterSettings">Settings to use for each cluster.</param>
  44. public Relayout(GeometryGraph graph, IEnumerable<Node> modifiedNodes, IEnumerable<Node> addedNodes,
  45. Func<Cluster, LayoutAlgorithmSettings> clusterSettings) {
  46. ValidateArg.IsNotNull(graph, "graph");
  47. ValidateArg.IsNotNull(clusterSettings, "clusterSettings");
  48. #if TEST_MSAGL
  49. graph.SetDebugIds();
  50. #endif
  51. this.graph = graph;
  52. this.modifiedNodes = modifiedNodes;
  53. this.clusterSettings = clusterSettings;
  54. ancestorsOfModifiedNodes =
  55. new Set<Cluster>(modifiedNodes.SelectMany(v => v.AllClusterAncestors));
  56. if (addedNodes == null) return;
  57. foreach (var v in addedNodes)
  58. CreateOrGetAddedChildrenOfParent(v.ClusterParents.First()).Add(v);
  59. ancestorsOfModifiedNodes.InsertRange(addedNodes.SelectMany(v => v.AllClusterAncestors));
  60. }
  61. HashSet<Node> CreateOrGetAddedChildrenOfParent(Cluster parent) {
  62. HashSet<Node> addedChildren;
  63. addedNodesByCluster.TryGetValue(parent, out addedChildren);
  64. if (addedChildren == null)
  65. addedNodesByCluster[parent] = addedChildren = new HashSet<Node>();
  66. return addedChildren;
  67. }
  68. /// <summary>
  69. /// The actual layout process
  70. /// </summary>
  71. protected override void RunInternal() {
  72. var openedClusters = modifiedNodes.OfType<Cluster>().Where(cl => !cl.IsCollapsed).ToArray();
  73. if (openedClusters.Length > 0)
  74. new InitialLayoutByCluster(graph, openedClusters, clusterSettings).Run();
  75. Visit(graph.RootCluster);
  76. // routing edges that cross cluster boundaries
  77. InitialLayoutByCluster.RouteParentEdges(graph, clusterSettings(graph.RootCluster).EdgeRoutingSettings);
  78. LayoutHelpers.RouteAndLabelEdges(graph, clusterSettings(graph.RootCluster),
  79. graph.Edges.Where(BetweenClusterOnTheRightLevel));
  80. graph.UpdateBoundingBox();
  81. ProgressComplete();
  82. }
  83. bool BetweenClusterOnTheRightLevel(Edge edge) {
  84. var sourceAncestors = new Set<Cluster>(edge.Source.AllClusterAncestors);
  85. var targetAncestors = new Set<Cluster>(edge.Target.AllClusterAncestors);
  86. return (sourceAncestors*targetAncestors).IsContained(ancestorsOfModifiedNodes);
  87. }
  88. // depth first traversal of cluster hierarchy
  89. // if the cluster is not in initiallayoutstate then visit children and then apply layout
  90. void Visit(Cluster u) {
  91. if (u.IsCollapsed || !ancestorsOfModifiedNodes.Contains(u))
  92. return;
  93. foreach (var c in u.Clusters)
  94. Visit(c);
  95. LayoutCluster(u);
  96. }
  97. /// <summary>
  98. /// Apply the appropriate layout to the specified cluster
  99. /// </summary>
  100. /// <param name="cluster">the root of the cluster hierarchy to lay out</param>
  101. /// <returns>list of edges external to the cluster</returns>
  102. void LayoutCluster(Cluster cluster) {
  103. ProgressStep();
  104. cluster.UnsetInitialLayoutState();
  105. FastIncrementalLayoutSettings settings = null;
  106. LayoutAlgorithmSettings s = clusterSettings(cluster);
  107. Directions layoutDirection = Directions.None;
  108. if (s is SugiyamaLayoutSettings) {
  109. var ss = s as SugiyamaLayoutSettings;
  110. settings = ss.FallbackLayoutSettings != null
  111. ? new FastIncrementalLayoutSettings((FastIncrementalLayoutSettings) ss.FallbackLayoutSettings)
  112. : new FastIncrementalLayoutSettings();
  113. layoutDirection = LayeredLayoutEngine.GetLayoutDirection(ss);
  114. }
  115. else {
  116. settings = new FastIncrementalLayoutSettings((FastIncrementalLayoutSettings) s);
  117. }
  118. settings.ApplyForces = true;
  119. settings.MinorIterations = 10;
  120. settings.AvoidOverlaps = true;
  121. settings.InterComponentForces = false;
  122. settings.IdealEdgeLength = new IdealEdgeLengthSettings {
  123. EdgeDirectionConstraints = layoutDirection,
  124. ConstrainedEdgeSeparation = 30
  125. };
  126. settings.EdgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.Spline;
  127. HashSet<Node> addedNodes;
  128. if (addedNodesByCluster.TryGetValue(cluster, out addedNodes)) {
  129. // if the structure of the cluster has changed then we apply unconstrained layout first,
  130. // then introduce structural constraints, and then all constraints
  131. settings.MinConstraintLevel = 0;
  132. settings.MaxConstraintLevel = 2;
  133. }
  134. else
  135. settings.MinConstraintLevel = 2;
  136. GeometryGraph newGraph = GetShallowCopyGraphUnderCluster(cluster);
  137. LayoutAlgorithmHelpers.ComputeDesiredEdgeLengths(settings.IdealEdgeLength, newGraph);
  138. // orthogonal ordering constraints preserve the left-of, above-of relationships between existing nodes
  139. // (we do not apply these to the newly added nodes)
  140. GenerateOrthogonalOrderingConstraints(
  141. newGraph.Nodes.Where(v => !addedNodes.Contains(v.UserData as Node)).ToList(), settings);
  142. LayoutComponent(newGraph, settings);
  143. //LayoutAlgorithmSettings.ShowGraph(newGraph);
  144. InitialLayoutByCluster.FixOriginalGraph(newGraph, true);
  145. cluster.UpdateBoundary(newGraph.BoundingBox);
  146. }
  147. /// <summary>
  148. /// Generate orthogonal ordering constraints to preserve the left/right, above/below relative positions of nodes
  149. /// </summary>
  150. /// <param name="nodes"></param>
  151. /// <param name="settings"></param>
  152. [Conditional("RelayoutOrthogonalOrderingConstraints")]
  153. void GenerateOrthogonalOrderingConstraints(IEnumerable<Node> nodes, FastIncrementalLayoutSettings settings) {
  154. Node p = null;
  155. foreach (var v in graph.Nodes.OrderBy(v => v.Center.X)) {
  156. if (p != null) {
  157. settings.AddStructuralConstraint(new HorizontalSeparationConstraint(p, v, 0.1));
  158. }
  159. p = v;
  160. }
  161. p = null;
  162. foreach (var v in graph.Nodes.OrderBy(v => v.Center.Y)) {
  163. if (p != null) {
  164. settings.AddStructuralConstraint(new VerticalSeparationConstraint(p, v, 0.1));
  165. }
  166. p = v;
  167. }
  168. }
  169. /// <summary>
  170. /// Creates a shallow copy of the cluster into a GeometryGraph
  171. /// </summary>
  172. /// <param name="cluster">cluster to copy</param>
  173. /// <returns>cluster children and edges between children in a GeometryGraph</returns>
  174. static GeometryGraph GetShallowCopyGraphUnderCluster(Cluster cluster) {
  175. Dictionary<Node, Node> originalToCopyNodeMap = InitialLayoutByCluster.ShallowNodeCopyDictionary(cluster);
  176. var newGraph = CreateGeometryGraphAndPopulateItWithNodes(originalToCopyNodeMap);
  177. foreach (var target in originalToCopyNodeMap.Keys)
  178. foreach (var underNode in AllSuccessors(target)) {
  179. foreach (var e in underNode.InEdges) {
  180. var sourceAncestorUnderRoot = InitialLayoutByCluster.Ancestor(e.Source, cluster);
  181. if (IsBetweenClusters(sourceAncestorUnderRoot, target))
  182. //it is a flat edge and we are only interested in flat edges
  183. newGraph.Edges.Add(InitialLayoutByCluster.CopyEdge(originalToCopyNodeMap, e,
  184. sourceAncestorUnderRoot, target));
  185. }
  186. foreach (var e in target.SelfEdges)
  187. newGraph.Edges.Add(InitialLayoutByCluster.CopyEdge(originalToCopyNodeMap, e));
  188. }
  189. return newGraph;
  190. }
  191. static GeometryGraph CreateGeometryGraphAndPopulateItWithNodes(Dictionary<Node, Node> originalToCopyNodeMap) {
  192. GeometryGraph newGraph = new GeometryGraph();
  193. foreach (var v in originalToCopyNodeMap.Values)
  194. newGraph.Nodes.Add(v);
  195. return newGraph;
  196. }
  197. static bool IsBetweenClusters(Node sourceAncestorUnderRoot, Node target) {
  198. return sourceAncestorUnderRoot != target && sourceAncestorUnderRoot != null;
  199. }
  200. static IEnumerable<Node> AllSuccessors(Node node) {
  201. var ret = new List<Node> {node};
  202. var cl = node as Cluster;
  203. if (cl != null)
  204. foreach (var u in cl.AllSuccessorsWidthFirst())
  205. if (u != node) ret.Add(u);
  206. return ret;
  207. }
  208. internal void LayoutComponent(GeometryGraph component, FastIncrementalLayoutSettings settings) {
  209. // for small graphs (below 100 nodes) do extra iterations
  210. settings.MaxIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation(
  211. component.Nodes.Count,
  212. /*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 3, /*maxIterations:*/ 5);
  213. settings.MinorIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation(component.Nodes.Count,
  214. /*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 2, /*maxIterations:*/ 10);
  215. FastIncrementalLayout fil = new FastIncrementalLayout(component, settings, settings.MinConstraintLevel,
  216. anyCluster => settings);
  217. Debug.Assert(settings.Iterations == 0);
  218. foreach (var level in Enumerable.Range(settings.MinConstraintLevel, settings.MaxConstraintLevel + 1)) {
  219. if (level != fil.CurrentConstraintLevel) {
  220. fil.CurrentConstraintLevel = level;
  221. if (level == 2) {
  222. settings.MinorIterations = 1;
  223. settings.ApplyForces = false;
  224. }
  225. }
  226. do {
  227. fil.Run();
  228. } while (!settings.IsDone);
  229. }
  230. // Pad the graph with margins so the packing will be spaced out.
  231. component.Margins = settings.ClusterMargin;
  232. component.UpdateBoundingBox();
  233. }
  234. #if TEST_MSAGL_AND_HAVEGRAPHVIEWERGDI
  235. protected static void ShowGraphInDebugViewer(GeometryGraph graph)
  236. {
  237. if (graph == null)
  238. {
  239. return;
  240. }
  241. Microsoft.Msagl.GraphViewerGdi.DisplayGeometryGraph.SetShowFunctions();
  242. //FixNullCurveEdges(graph.Edges);
  243. var debugCurves = graph.Nodes.Select(n => n.BoundaryCurve).Select(c => new DebugCurve("red", c));
  244. debugCurves = debugCurves.Concat(graph.RootCluster.AllClustersDepthFirst().Select(c => c.BoundaryCurve).Select(c => new DebugCurve("green", c)));
  245. debugCurves = debugCurves.Concat(graph.Edges.Select(e => new DebugCurve(120, 1, "blue", e.Curve)));
  246. debugCurves = debugCurves.Concat(graph.Edges.Where(e => e.Label != null).Select(e => new DebugCurve("green", CurveFactory.CreateRectangle(e.LabelBBox))));
  247. var arrowHeadsAtSource = from e in graph.Edges
  248. where e.Curve != null && e.EdgeGeometry.SourceArrowhead != null
  249. select new DebugCurve(120, 2, "black", new LineSegment(e.Curve.Start, e.EdgeGeometry.SourceArrowhead.TipPosition));
  250. var arrowHeadsAtTarget = from e in graph.Edges
  251. where e.Curve != null && e.EdgeGeometry.TargetArrowhead != null
  252. select new DebugCurve(120, 2, "black", new LineSegment(e.Curve.End, e.EdgeGeometry.TargetArrowhead.TipPosition));
  253. LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debugCurves.Concat(arrowHeadsAtSource).Concat(arrowHeadsAtTarget));
  254. }
  255. #endif
  256. }
  257. }