/// <summary> /// Shallow copy the settings /// </summary> /// <param name="previousSettings"></param> public FastIncrementalLayoutSettings(FastIncrementalLayoutSettings previousSettings) { ValidateArg.IsNotNull(previousSettings, "previousSettings"); maxIterations = previousSettings.maxIterations; minorIterations = previousSettings.minorIterations; projectionIterations = previousSettings.projectionIterations; approximateRepulsion = previousSettings.approximateRepulsion; initialStepSize = previousSettings.initialStepSize; RungeKuttaIntegration = previousSettings.RungeKuttaIntegration; decay = previousSettings.decay; friction = previousSettings.friction; repulsiveForceConstant = previousSettings.repulsiveForceConstant; attractiveForceConstant = previousSettings.attractiveForceConstant; gravity = previousSettings.gravity; interComponentForces = previousSettings.interComponentForces; applyForces = previousSettings.applyForces; IdealEdgeLength = previousSettings.IdealEdgeLength; AvoidOverlaps = previousSettings.AvoidOverlaps; RespectEdgePorts = previousSettings.RespectEdgePorts; RouteEdges = previousSettings.RouteEdges; approximateRouting = previousSettings.approximateRouting; logScaleEdgeForces = previousSettings.logScaleEdgeForces; displacementThreshold = previousSettings.displacementThreshold; minConstraintLevel = previousSettings.minConstraintLevel; maxConstraintLevel = previousSettings.maxConstraintLevel; attractiveInterClusterForceConstant = previousSettings.attractiveInterClusterForceConstant; clusterGravity = previousSettings.clusterGravity; PackingAspectRatio = previousSettings.PackingAspectRatio; NodeSeparation = previousSettings.NodeSeparation; ClusterMargin = previousSettings.ClusterMargin; }
static Graph CtreateDrawingGraph(out FastIncrementalLayoutSettings settings) { settings = new FastIncrementalLayoutSettings { RouteEdges = true, NodeSeparation = 30}; var drawingGraph = new Graph(); const string id0 = "0"; const string id1 = "1"; AddEdge(drawingGraph, id0, id1); AddEdge(drawingGraph, "0", "2"); AddEdge(drawingGraph, "1", "3"); AddEdge(drawingGraph, "2", "4"); AddEdge(drawingGraph, "2", "5"); AddEdge(drawingGraph, "2", "6"); AddEdge(drawingGraph, "5", "7"); AddEdge(drawingGraph, "5", "6"); AddEdge(drawingGraph, "7", "8"); AddEdge(drawingGraph, "8", "6"); drawingGraph.CreateGeometryGraph(); foreach (Microsoft.Msagl.Drawing.Node node in drawingGraph.Nodes) { double w, h; var label = node.Label; var font = new Font(label.FontName, (float)label.FontSize); StringMeasure.MeasureWithFont(label.Text, font, out w, out h); node.Label.Width = w; node.Label.Height = h; node.Attr.Shape = Shape.DrawFromGeometry; node.GeometryNode.BoundaryCurve=CurveFactory.CreateRectangleWithRoundedCorners(1.2*w,1.2*h,3,3,new Point()); } return drawingGraph; }
public void CalculateLayout_ReportsAllProgress() { GeometryGraph treeGraph = GraphGenerator.GenerateTree(20); var settings = new FastIncrementalLayoutSettings(); InitialLayout layout = new InitialLayout(treeGraph, settings); double progress = 0.0; EventHandler<ProgressChangedEventArgs> handler = (s, e) => progress = e.RatioComplete; try { layout.ProgressChanged += handler; layout.Run(); } finally { layout.ProgressChanged -= handler; } Assert.AreEqual(0, treeGraph.BoundingBox.Bottom); Assert.AreEqual(0, treeGraph.BoundingBox.Left); foreach (var v in treeGraph.Nodes) { Assert.IsTrue(treeGraph.BoundingBox.Contains(v.BoundingBox)); } Assert.AreEqual(1.0, progress, "Progress was never reported as 100%. Last update was at " + progress + "%"); }
public override void Initialize() { EnableDebugViewer(); base.Initialize(); graph = GraphGenerator.GenerateOneSimpleGraph(); GraphGenerator.SetRandomNodeShapes(graph, random); allNodes = graph.Nodes.ToList(); settings = new FastIncrementalLayoutSettings(); settings.AvoidOverlaps = true; }
/// <summary> /// Static layout of graph by gradually adding constraints. /// Uses PivotMds to find initial layout. /// Breaks the graph into connected components (nodes in the same cluster are considered /// connected whether or not there is an edge between them), then lays out each component /// individually. Finally, a simple packing is applied. /// ratio as close as possible to the PackingAspectRatio property (not currently used). /// </summary> public InitialLayout(GeometryGraph graph, FastIncrementalLayoutSettings settings) { ValidateArg.IsNotNull(graph, "graph"); ValidateArg.IsNotNull(settings, "settings"); this.graph = graph; this.settings = new FastIncrementalLayoutSettings(settings); this.settings.ApplyForces = true; this.settings.InterComponentForces = true; this.settings.RungeKuttaIntegration = false; this.settings.RespectEdgePorts = false; }
/// <summary> /// Obtain a starting configuration that is feasible with respect to the structural /// constraints. This is necessary to avoid e.g. cycles in the constraint graph; /// for example, dragging the root of a downward-pointing tree downward below other /// nodes of the tree can result in auto-generation of constraints generating some /// constraints with the root on the right-hand side, and the structural constraints /// have it on the left-hand side. /// /// When AvoidOverlaps==true and we reach ConstraintLevel>=2 then we also need to remove /// overlaps... prior to this we need to force horizontal resolving of overlaps /// between *all* nodes involved in vertical equality constraints (i.e. no skipping), /// and then vertical overlap resolution of all nodes involved in horizontal equality /// constraints /// </summary> static internal void Enforce(FastIncrementalLayoutSettings settings, int currentConstraintLevel, IEnumerable <FiNode> nodes, List <IConstraint> horizontalConstraints, List <IConstraint> verticalConstraints, IEnumerable <Cluster> clusterHierarchies, Func <Cluster, LayoutAlgorithmSettings> clusterSettings) { foreach (LockPosition l in settings.locks) { l.Project(); } ResetPositions(nodes); double dblVpad = settings.NodeSeparation + Pad; double dblHpad = settings.NodeSeparation; double dblCVpad = settings.ClusterMargin + Pad; double dblCHpad = settings.ClusterMargin; for (int level = settings.MinConstraintLevel; level <= currentConstraintLevel; ++level) { // to obtain a feasible solution when equality constraints are present we need to be extra careful // but the solution below is a little bit crummy, is not currently optimized when there are no // equality constraints and we do not really have any scenarios involving equality constraints at // the moment, and also the fact that it turns off DeferToVertical causes it to resolve too // many overlaps horizontally, so let's skip it for now. //if (level >= 2 && settings.AvoidOverlaps) //{ // RemoveOverlapsOnEqualityConstraints(dblVpad, dblHpad, horizontalConstraints, verticalConstraints); //} var hsSolver = new AxisSolver(true, nodes, clusterHierarchies, level >= 2 && settings.AvoidOverlaps, level, clusterSettings); hsSolver.structuralConstraints = horizontalConstraints; hsSolver.OverlapRemovalParameters = new Core.Geometry.OverlapRemovalParameters { AllowDeferToVertical = true, ConsiderProportionalOverlap = settings.IdealEdgeLength.EdgeDirectionConstraints != Core.Geometry.Directions.None }; hsSolver.Initialize(dblHpad, dblVpad, dblCHpad, dblCVpad, v => v.Center); hsSolver.SetDesiredPositions(); hsSolver.Solve(); ResetPositions(nodes); var vsSolver = new AxisSolver(false, nodes, clusterHierarchies, level >= 2 && settings.AvoidOverlaps, level, clusterSettings); vsSolver.structuralConstraints = verticalConstraints; vsSolver.Initialize(dblHpad, dblVpad, dblCHpad, dblCVpad, v => v.Center); vsSolver.SetDesiredPositions(); vsSolver.Solve(); ResetPositions(nodes); } }
private static void LayoutAndValidate(GeometryGraph graph, double separationRatio, ISet<Node> validationExcludedNodes) { const double downwardEdgeSeparation = 70; FastIncrementalLayoutSettings settings = new FastIncrementalLayoutSettings(); settings.IdealEdgeLength = new IdealEdgeLengthSettings { ConstrainedEdgeSeparation = downwardEdgeSeparation, EdgeDirectionConstraints = Directions.South }; settings.AvoidOverlaps = true; InitialLayout initialLayout = new InitialLayout(graph, settings); initialLayout.Run(); ShowGraphInDebugViewer(graph); ValidateDownwardEdgeConstraints(graph, downwardEdgeSeparation, validationExcludedNodes); }
/// <summary> /// Obtain a starting configuration that is feasible with respect to the structural /// constraints. This is necessary to avoid e.g. cycles in the constraint graph; /// for example, dragging the root of a downward-pointing tree downward below other /// nodes of the tree can result in auto-generation of constraints generating some /// constraints with the root on the right-hand side, and the structural constraints /// have it on the left-hand side. /// /// When AvoidOverlaps==true and we reach ConstraintLevel>=2 then we also need to remove /// overlaps... prior to this we need to force horizontal resolving of overlaps /// between *all* nodes involved in vertical equality constraints (i.e. no skipping), /// and then vertical overlap resolution of all nodes involved in horizontal equality /// constraints /// </summary> static internal void Enforce(FastIncrementalLayoutSettings settings, int currentConstraintLevel, IEnumerable<FiNode> nodes, List<IConstraint> horizontalConstraints, List<IConstraint> verticalConstraints, IEnumerable<Cluster> clusterHierarchies, Func<Cluster, LayoutAlgorithmSettings> clusterSettings) { foreach (LockPosition l in settings.locks) { l.Project(); } ResetPositions(nodes); double dblVpad = settings.NodeSeparation + Pad; double dblHpad = settings.NodeSeparation; double dblCVpad = settings.ClusterMargin + Pad; double dblCHpad = settings.ClusterMargin; for (int level = settings.MinConstraintLevel; level <= currentConstraintLevel; ++level) { // to obtain a feasible solution when equality constraints are present we need to be extra careful // but the solution below is a little bit crummy, is not currently optimized when there are no // equality constraints and we do not really have any scenarios involving equality constraints at // the moment, and also the fact that it turns off DeferToVertical causes it to resolve too // many overlaps horizontally, so let's skip it for now. //if (level >= 2 && settings.AvoidOverlaps) //{ // RemoveOverlapsOnEqualityConstraints(dblVpad, dblHpad, horizontalConstraints, verticalConstraints); //} var hsSolver = new AxisSolver(true, nodes, clusterHierarchies, level >= 2 && settings.AvoidOverlaps, level, clusterSettings); hsSolver.structuralConstraints = horizontalConstraints; hsSolver.OverlapRemovalParameters = new Core.Geometry.OverlapRemovalParameters { AllowDeferToVertical = true, ConsiderProportionalOverlap = settings.IdealEdgeLength.EdgeDirectionConstraints != Core.Geometry.Directions.None }; hsSolver.Initialize(dblHpad, dblVpad, dblCHpad, dblCVpad, v => v.Center); hsSolver.SetDesiredPositions(); hsSolver.Solve(); ResetPositions(nodes); var vsSolver = new AxisSolver(false, nodes, clusterHierarchies, level >= 2 && settings.AvoidOverlaps, level, clusterSettings); vsSolver.structuralConstraints = verticalConstraints; vsSolver.Initialize(dblHpad, dblVpad, dblCHpad, dblCVpad, v => v.Center); vsSolver.SetDesiredPositions(); vsSolver.Solve(); ResetPositions(nodes); } }
static void MoveN0ToTheLeft(Node n0, GeometryGraph graph, FastIncrementalLayoutSettings settings) { n0.Center += new Point(-10,0); LockPosition lockPosition = settings.CreateLock(n0,n0.BoundingBox); settings.IncrementalRun(graph); RouteEdges(graph,settings); //LayoutAlgorithmSettings.ShowGraph(graph); settings.ClearLocks(); settings.RemoveLock(lockPosition); }
static void TestFD() { GeometryGraph graph = CreateGeometryGraphForFD(); //LayoutAlgorithmSettings.ShowGraph(graph); var settings = new FastIncrementalLayoutSettings { AvoidOverlaps = true, ApplyForces = false, RungeKuttaIntegration = true }; var ir = new InitialLayout(graph, settings); ir.Run(); RouteEdges(graph, settings); //LayoutAlgorithmSettings.ShowGraph(graph); // AddNodeFd(graph); var n = new Node(CurveFactory.CreateDiamond(200, 200, new Point(350, 230))); var e = new Edge(n, graph.Nodes[42]); graph.Edges.Add(e); e = new Edge(n, graph.Nodes[6]); graph.Edges.Add(e); e = new Edge(n, graph.Nodes[12]); graph.Edges.Add(e); graph.Nodes.Add(n); graph.RootCluster.AddChild(n); settings.algorithm=new FastIncrementalLayout(graph, settings, settings.MaxConstraintLevel, f=>settings); settings.Unconverge(); settings.CreateLock(n, new Rectangle(200, 400, 500, 100)); do { settings.IncrementalRun(graph); } while (!settings.Converged); RouteEdges(graph, settings); #if DEBUG LayoutAlgorithmSettings.ShowGraph(graph); #endif Environment.Exit(0); }
static void RouteEdges(GeometryGraph graph, FastIncrementalLayoutSettings settings) { if (graph.Edges.Count < 1000) { var router = new SplineRouter(graph, settings.EdgeRoutingSettings.Padding, settings.EdgeRoutingSettings.PolylinePadding, settings.EdgeRoutingSettings.ConeAngle, settings.EdgeRoutingSettings.BundlingSettings); router.Run(); } else { var sr = new StraightLineEdges(graph.Edges, 1); sr.Run(); } }
/// <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); }
public void RoutingWithThreeGroups() { var graph = LoadGraph("abstract.msagl.geom"); var root = graph.RootCluster; var a = new Cluster { UserData = "a" }; foreach (string id in new[] { "17", "39", "13", "19", "28", "12" }) a.AddChild(graph.FindNodeByUserData(id)); var b = new Cluster { UserData = "b" }; b.AddChild(a); b.AddChild(graph.FindNodeByUserData("18")); root.AddChild(b); var c = new Cluster { UserData = "c" }; foreach (string id in new[] { "30", "5", "6", "7", "8" }) c.AddChild(graph.FindNodeByUserData(id)); root.AddChild(c); var clusterNodes = new Set<Node>(root.AllClustersDepthFirst().SelectMany(cl => cl.Nodes)); foreach (var node in graph.Nodes.Where(n => clusterNodes.Contains(n) == false)) root.AddChild(node); FixClusterBoundariesWithNoRectBoundaries(root, 5); var defaultSettings = new FastIncrementalLayoutSettings(); var rootSettings = new FastIncrementalLayoutSettings() { AvoidOverlaps = true }; var initialLayout = new InitialLayoutByCluster(graph, new[] { graph.RootCluster }, cl => cl == root ? rootSettings : defaultSettings); initialLayout.Run(); const double Padding = 5; SplineRouter splineRouter = new SplineRouter(graph, Padding/3, Padding, Math.PI / 6); splineRouter.Run(); #if TEST_MSAGL if (!DontShowTheDebugViewer()) { graph.UpdateBoundingBox(); DisplayGeometryGraph.ShowGraph(graph); } #endif }
public void GraphModelGroupedForceDirectedRectilinearTest() { LayoutAlgorithmSettings settings; var graph = LoadGraph("GraphModelGrouped.msagl.geom", out settings); settings = new FastIncrementalLayoutSettings { NodeSeparation = 5, PackingAspectRatio = 1.2, AvoidOverlaps = true, GravityConstant = 0.5 }; settings.EdgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.Rectilinear; settings.EdgeRoutingSettings.Padding = 2; settings.EdgeRoutingSettings.CornerRadius = 2; var layout = new InitialLayoutByCluster(graph, settings); foreach (var c in graph.RootCluster.AllClustersDepthFirst().Where(c => c != graph.RootCluster)) { c.RectangularBoundary = new RectangularClusterBoundary { LeftMargin = 5, RightMargin = 5, BottomMargin = 5, TopMargin = 5 }; c.BoundaryCurve = CurveFactory.CreateRectangle(30, 30, new Point(15, 15)); } double progress = 0.0; EnableDebugViewer(); EventHandler<ProgressChangedEventArgs> handler = (s, e) => progress = e.RatioComplete; try { layout.ProgressChanged += handler; layout.Run(); } finally { layout.ProgressChanged -= handler; } Assert.IsTrue(1.0 <= progress, "Progress was never reported as 100%. Last update was at " + progress + "%"); ShowGraphInDebugViewer(graph); foreach (var e in graph.Edges) { Assert.IsNotNull(e.Curve, "Edge was not routed"); } }
static GeometryGraph GetTestGraphWithClusters(out LayoutAlgorithmSettings settings) { GeometryGraph graph = GeometryGraphReader.CreateFromFile( "C:\\dev\\GraphLayout\\MSAGLTests\\Resources\\MSAGLGeometryGraphs\\abstract.msagl.geom", //"E:\\dev\\MSAGL\\GraphLayout\\MSAGLTests\\Resources\\MSAGLGeometryGraphs\\abstract.msagl.geom", out settings); foreach (var edge in graph.Edges) { edge.Curve = null; edge.EdgeGeometry.TargetArrowhead = null; } graph.UpdateBoundingBox(); var root = graph.RootCluster; var a = new Cluster {UserData = "a"}; foreach (string id in new[] {"17", "39", "13", "19", "28", "12"}) a.AddChild(graph.FindNodeByUserData(id)); var b = new Cluster {UserData = "b"}; b.AddChild(a); b.AddChild(graph.FindNodeByUserData("18")); root.AddChild(b); var c = new Cluster {UserData = "c"}; foreach (string id in new[] {"30", "5", "6", "7", "8"}) c.AddChild(graph.FindNodeByUserData(id)); root.AddChild(c); var clusterNodes = new Set<Node>(root.AllClustersDepthFirst().SelectMany(cl => cl.Nodes)); foreach (var node in graph.Nodes.Where(n => clusterNodes.Contains(n) == false)) root.AddChild(node); FixClusterBoundariesWithNoRectBoundaries(root, 5); var fastIncrementalLayoutSettings = new FastIncrementalLayoutSettings(); var d=new Dictionary<Cluster, LayoutAlgorithmSettings>(); d[root] = new FastIncrementalLayoutSettings { AvoidOverlaps = true }; var initialLayout = new InitialLayoutByCluster(graph, fastIncrementalLayoutSettings); initialLayout.Run(); graph.UpdateBoundingBox(); //FixClusterBoundariesWithNoRectBoundaries(root, 5); return graph; }
static void FillClustersAndSettings(FastIncrementalLayoutSettings settings, GeometryGraph geometryGraph) { settings.AvoidOverlaps = true; // settings.RectangularClusters = true; var root = new Cluster(); var cluster = new Cluster(); root.AddChild(cluster); for (int i = 0; i < 4; i++) { var istring = i.ToString(); cluster.AddChild(geometryGraph.Nodes.First(n => n.UserData.ToString() == istring)); } cluster.BoundaryCurve = cluster.BoundingBox.Perimeter(); cluster = new Cluster(); root.AddChild(cluster); geometryGraph.RootCluster.AddChild(root); //make a subcluster var parent = cluster; cluster = new Cluster(); cluster.AddChild(geometryGraph.Nodes.First(n => n.UserData.ToString() == "4")); cluster.AddChild(geometryGraph.Nodes.First(n => n.UserData.ToString() == "5")); parent.AddChild(cluster); cluster = new Cluster(); for (int i = 6; i < 9; i++) { var istring = i.ToString(); cluster.AddChild(geometryGraph.Nodes.First(n => n.UserData.ToString() == istring)); } parent.AddChild(cluster); foreach (var cl in geometryGraph.RootCluster.AllClustersDepthFirst()) { if(cl.BoundaryCurve==null) cl.BoundaryCurve=cl.BoundingBox.Perimeter(); } }
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(); }
static void Layout(GeometryGraph graph, Random rnd) { if (rnd.Next() % 5 < 3){ var settings = new SugiyamaLayoutSettings(); var layout = new InitialLayoutByCluster(graph, settings) {RunInParallel = false}; layout.Run(); } else { var settings = new FastIncrementalLayoutSettings { AvoidOverlaps = true }; var layout = new InitialLayoutByCluster(graph, settings); layout.Run(); } }
public void TreeGraphFastIncrementalLayout() { GeometryGraph treeGraph = GraphGenerator.GenerateTree(20); treeGraph.RootCluster = new Cluster(treeGraph.Nodes); var settings = new FastIncrementalLayoutSettings { AvoidOverlaps = true, PackingAspectRatio = 1.6 }; settings.EdgeRoutingSettings = new EdgeRoutingSettings { EdgeRoutingMode = EdgeRoutingMode.Spline, ConeAngle = Math.PI / 6, Padding = settings.NodeSeparation / 2.1 }; foreach (var v in treeGraph.Nodes) { v.BoundingBox = new Rectangle(0, 0, new Point(30, 30)); } foreach (var e in treeGraph.Edges) { e.EdgeGeometry.SourceArrowhead = new Arrowhead { Length = 4, Width = 4 }; e.EdgeGeometry.TargetArrowhead = new Arrowhead { Length = 8, Width = 4 }; } var layout = new InitialLayoutByCluster(treeGraph, settings); double progress = 0.0; EventHandler<ProgressChangedEventArgs> handler = (s, e) => progress = e.RatioComplete; try { layout.ProgressChanged += handler; layout.Run(); } finally { layout.ProgressChanged -= handler; } EnableDebugViewer(); ShowGraphInDebugViewer(treeGraph); const double EdgeLengthDelta = 0.5; foreach (var e in treeGraph.Edges) { Assert.IsNotNull(e.Curve, "Edge curves not populated"); if (e.Source != e.Target) { double actualLength = (e.Source.Center - e.Target.Center).Length; double actualDesiredRatio = e.Length / actualLength; Assert.AreEqual(1, actualDesiredRatio, EdgeLengthDelta, "Edge length is not correct"); } } double aspectRatio = treeGraph.BoundingBox.Width / treeGraph.BoundingBox.Height; Assert.AreEqual(settings.PackingAspectRatio, aspectRatio, 0.2, "Aspect ratio too far from desired"); Assert.AreEqual(1.0, progress, "Progress was never reported as 100%. Last update was at " + progress + "%"); }
/// <summary> /// /// Non-overlap between nodes in the same cluster (or the root cluster), between the convex hulls /// of clusters and nodes that do belong to those clusters and between clusters and clusters. /// </summary> /// <param name="cluster"></param> /// <param name="settings">for padding extra space around nodes</param> public AllPairsNonOverlappingBoundaries(Cluster cluster, FastIncrementalLayoutSettings settings) { foreach (var v in cluster.nodes) { hulls.Add(new RCHull(null,v, settings.NodeSeparation)); } foreach (var c in cluster.clusters) { traverseClusters(null, c, settings.NodeSeparation); } }
public void GraphModelGroupedForceDirectedSplineTest() { LayoutAlgorithmSettings settings; var graph = LoadGraph("GraphModelGrouped.msagl.geom", out settings); settings = new FastIncrementalLayoutSettings { NodeSeparation = 5, PackingAspectRatio = 1.2, AvoidOverlaps = true }; settings.EdgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.Spline; settings.EdgeRoutingSettings.Padding = 2; var layout = new InitialLayoutByCluster(graph, settings); foreach (var c in graph.RootCluster.AllClustersDepthFirst().Where(c => c != graph.RootCluster)) { c.RectangularBoundary = new RectangularClusterBoundary { LeftMargin = 5, RightMargin = 5, BottomMargin = 5, TopMargin = 5 }; c.BoundaryCurve = CurveFactory.CreateRectangle(30, 30, new Point(15, 15)); } double progress = 0.0; EnableDebugViewer(); EventHandler<ProgressChangedEventArgs> handler = (s, e) => progress = e.RatioComplete; try { layout.ProgressChanged += handler; layout.Run(); } finally { layout.ProgressChanged -= handler; } // Ignore this assertion due to bug: 688960 - One MSAGL unit test is failing due to Parallel Linq which affects the Progress accounting. //Assert.AreEqual(1.0, progress, "Progress was never reported as 100%. Last update was at " + progress + "%"); ShowGraphInDebugViewer(graph); foreach (var e in graph.Edges) { Assert.IsNotNull(e.Curve, "Edge was not routed"); } }
static void Main(string[] args) { #if DEBUG DisplayGeometryGraph.SetShowFunctions(); #endif ArgsParser.ArgsParser argsParser = SetArgsParser(args); if (argsParser.OptionIsUsed(PolygonDistanceTestOption)) TestPolygonDistance(); else if (argsParser.OptionIsUsed(TestCdtThreaderOption)) TestCdtThreader(); else if (argsParser.OptionIsUsed(RandomBundlingTest)) RandomBundlingTests.RsmContent(); bundling = argsParser.OptionIsUsed(BundlingOption); var gviewer = new GViewer(); gviewer.MouseMove += Draw.GviewerMouseMove; if(argsParser.OptionIsUsed(FdOption)) { TestFD(); gviewer.CurrentLayoutMethod = LayoutMethod.IcrementalLayout; } Form form = CreateForm(null, gviewer); if (argsParser.OptionIsUsed(AsyncLayoutOption)) gviewer.AsyncLayout = true; string listOfFilesFile = argsParser.GetValueOfOptionWithAfterString(ListOfFilesOption); if (listOfFilesFile != null) { ProcessListOfFiles(listOfFilesFile, argsParser); return; } string fileName = argsParser.GetValueOfOptionWithAfterString(FileOption); string ext = Path.GetExtension(fileName); if (ext != null) { ext = ext.ToLower(); if (ext == ".dot") { ProcessDotFile(gviewer, argsParser, fileName); } else { if (ext == ".geom") { GeometryGraph geometryGraph = GeometryGraphReader.CreateFromFile(fileName); geometryGraph.Margins = 10; FixHookPorts(geometryGraph); // if (argsParser.OptionIsUsed(BundlingOption)) { for (int i = 0; i < 1; i++) { #if DEBUG /*DisplayGeometryGraph.ShowGraph(geometryGraph); var l = new List<DebugCurve>(); l.AddRange(geometryGraph.Nodes.Select(n=>new DebugCurve(100,1,"black",n.BoundaryCurve))); l.AddRange(geometryGraph.Edges.Select(e=>new DebugCurve(100,1,"black", new LineSegment(e.Source.Center,e.Target.Center)))); foreach (var cl in geometryGraph.RootCluster.AllClustersDepthFirst()) { l.Add(new DebugCurve(100,2,"blue",cl.BoundaryCurve)); foreach (var node in cl.Nodes) l.Add(new DebugCurve(100, 2, "brown", node.BoundaryCurve)); foreach (var e in cl.Edges) l.Add(new DebugCurve(100, 2, "pink", new LineSegment(e.Source.Center, e.Target.Center))); } DisplayGeometryGraph.ShowDebugCurves(l.ToArray());*/ #endif BundlingSettings bs = GetBundlingSettings(argsParser); double loosePadding; double tightPadding = GetPaddings(argsParser, out loosePadding); if (argsParser.OptionIsUsed(MdsOption)) { var mdsLayoutSettings = new MdsLayoutSettings {RemoveOverlaps = true, NodeSeparation = loosePadding*3}; var mdsLayout = new MdsGraphLayout(mdsLayoutSettings, geometryGraph); mdsLayout.Run(); } else { if (argsParser.OptionIsUsed(FdOption)) { var settings = new FastIncrementalLayoutSettings {AvoidOverlaps = true}; (new InitialLayout(geometryGraph, settings)).Run(); } } var splineRouter = new SplineRouter(geometryGraph, geometryGraph.Edges, tightPadding, loosePadding, Math.PI/6, bs); splineRouter.Run(); } #if DEBUG DisplayGeometryGraph.ShowGraph(geometryGraph); #endif return; } else { if (ext == ".msagl") { Graph graph = Graph.Read(fileName); // DisplayGeometryGraph.ShowGraph(graph.GeometryGraph); if (graph != null) { if (argsParser.OptionIsUsed(BundlingOption)) { BundlingSettings bs = GetBundlingSettings(argsParser); double loosePadding; double tightPadding = GetPaddings(argsParser, out loosePadding); var br = new SplineRouter(graph.GeometryGraph, tightPadding, loosePadding, Math.PI/6, bs); br.Run(); // DisplayGeometryGraph.ShowGraph(graph.GeometryGraph); } } gviewer.NeedToCalculateLayout = false; gviewer.Graph = graph; gviewer.NeedToCalculateLayout = true; } } } } else if (argsParser.OptionIsUsed(TestCdtOption)) { Triangulation(argsParser.OptionIsUsed(ReverseXOption)); Environment.Exit(0); } else if (argsParser.OptionIsUsed(TestCdtOption0)) { TestTriangulationOnSmallGraph(argsParser); Environment.Exit(0); } else if (argsParser.OptionIsUsed(TestCdtOption2)) { TestTriangulationOnPolys(); Environment.Exit(0); } else if (argsParser.OptionIsUsed(TestCdtOption1)) { ThreadThroughCdt(); Environment.Exit(0); } else if (argsParser.OptionIsUsed(ConstraintsTestOption)) TestGraphWithConstraints(); Application.Run(form); }
void GenerateOrthogonalOrderingConstraints(IEnumerable<Node> nodes, FastIncrementalLayoutSettings settings) { Node p = null; foreach (var v in graph.Nodes.OrderBy(v => v.Center.X)) { if (p != null) { settings.AddStructuralConstraint(new HorizontalSeparationConstraint(p, v, 0.1)); } p = v; } p = null; foreach (var v in graph.Nodes.OrderBy(v => v.Center.Y)) { if (p != null) { settings.AddStructuralConstraint(new VerticalSeparationConstraint(p, v, 0.1)); } p = v; } }
/// <summary> /// Create the graph data structures. /// </summary> /// <param name="geometryGraph"></param> /// <param name="settings">The settings for the algorithm.</param> /// <param name="initialConstraintLevel">initialize at this constraint level</param> /// <param name="clusterSettings">settings by cluster</param> internal FastIncrementalLayout(GeometryGraph geometryGraph, FastIncrementalLayoutSettings settings, int initialConstraintLevel, Func <Cluster, LayoutAlgorithmSettings> clusterSettings) { graph = geometryGraph; this.settings = settings; this.clusterSettings = clusterSettings; int i = 0; ICollection <Node> allNodes = graph.Nodes; nodes = new FiNode[allNodes.Count]; foreach (Node v in allNodes) { v.AlgorithmData = nodes[i] = new FiNode(i, v); i++; } clusterEdges.Clear(); edges.Clear(); foreach (Edge e in graph.Edges) { if (e.Source is Cluster || e.Target is Cluster) { clusterEdges.Add(e); } else { edges.Add(new FiEdge(e)); } foreach (var l in e.Labels) { l.InnerPoints = l.OuterPoints = null; } } SetLockNodeWeights(); components = new List <FiNode[]>(); if (!settings.InterComponentForces) { basicGraph = new BasicGraph <FiEdge>(edges, nodes.Length); foreach (var componentNodes in ConnectedComponentCalculator <FiEdge> .GetComponents(basicGraph)) { var vs = new FiNode[componentNodes.Count()]; int vi = 0; foreach (int v in componentNodes) { vs[vi++] = nodes[v]; } components.Add(vs); } } else // just one big component (regardless of actual edges) { components.Add(nodes); } horizontalSolver = new AxisSolver(true, nodes, new[] { geometryGraph.RootCluster }, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings) { OverlapRemovalParameters = new OverlapRemovalParameters { AllowDeferToVertical = true, // use "ProportionalOverlap" mode only when iterative apply forces layout is being used. // it is not necessary otherwise. ConsiderProportionalOverlap = settings.ApplyForces } }; verticalSolver = new AxisSolver(false, nodes, new[] { geometryGraph.RootCluster }, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings); SetupConstraints(); geometryGraph.RootCluster.ComputeWeight(); foreach ( Cluster c in geometryGraph.RootCluster.AllClustersDepthFirst().Where(c => c.RectangularBoundary == null) ) { c.RectangularBoundary = new RectangularClusterBoundary(); } CurrentConstraintLevel = initialConstraintLevel; }
/// <summary> /// reads the layout algorithm settings /// </summary> LayoutAlgorithmSettings ReadLayoutAlgorithmSettings(XmlReader reader) { LayoutAlgorithmSettings layoutSettings = null; CheckToken(GeometryToken.LayoutAlgorithmSettings); if (reader.IsEmptyElement) { reader.Read(); return null; } //reader.Read(); var edgeRoutingMode = (EdgeRoutingMode) GetIntAttributeOrDefault(GeometryToken.EdgeRoutingMode, (int) EdgeRoutingMode.Spline); var str = GetAttribute(GeometryToken.LayoutAlgorithmType); if (XmlReader.NodeType == XmlNodeType.EndElement) { //todo - support fastincremental settings layoutSettings = new FastIncrementalLayoutSettings(); EdgeRoutingSettings routingSettings = layoutSettings.EdgeRoutingSettings; routingSettings.EdgeRoutingMode = edgeRoutingMode; } else { if (str != null) { var token = (GeometryToken) Enum.Parse(typeof (GeometryToken), str, false); if (token == GeometryToken.SugiyamaLayoutSettings) { layoutSettings = ReadSugiyamaLayoutSettings(edgeRoutingMode); } else if (token == GeometryToken.MdsLayoutSettings) { var mds = new MdsLayoutSettings(); EdgeRoutingSettings routingSettings = mds.EdgeRoutingSettings; routingSettings.EdgeRoutingMode = edgeRoutingMode; layoutSettings = mds; if (XmlReader.IsStartElement(GeometryToken.Reporting.ToString())) { #if REPORTING mds.Reporting = #endif ReadBooleanElement(GeometryToken.Reporting); } mds.Exponent = ReadDoubleElement(reader); mds.IterationsWithMajorization = ReadIntElement(GeometryToken.IterationsWithMajorization); mds.PivotNumber = ReadIntElement(GeometryToken.PivotNumber); mds.RotationAngle = ReadDoubleElement(reader); mds.ScaleX = ReadDoubleElement(reader); mds.ScaleY = ReadDoubleElement(reader); } else //todo - write a reader and a writer for FastIncrementalLayoutSettings throw new NotImplementedException(); } } reader.ReadEndElement(); return layoutSettings; }
/// <summary> /// Simple unconstrained layout of graph used by multiscale layout /// </summary> /// <param name="graph"></param> /// <param name="edgeLengthMultiplier"></param> /// <param name="level">double in range [0,1] indicates how far down the multiscale stack we are /// 1 is at the top, 0 at the bottom, controls repulsive force strength and ideal edge length</param> private void SimpleLayout(GeometryGraph graph, double level, double edgeLengthMultiplier) { var settings = new FastIncrementalLayoutSettings { MaxIterations = 10, MinorIterations = 3, GravityConstant = 1.0 - level, RepulsiveForceConstant = Math.Log(edgeLengthMultiplier * level * 500 + Math.E), InitialStepSize = 0.6 }; foreach (var e in graph.Edges) e.Length *= Math.Log(level * 500 + Math.E); settings.InitializeLayout(graph, settings.MinConstraintLevel); do { #pragma warning disable 618 LayoutHelpers.CalculateLayout(graph, settings, null); #pragma warning restore 618 } while (settings.Iterations < settings.MaxIterations); }
/// <summary> /// Create the graph data structures. /// </summary> /// <param name="geometryGraph"></param> /// <param name="settings">The settings for the algorithm.</param> /// <param name="initialConstraintLevel">initialize at this constraint level</param> /// <param name="clusterSettings">settings by cluster</param> internal FastIncrementalLayout(GeometryGraph geometryGraph, FastIncrementalLayoutSettings settings, int initialConstraintLevel, Func<Cluster, LayoutAlgorithmSettings> clusterSettings) { graph = geometryGraph; this.settings = settings; this.clusterSettings = clusterSettings; int i = 0; ICollection<Node> allNodes = graph.Nodes; nodes = new FiNode[allNodes.Count]; foreach (Node v in allNodes) { v.AlgorithmData = nodes[i] = new FiNode(i, v); i++; } clusterEdges.Clear(); edges.Clear(); foreach (Edge e in graph.Edges) { if (e.Source is Cluster || e.Target is Cluster) clusterEdges.Add(e); else edges.Add(new FiEdge(e)); foreach (var l in e.Labels) l.InnerPoints = l.OuterPoints = null; } SetLockNodeWeights(); components = new List<FiNode[]>(); if (!settings.InterComponentForces) { basicGraph = new BasicGraph<FiEdge>(edges, nodes.Length); foreach (var componentNodes in ConnectedComponentCalculator<FiEdge>.GetComponents(basicGraph)) { var vs = new FiNode[componentNodes.Count()]; int vi = 0; foreach (int v in componentNodes) { vs[vi++] = nodes[v]; } components.Add(vs); } } else // just one big component (regardless of actual edges) components.Add(nodes); horizontalSolver = new AxisSolver(true, nodes, new[] {geometryGraph.RootCluster}, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings) { OverlapRemovalParameters = new OverlapRemovalParameters { AllowDeferToVertical = true, // use "ProportionalOverlap" mode only when iterative apply forces layout is being used. // it is not necessary otherwise. ConsiderProportionalOverlap = settings.ApplyForces } }; verticalSolver = new AxisSolver(false, nodes, new[] {geometryGraph.RootCluster}, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings); SetupConstraints(); geometryGraph.RootCluster.ComputeWeight(); foreach ( Cluster c in geometryGraph.RootCluster.AllClustersDepthFirst().Where(c => c.RectangularBoundary == null) ) { c.RectangularBoundary = new RectangularClusterBoundary(); } CurrentConstraintLevel = initialConstraintLevel; }
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); }
public static void Layout(GeometryGraph graph) { foreach (Node n in graph.Nodes) n.BoundaryCurve = CurveFactory.CreateEllipse(20.0, 10.0, new Point()); foreach (Cluster c in graph.RootCluster.AllClustersDepthFirst()) c.BoundaryCurve = c.BoundingBox.Perimeter(); var settings = new FastIncrementalLayoutSettings(); settings.AvoidOverlaps = true; settings.NodeSeparation = 30; settings.RouteEdges = true; LayoutHelpers.CalculateLayout(graph, settings, new CancelToken()); foreach (Cluster c in graph.RootCluster.AllClustersDepthFirst()) c.BoundaryCurve = c.BoundingBox.Perimeter(); var bundlingsettings = new BundlingSettings() { EdgeSeparation = 5, CreateUnderlyingPolyline = true }; var router = new SplineRouter(graph, 10.0, 1.25, Math.PI / 6.0, bundlingsettings); router.Run(); graph.UpdateBoundingBox(); }