示例#1
0
 /// <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;
 }
示例#2
0
        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;
        }
示例#6
0
        /// <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);
     }
 }
示例#9
0
        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);
        }
示例#10
0
        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);
        }
示例#11
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");
            }
        }
示例#15
0
        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;
        }
 /// <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;
 }
示例#17
0
        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");
            }
        }
示例#23
0
        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;
     }
 }
示例#25
0
        /// <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);
 }
示例#30
0
        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();
        }