/// <summary>
 /// Computes the "stress" of the current layout of the given graph:
 /// 
 ///   stress = sum_{(u,v) in V} D(u,v)^(-2) (d(u,v) - D(u,v))^2
 /// 
 /// where:
 ///   V is the set of nodes
 ///   d(u,v) is the euclidean distance between the centers of nodes u and v
 ///   D(u,v) is the graph-theoretic path length between u and v - scaled by average edge length.
 ///   
 /// The idea of “stress” in graph layout is that nodes that are immediate neighbors should be closer 
 /// together than nodes that are a few hops apart (i.e. that have path length>1).  More generally 
 /// the distance between nodes in the drawing should be proportional to the path length between them.  
 /// The lower the “stress” score of a particular graph layout the better it conforms to this ideal.
 /// 
 /// </summary>
 /// <param name="graph"></param>
 /// <returns></returns>
 public static double Stress(GeometryGraph graph)
 {
     ValidateArg.IsNotNull(graph, "graph");
     double stress = 0;
     if (graph.Edges.Count == 0)
     {
         return stress;
     }
     var apd = new AllPairsDistances(graph, false);
     apd.Run();
     var D = apd.Result;
     double l = graph.Edges.Average(e => e.Length);
     int i = 0;
     foreach (var u in graph.Nodes)
     {
         int j = 0;
         foreach (var v in graph.Nodes)
         {
             if (i != j)
             {
                 double duv = (u.Center - v.Center).Length;
                 double Duv = l * D[i][j];
                 double d = Duv - duv;
                 stress += d * d / (Duv * Duv);
             }
             ++j;
         }
         ++i;
     }
     return stress;
 }
        internal static void RankGraph(LgData lgData, GeometryGraph mainGeometryGraph) {
//fromDrawingToEdgeInfo = new Dictionary<ICurve, LgEdgeInfo>();
            foreach (var connectedGraph in lgData.ConnectedGeometryGraphs)
                RankTheGraph(lgData, mainGeometryGraph, connectedGraph);

            UpdateRanksOfClusters(lgData);
        }
        /// <summary>
        /// create a geometry edge, the geometry source and target have to be set already
        /// </summary>
        /// <param name="drawingEdge"></param>
        /// <param name="msaglGraph"></param>
        /// <returns></returns>
        static Core.Layout.Edge CreateGeometryEdgeAndAddItToGeometryGraph(Edge drawingEdge, GeometryGraph msaglGraph) {
            var msaglEdge = CreateGeometryEdgeFromDrawingEdge(drawingEdge);

            msaglGraph.Edges.Add(msaglEdge);
            
            return msaglEdge;
        }
Example #4
0
        /// <summary>
        /// Computes the standard deviation of the edge length change for a set of given edges.
        /// </summary>
        /// <param name="graphOld"></param>
        /// <param name="graphNew"></param>
        /// <param name="proximityEdges"></param>
        /// <returns></returns>
        public static Tuple<String, double> EdgeLengthDeviation(GeometryGraph graphOld, GeometryGraph graphNew,
                                                 HashSet<Tuple<int, int>> proximityEdges)
        {
            if (proximityEdges.Count == 0) return Tuple.Create("EdgeLengthDeviation", -1.0);
            double meanRatio = 0;
            foreach (Tuple<int, int> p in proximityEdges) {
                double oldDist=(graphOld.Nodes[p.Item1].Center - graphOld.Nodes[p.Item2].Center).Length;
                double newDist = (graphNew.Nodes[p.Item1].Center - graphNew.Nodes[p.Item2].Center).Length;

                double ratio = newDist/oldDist;
                meanRatio += ratio;

            }
            meanRatio /= proximityEdges.Count;

            double standardDeviation = 0;
            foreach (Tuple<int, int> p in proximityEdges) {
                double oldDist = (graphOld.Nodes[p.Item1].Center - graphOld.Nodes[p.Item2].Center).Length;
                double newDist = (graphNew.Nodes[p.Item1].Center - graphNew.Nodes[p.Item2].Center).Length;

                double deviation = (newDist / oldDist)-meanRatio;
                deviation = deviation * deviation;

                standardDeviation += deviation;

            }
            standardDeviation = Math.Sqrt(standardDeviation/proximityEdges.Count)/meanRatio;

            return Tuple.Create("ProximityEdgeLengthDeviation",standardDeviation);
        }
        internal ConstrainedOrdering(
            GeometryGraph geomGraph,
            BasicGraph<Node, IntEdge> basicIntGraph,
            int[] layering,
            Dictionary<Node, int> nodeIdToIndex,
            Database database,
            SugiyamaLayoutSettings settings) {

            this.settings = settings;
            horizontalConstraints = settings.HorizontalConstraints;

            horizontalConstraints.PrepareForOrdering(nodeIdToIndex, layering);

            geometryGraph = geomGraph;
            this.database = database;
            intGraph = basicIntGraph;
            initialLayering = layering;
            //this has to be changed only to insert layers that are needed
            if (NeedToInsertLayers(layering)) {
                for (int i = 0; i < layering.Length; i++)
                    layering[i] *= 2;
                LayersAreDoubled = true;
                numberOfLayers = -1;
            }

            PrepareProperLayeredGraphAndFillLayerInfos();

            adjSwapper = new AdjacentSwapsWithConstraints(
                LayerArrays,
                HasCrossWeights(),
                ProperLayeredGraph,
                layerInfos);
        }
Example #6
0
        public static GeometryGraph CopyGraph(GeometryGraph graph)
        {
            if (graph == null) return null;
            var copy = new GeometryGraph();

            Dictionary<Node,Node> nodeCopy=new Dictionary<Node, Node>(graph.Nodes.Count);

            foreach (Node node in graph.Nodes) {
                var c = new Node();
                copy.Nodes.Add(c);
                nodeCopy[node] = c;
                c.BoundaryCurve = node.BoundaryCurve.Clone();
            }

            foreach (Edge edge in graph.Edges) {
                var source = edge.Source;
                var target = edge.Target;
                var copySource = nodeCopy[source];
                var copyTarget = nodeCopy[target];
                Edge edgeCopy=new Edge(copySource,copyTarget);
                copy.Edges.Add(edgeCopy);
                StraightLineEdges.RouteEdge(edgeCopy,0);
            }

            return copy;
        }
Example #7
0
        internal static GeometryGraph CreateAndLayoutGraph()
        {
            double w = 30;
            double h = 20;
            GeometryGraph graph = new GeometryGraph();
            Node a = new Node( new Ellipse(w, h, new P()),"a");
            Node b = new Node( CurveFactory.CreateRectangle(w, h, new P()),"b");
            Node c = new Node( CurveFactory.CreateRectangle(w, h, new P()),"c");
            Node d = new Node(CurveFactory.CreateRectangle(w, h, new P()), "d");

            graph.Nodes.Add(a);
            graph.Nodes.Add(b);
            graph.Nodes.Add(c);
            graph.Nodes.Add(d);
            Edge e = new Edge(a, b) { Length = 10 };
            graph.Edges.Add(e);
            graph.Edges.Add(new Edge(b, c) { Length = 3 });
            graph.Edges.Add(new Edge(b, d) { Length = 4 });

            //graph.Save("c:\\tmp\\saved.msagl");
            var settings = new Microsoft.Msagl.Layout.MDS.MdsLayoutSettings();
            LayoutHelpers.CalculateLayout(graph, settings, null);

            return graph;
        }
        /// <summary>
        /// Generate lattice graph with given number of nodes
        /// </summary>
        /// <returns>A graph with lattice pattern</returns>
        public static GeometryGraph GenerateSquareLattice(int nodeCount) {
            GeometryGraph graph = new GeometryGraph();

            int nodesOnOneEdge = (int)Math.Ceiling(Math.Sqrt(nodeCount));

            for (int i = 0; i < nodesOnOneEdge; i++) {
                for (int j = 0; j < nodesOnOneEdge; j++) {
                    Node node = CreateNode(graph.Nodes.Count.ToString(CultureInfo.InvariantCulture));

                    graph.Nodes.Add(node);

                    if (i > 0) {
                        List<Node> allNodes = graph.Nodes.ToList();
                        Node sourceNode = allNodes[(graph.Nodes.Count - 1) - nodesOnOneEdge];
                        Node targetNode = allNodes[graph.Nodes.Count - 1];
                        Edge edge = CreateEdge(sourceNode, targetNode);
                        graph.Edges.Add(edge);
                    }

                    if (j > 0) {
                        List<Node> allNodes = graph.Nodes.ToList();
                        Node sourceNode = allNodes[graph.Nodes.Count - 2];
                        Node targetNode = allNodes[graph.Nodes.Count - 1];
                        Edge edge = CreateEdge(sourceNode, targetNode);
                        graph.Edges.Add(edge);
                    }
                }
            }

            return graph;
        }
        public void SimpleDeepTranslationTest()
        {
            var graph = new GeometryGraph();
            var a = new Node(CurveFactory.CreateRectangle(30, 20, new Point()));
            var b = new Node(CurveFactory.CreateRectangle(30, 20, new Point(100, 0)));
            var e = new Edge(a, b);
            graph.Nodes.Add(a);
            graph.Nodes.Add(b);
            graph.Edges.Add(e);
            var c = CreateCluster(new Node[] { a, b }, 10);
            c.CalculateBoundsFromChildren(0);
            var originalClusterBounds = c.BoundingBox;
            RouteEdges(graph, 10);
            var edgeBounds = e.BoundingBox;

            Assert.AreEqual(c.BoundingBox.Width, 150, "Cluster has incorrect width");
            Assert.AreEqual(c.BoundingBox.Height, 40, "Cluster has incorrect width");

            var delta = new Point(10, 20);
            c.DeepTranslation(delta, true);
            Rectangle translatedClusterBounds = c.BoundingBox;

            Assert.IsTrue(ApproximateComparer.Close((translatedClusterBounds.LeftBottom - originalClusterBounds.LeftBottom), delta), "edge was not translated");

            c.CalculateBoundsFromChildren(0);

            Assert.IsTrue(ApproximateComparer.Close(translatedClusterBounds, c.BoundingBox), "translated bounds do not equal computed bounds of translated cluster");
            Assert.IsTrue(ApproximateComparer.Close((e.BoundingBox.LeftBottom - edgeBounds.LeftBottom), delta), "edge was not translated");
        }
        static void PostRunTransform(GeometryGraph geometryGraph, PlaneTransformation transformation)
        {
            bool transform = !transformation.IsIdentity;
            if (transform)
            {
                foreach (Node n in geometryGraph.Nodes)
                {
                    n.Transform(transformation);
                }

                //restore labels widths and heights
                foreach (Edge e in geometryGraph.Edges)
                {
                    if (e.Label != null)
                    {
                        e.Label.Width = e.OriginalLabelWidth;
                        e.Label.Height = e.OriginalLabelHeight;
                    }
                }

                TransformCurves(geometryGraph, transformation);
            }

            geometryGraph.UpdateBoundingBox();
        }
        /// <summary>
        /// Calculates the graph layout
        /// </summary>
        /// <exception cref="CancelException">Thrown when the layout is canceled.</exception>
#else
        /// <summary>
        /// Calculates the graph layout
        /// </summary>
        /// <exception cref="System.OperationCanceledException">Thrown when the layout is canceled.</exception>
#endif
        public static void CalculateLayout(GeometryGraph geometryGraph, LayoutAlgorithmSettings settings, CancelToken cancelToken) {
            Console.WriteLine("starting CalculateLayout");
            if (settings is RankingLayoutSettings) {
                var rankingLayoutSettings = settings as RankingLayoutSettings;
                var rankingLayout = new RankingLayout(rankingLayoutSettings, geometryGraph);
                rankingLayout.Run(cancelToken);
                RouteAndLabelEdges(geometryGraph, settings, geometryGraph.Edges);
            }
            else if (settings is MdsLayoutSettings) {
                var mdsLayoutSettings = settings as MdsLayoutSettings;
                var mdsLayout = new MdsGraphLayout(mdsLayoutSettings, geometryGraph);
                mdsLayout.Run(cancelToken);
                if (settings.EdgeRoutingSettings.EdgeRoutingMode != EdgeRoutingMode.None)
                    RouteAndLabelEdges(geometryGraph, settings, geometryGraph.Edges);
            }
            else if (settings is FastIncrementalLayoutSettings) {
                var incrementalSettings = settings as FastIncrementalLayoutSettings;
                incrementalSettings.AvoidOverlaps = true;
                var initialLayout = new InitialLayout(geometryGraph, incrementalSettings);
                initialLayout.Run(cancelToken);
                if (settings.EdgeRoutingSettings.EdgeRoutingMode != EdgeRoutingMode.None)
                    RouteAndLabelEdges(geometryGraph, settings, geometryGraph.Edges);
                //incrementalSettings.IncrementalRun(geometryGraph);
            }
            else {
                var sugiyamaLayoutSettings = settings as SugiyamaLayoutSettings;
                if (sugiyamaLayoutSettings != null)
                    ProcessSugiamaLayout(geometryGraph, sugiyamaLayoutSettings, cancelToken);
                else {
                    Debug.Assert(settings is LgLayoutSettings);
                    LayoutLargeGraphWithLayers(geometryGraph, settings, cancelToken);
                }
            }
        }
Example #12
0
 private void ChangeShapes(GeometryGraph g)
 {
     foreach(var n in g.Nodes){
         var box=n.BoundaryCurve.BoundingBox;
         n.BoundaryCurve = new Ellipse(20, 20, n.Center);
     }
 }
 /// <summary>
 /// Constructor
 /// </summary>
 /// <param name="streamPar">the stream to write the graph into</param>
 /// <param name="graphP">the graph</param>
 /// <param name="settings">The settings to be written.</param>
 public GeometryGraphWriter(Stream streamPar, GeometryGraph graphP, LayoutAlgorithmSettings settings) {
     stream = streamPar;
     Graph = graphP;
     Settings = settings;
     var xmlWriterSettings = new XmlWriterSettings {Indent = true};
     XmlWriter = XmlWriter.Create(stream, xmlWriterSettings);
     EdgeEnumeration = graphP.Edges;
 }
 /// <summary>
 /// Gives each label a size.
 /// By default they don't have a size so we must fill it in.
 /// </summary>
 private static void AddLabelSizes(GeometryGraph graph)
 {
     foreach (Label label in graph.CollectAllLabels())
     {
         label.Width = 30;
         label.Height = 15;
     }
 }
Example #15
0
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (gleeGraph == null)
                gleeGraph = CreateAndLayoutGraph();

            DrawFromGraph(e.Graphics);
        }
        internal static void FixBoundingBox(GeometryGraph component, LayoutAlgorithmSettings settings)
        {
            // Pad the graph with margins so the packing will be spaced out.
            component.Margins = settings.ClusterMargin;
            component.UpdateBoundingBox();

            // Zero the graph
            component.Translate(-component.BoundingBox.LeftBottom);
        }
 ///<summary>
 ///</summary>
 ///<param name="geomGraph"></param>
 static public void ShowGraph(GeometryGraph geomGraph) {
     var graph = new Graph();
     geomGraph.UpdateBoundingBox();
     var bb = geomGraph.BoundingBox;
     bb.Pad(geomGraph.Margins);
     geomGraph.BoundingBox = bb;
     BindGeomGraphToDrawingGraph(graph, geomGraph);
     DisplayGraph(graph, new Form());
 }
        ///<summary>
        ///</summary>
        ///<param name="graph"></param>
        ///<param name="geomGraph"></param>
        static public void BindGeomGraphToDrawingGraph(Graph graph, GeometryGraph geomGraph) {
            graph.GeometryGraph = geomGraph;
            var nodeIds = new Dictionary<GeomNode, string>();
            BindNodes(graph, geomGraph, nodeIds);

            BindClusters(graph, geomGraph, nodeIds);

            BindEdges(graph, geomGraph, nodeIds);
        }
 internal DeviceIndependendZoomCalculatorForNodes(
     Func<Node, LgNodeInfo> nodeToLgNodeInfo, GeometryGraph graph, LgLayoutSettings settings, int maxAmountPerTile)
 {
     NodeToLgNodeInfo = nodeToLgNodeInfo;
     this.maxAmountPerTile = maxAmountPerTile;
     Graph = graph;
     Settings = settings;
     unassigned = graph.Nodes.Count;
 }
Example #20
0
 protected override void OnPaint(PaintEventArgs e) {
     e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
     e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
     base.OnPaint(e);
     if (geometryGraph == null) {
         geometryGraph = CreateAndLayoutGraph();
     }
     DrawFromGraph(e.Graphics);
 }
 GeometryGraph FillGraph(GeometryGraph geometryGraph) {
     ProcessNodes(geometryGraph);
     if (drawingGraph.RootSubgraph != null) {
         geometryGraph.RootCluster = ProcessSubGraphs(drawingGraph.RootSubgraph);
         geometryGraph.RootCluster.GeometryParent = geometryGraph;
     }
     ProcessEdges(geometryGraph);
     ProcessGraphAttrs(drawingGraph, geometryGraph, drawingGraph.LayoutAlgorithmSettings);
     return geometryGraph;
 }
 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>
 /// 
 /// </summary>
 /// <param name="pushingNodes">the nodes are already at the correct positions</param>
 /// <param name="graph"></param>
 /// <param name="layoutSettings"></param>
 public IncrementalDragger(IEnumerable<GeomNode> pushingNodes, GeometryGraph graph, LayoutAlgorithmSettings layoutSettings) {
     this.graph = graph;
     this.nodeSeparation = layoutSettings.NodeSeparation;
     this.layoutSettings = layoutSettings;
     pushingNodesArray = pushingNodes as GeomNode[] ?? pushingNodes.ToArray();
     Debug.Assert(pushingNodesArray.All(n => DefaultClusterParent(n) == null) ||
                   (new Set<GeomNode>(pushingNodesArray.Select(n => n.ClusterParents.First()))).Count == 1,
                             "dragged nodes have to belong to the same cluster");
     InitBumperPushers();
 }
Example #24
0
        /// <summary>
        /// Additionally needed area of nodes compared to the optimal packing of the nodes.
        /// </summary>
        /// <param name="graphOriginal"></param>
        /// <param name="graphNew"></param>
        /// <returns></returns>
        public static Tuple<String, double> Area(GeometryGraph graph)
        {
            double minimalArea = graph.Nodes.Sum(v => v.BoundingBox.Area);

            Rectangle boundingBoxNew=new Rectangle(graph.Nodes.Select(v=>v.BoundingBox));
            double areaNew = boundingBoxNew.Area;

            double ratio = areaNew/minimalArea;
            //            return Tuple.Create("AreaIncreaseToMinPacking",ratio - 1);//we are interested in increase compared to optimal packing.
            return Tuple.Create("AreaAbsolute/(1E6)", areaNew / (1E6));//we are interested in increase compared to optimal packing.
        }
 /// <summary>
 /// Creates a smoothed polyline
 /// </summary>
 internal SmoothedPolylineCalculator(IntEdge edgePathPar, Anchor[] anchorsP, GeometryGraph origGraph, SugiyamaLayoutSettings settings, LayerArrays la, ProperLayeredGraph layerGraph, Database databaseP) {
     this.database = databaseP;
     edgePath = edgePathPar;
     anchors = anchorsP;
     this.layerArrays = la;
     this.originalGraph = origGraph;
     this.settings = settings;
     this.layeredGraph = layerGraph;
     rightHierarchy = BuildRightHierarchy();
     leftHierarchy = BuildLeftHierarchy();
 }
 internal Routing(SugiyamaLayoutSettings settings, GeometryGraph originalGraph, Database dbP,
                  LayerArrays yLayerArrays,
                  ProperLayeredGraph properLayeredGraph,
                  BasicGraph<Node, IntEdge> intGraph
     ) {
     this.settings = settings;
     OriginalGraph = originalGraph;
     Database = dbP;
     ProperLayeredGraph = properLayeredGraph;
     LayerArrays = yLayerArrays;
     IntGraph = intGraph;
 }
        /// <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>
 /// Fastest method we have for laying out large graphs.  Also does a pretty good job
 /// of unfolding graphs.
 /// The idea is that a stack of successively more and more simplified (abridged) graphs
 /// is constructed, then each graph on the stack is laid out, starting nodes at the positions
 /// of their ancestors in the more abridged graph.
 /// </summary>
 public void CalculateLayout(GeometryGraph graph, double edgeLengthOffset, double edgeLengthMultiplier)
 {
     if (graph.Nodes.Count <= 1)
     {
         return;
     }
     GeometryGraph G = graph;
     // build stack of successively more abridged graphs
     var GraphStack = new Stack<GeometryGraph>();
     while (G.Nodes.Count > 3)
     {
         GraphStack.Push(G);
         int n = G.Nodes.Count;
         G = CreateAbridgedGraph(G, edgeLengthOffset, edgeLengthMultiplier);
         if (G.Nodes.Count == n)
         {
             // if the abridged graph is no smaller then pop back the previous one
             // and break the loop.  If graph contains more than one component then
             // Nodes.Count may not get below 3.
             G = GraphStack.Pop();
             break;
         }
     }
     // layout most abridged graph
     SimpleLayout(G, 1.0, edgeLengthMultiplier);
     // work back up the stack, expanding each successive graph such that nodes
     // with a single ancestor in the abridged graph are initially placed at the same position
     // as obtained with the previous layout, and nodes which were previously paired
     // are placed at the centroid of their neighbours.  Then apply layout again.
     double totalGraphCount = GraphStack.Count + 1;
     while (GraphStack.Count > 0)
     {
         var toCenter = new List<Node>();
         foreach (var u in G.Nodes)
         {
             var v = u.UserData as Node;
             if (v != null)
             {
                 v.Center = u.Center;
             }
             else
             {
                 var e = u.UserData as Edge;
                 e.Source.Center = e.Target.Center = u.Center;
                 toCenter.Add(e.Source);
                 toCenter.Add(e.Target);
             }
         }
         toCenter.ForEach(CenterNode);
         G = GraphStack.Pop();
         SimpleLayout(G, GraphStack.Count / totalGraphCount, edgeLengthMultiplier);
     }
 }
        /// <summary>
        /// Recursively lay out the given clusters using the specified settings for each cluster, or if none is given for a particular
        /// cluster then inherit from the cluster's ancestor - or from the specifed defaultSettings.
        /// Clusters (other than the root) will be translated (together with their descendants) such that their 
        /// bottom-left point of their new boundaries are the same as the bottom-left of their old boundaries 
        /// (i.e. clusters are laid-out in place).
        /// </summary>
        /// <param name="graph">The graph being operated on.</param>
        /// <param name="clusters">The clusters to layout.</param>
        /// <param name="clusterSettings">Settings to use for each cluster and its descendents (if none provided for that descendent.</param>
        public InitialLayoutByCluster(GeometryGraph graph, IEnumerable<Cluster> clusters,
            Func<Cluster, LayoutAlgorithmSettings> clusterSettings) {
            ValidateArg.IsNotNull(graph, "graph");
            ValidateArg.IsNotNull(clusters, "clusters");
            ValidateArg.IsNotNull(clusterSettings, "clusterSettings");
#if TEST_MSAGL
            graph.SetDebugIds();
#endif

            this.graph = graph;
            this.clusters = clusters.ToList();
            this.clusterSettings = clusterSettings;
        }
        GeometryGraph CreateGeometryGraph(PreGraph preGraph) {
            var graph = new GeometryGraph();
            var nodeDictionary = new Dictionary<ICurve, Node>();
            foreach (var curve in preGraph.nodeBoundaries) {
                var node = new Node(curve);
                nodeDictionary[curve] = node;
                graph.Nodes.Add(node);
            }
            foreach (var eg in preGraph.edgeGeometries)
                AddEdgeGeometryToGraph(eg, graph, nodeDictionary);

            return graph;
        }