PageRenderTime 48ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/GraphLayout/GraphControlSilverlight/GraphControlSilverlight/NestedGraphHelper.cs

https://gitlab.com/hoseinyeganloo/automatic-graph-layout
C# | 259 lines | 204 code | 21 blank | 34 comment | 19 complexity | 7fa26da8e41ffce74b63edc50633648d MD5 | raw file
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using System.Net;
  5. using System.Windows;
  6. using System.Windows.Threading;
  7. using System.Windows.Media;
  8. using System.Windows.Controls;
  9. using GeometryGraph = Microsoft.Msagl.Core.Layout.GeometryGraph;
  10. using GeometryEdge = Microsoft.Msagl.Core.Layout.Edge;
  11. using GeometryNode = Microsoft.Msagl.Core.Layout.Node;
  12. using GeometryPoint = Microsoft.Msagl.Core.Geometry.Point;
  13. using DrawingGraph = Microsoft.Msagl.Drawing.Graph;
  14. using DrawingEdge = Microsoft.Msagl.Drawing.Edge;
  15. using DrawingNode = Microsoft.Msagl.Drawing.Node;
  16. using Microsoft.Msagl.Routing;
  17. using Microsoft.Msagl.Core.Routing;
  18. namespace Microsoft.Msagl.GraphControlSilverlight
  19. {
  20. /*
  21. * The basic idea of nested graphs is that a DNode gets a DLabel with a DGraph as its Content. There are two problems to solve: layout, and
  22. * layout. More accurately, there are two unrelated things called "layout" involved, and both are a problem.
  23. *
  24. * The GRAPH layout is the one that's invoked with the DGraph.BeginLayout() method. We need to make sure that the layout of each inner node is
  25. * completed before we invoke BeginLayout on the outer graph. This can be done by setting event handlers on the inner graphs' GraphLayoutDone
  26. * events. If we don't do this, the outer graph won't know the size of its nodes and will be unable to produce a correct layout. In order to do
  27. * this, I'm using a Queue of graphs which need to be laid out (in the correct order). With some tweaks, we could layout multiple graphs at
  28. * the same time, provided that they are "cousins" and not descendants, but I doubt this would provide a noticeable performance improvement except
  29. * in extreme cases.
  30. *
  31. * The XAML layout, on the other hand, is what the XAML framework does to render stuff to the screen. It involves calculating the size of
  32. * XAML elements and figuring out where they need to be placed. The details are hidden from us, and most of the time it works behind the scenes.
  33. * However, there is one key issue - layout is NOT done if an element is not in a visual tree (i.e. is not somewhere in the MainWindow). This
  34. * is a problem because the graph layout needs to clear its own visual tree before starting, which means that elements are going to end up
  35. * outside the visual tree, which in turn means that their size will be set to zero.
  36. *
  37. * Fortunately, we can explicitly tell the XAML engine to figure out their size even if they are outside a visual tree; this is what the
  38. * "Measure" method down below does. We need to do this in the GraphLayoutStarting event, which happens after the elements have been
  39. * removed from the visual tree.
  40. */
  41. public class NestedGraphHelper
  42. {
  43. private DGraph m_Graph;
  44. private NestedGraphHelper(DGraph graph)
  45. {
  46. m_Graph = graph;
  47. }
  48. private Queue<DNestedGraphLabel> m_LayoutQueue;
  49. private void BeginLayout()
  50. {
  51. m_LayoutQueue = new Queue<DNestedGraphLabel>();
  52. SetupQueue(m_Graph);
  53. m_Graph.GraphLayoutDone += graph_GraphLayoutDone;
  54. BeginNextLayout();
  55. }
  56. private void PopulateAllCrossEdges(DGraph graph)
  57. {
  58. graph.PopulateCrossEdges();
  59. foreach (DGraph g in graph.NestedGraphs)
  60. PopulateAllCrossEdges(g);
  61. }
  62. void graph_GraphLayoutDone(object sender, EventArgs e)
  63. {
  64. m_Graph.GraphLayoutDone -= graph_GraphLayoutDone;
  65. try
  66. {
  67. PopulateAllCrossEdges(m_Graph);
  68. }
  69. catch (ArgumentException)
  70. {
  71. // DNestedGraphLabel.GetDGraphOffset has failed with an ArgumentException. This means that the graph has not been loaded into
  72. // the visual tree yet, which means that I can't reliably get the positions of subgraphs within the labels, which prevents me
  73. // from drawing cross-edges properly. As near as I can tell, there is no good reason for this; I can get the sizes of everything
  74. // without loading, so why does TransformToVisual require loading? Anyway, the only thing I can do is wait until the graph has
  75. // been loaded.
  76. m_Graph.Loaded += m_Graph_Loaded;
  77. }
  78. }
  79. void m_Graph_Loaded(object sender, RoutedEventArgs e)
  80. {
  81. m_Graph.Loaded -= m_Graph_Loaded;
  82. PopulateAllCrossEdges(m_Graph);
  83. }
  84. internal static void BeginLayout(DGraph graph)
  85. {
  86. NestedGraphHelper helper = new NestedGraphHelper(graph);
  87. helper.BeginLayout();
  88. }
  89. private void SetupQueue(DGraph g)
  90. {
  91. foreach (DNode n in g.Nodes())
  92. {
  93. DNestedGraphLabel l = n.Label as DNestedGraphLabel;
  94. if (l != null)
  95. {
  96. foreach (DGraph graph in l.Graphs)
  97. {
  98. SetupQueue(graph);
  99. //graph.PopulateChildren();
  100. }
  101. m_LayoutQueue.Enqueue(l);
  102. }
  103. }
  104. }
  105. private int count = 0;
  106. private void BeginNextLayout()
  107. {
  108. if (--count > 0)
  109. return;
  110. if (!m_LayoutQueue.Any())
  111. {
  112. m_Graph.Dispatcher.BeginInvoke((Action)(() => m_Graph.BeginLayout(true)));
  113. return;
  114. }
  115. DNestedGraphLabel l = m_LayoutQueue.Dequeue();
  116. count = 0;
  117. foreach (DGraph graph in l.Graphs)
  118. {
  119. count++;
  120. graph.GraphLayoutDone += SubGraphLayoutDone;
  121. graph.Dispatcher.BeginInvoke(() => graph.BeginLayout(true));
  122. }
  123. }
  124. public static T FindAncestorOrSelf<T>(DependencyObject obj)
  125. where T : DependencyObject
  126. {
  127. while (obj != null)
  128. {
  129. T objTest = obj as T;
  130. if (objTest != null)
  131. return objTest;
  132. obj = VisualTreeHelper.GetParent(obj);
  133. }
  134. return null;
  135. }
  136. private void SubGraphLayoutDone(object sender, EventArgs e)
  137. {
  138. DGraph g = sender as DGraph;
  139. g.GraphLayoutDone -= SubGraphLayoutDone;
  140. g.Measure();
  141. g.Dispatcher.BeginInvoke((Action)(() => BeginNextLayout()));
  142. }
  143. private static void MeasureAllNestedGraphs(DGraph dg)
  144. {
  145. foreach (DNestedGraphLabel label in dg.Nodes().Cast<DNode>().Where(n => n.Label is DNestedGraphLabel).Select(n => n.Label as DNestedGraphLabel))
  146. {
  147. foreach (DGraph idg in label.Graphs)
  148. {
  149. MeasureAllNestedGraphs(idg);
  150. idg.Measure();
  151. }
  152. if (label.Content is FrameworkElement)
  153. (label.Content as FrameworkElement).Measure();
  154. }
  155. }
  156. /// <summary>
  157. /// Creates an auxilliary geometry graph, which contains all the nodes of a flattened nested graph. Produces a map from the original graph nodes to the auxilliary graph nodes.
  158. /// </summary>
  159. /// <param name="gg">The auxilliary graph to be filled.</param>
  160. /// <param name="graph">The original graph.</param>
  161. /// <param name="offset">Offset to be applied to all nodes.</param>
  162. /// <param name="connectedNodes">Map from original graph nodes to auxilliary graph nodes.</param>
  163. private static void FillAuxGraph(GeometryGraph gg, DGraph graph, GeometryPoint offset, Dictionary<DNode, GeometryNode> connectedNodes)
  164. {
  165. foreach (DNode n in graph.Nodes())
  166. {
  167. GeometryNode clone = new GeometryNode(n.GeometryNode.BoundaryCurve.Clone());
  168. clone.BoundaryCurve.Translate(offset);
  169. gg.Nodes.Add(clone);
  170. connectedNodes[n] = clone;
  171. DNestedGraphLabel ngLabel = n.Label as DNestedGraphLabel;
  172. if (ngLabel != null)
  173. {
  174. GeometryPoint labelPosition = new GeometryPoint(Canvas.GetLeft(ngLabel), Canvas.GetTop(ngLabel));
  175. foreach (DGraph dg in ngLabel.Graphs)
  176. {
  177. Point p = ngLabel.GetDGraphOffset(dg);
  178. GeometryPoint offsetToLabel = new GeometryPoint(p.X, p.Y);
  179. GeometryPoint graphOffset = labelPosition + offsetToLabel;
  180. GeometryPoint normalizedOffset = graphOffset - dg.Graph.BoundingBox.LeftBottom;
  181. FillAuxGraph(gg, dg, offset + normalizedOffset, connectedNodes);
  182. }
  183. }
  184. }
  185. }
  186. public static void DrawCrossEdges(DGraph graph, IEnumerable<DEdge> edges)
  187. {
  188. GeometryGraph ggAux = new GeometryGraph();
  189. Dictionary<DNode, GeometryNode> nodeMap = new Dictionary<DNode, GeometryNode>();
  190. foreach (DEdge edge in edges)
  191. {
  192. if (edge.Label != null)
  193. edge.Label.MeasureLabel();
  194. nodeMap[edge.Source] = null;
  195. nodeMap[edge.Target] = null;
  196. }
  197. FillAuxGraph(ggAux, graph, new GeometryPoint(0.0, 0.0), nodeMap);
  198. Dictionary<DEdge, GeometryEdge> edgeMap = new Dictionary<DEdge, GeometryEdge>();
  199. foreach (DEdge edge in edges)
  200. {
  201. GeometryEdge gEdge = new GeometryEdge(nodeMap[edge.Source], nodeMap[edge.Target]) { GeometryParent = ggAux };
  202. gEdge.EdgeGeometry.SourceArrowhead = edge.GeometryEdge.EdgeGeometry.SourceArrowhead;
  203. gEdge.EdgeGeometry.TargetArrowhead = edge.GeometryEdge.EdgeGeometry.TargetArrowhead;
  204. gEdge.Label = edge.GeometryEdge.Label;
  205. edgeMap[edge] = gEdge;
  206. ggAux.Edges.Add(gEdge);
  207. }
  208. var router = new SplineRouter(ggAux, 3.0, 2.0, Math.PI / 6.0) { ContinueOnOverlaps = true };
  209. router.Run();
  210. foreach (DEdge edge in edges)
  211. {
  212. edge.GeometryEdge = edgeMap[edge];
  213. if (edge.DrawingEdge.Label != null)
  214. {
  215. if (edge.GeometryEdge.Label.Center == new GeometryPoint())
  216. edge.GeometryEdge.Label.Center = edge.GeometryEdge.BoundingBox.Center;
  217. edge.DrawingEdge.Label.GeometryLabel = edge.GeometryEdge.Label;
  218. }
  219. }
  220. foreach (DEdge edge in edges)
  221. {
  222. edge.MakeVisual();
  223. Canvas.SetZIndex(edge, 20000);
  224. graph.MainCanvas.Children.Add(edge);
  225. if (edge.Label != null)
  226. {
  227. edge.Label.MakeVisual();
  228. Canvas.SetZIndex(edge.Label, 20000);
  229. graph.MainCanvas.Children.Add(edge.Label);
  230. }
  231. }
  232. }
  233. public static void DrawCrossEdge(DEdge edge)
  234. {
  235. DrawCrossEdges(edge.ParentGraph, new DEdge[] { edge });
  236. }
  237. }
  238. }