// Abstract the creation of the GeometryGraph and the node.CreateBoundary calls away in // a single call on the Diagram. public void Run() { drawingGraph.CreateGeometryGraph(); foreach (var node in drawingGraph.Nodes) { if (node is LabeledNode ln) { ln.CreateBoundary(); } } var routingSettings = new Microsoft.Msagl.Core.Routing.EdgeRoutingSettings { UseObstacleRectangles = true, BendPenalty = 100, EdgeRoutingMode = Microsoft.Msagl.Core.Routing.EdgeRoutingMode.StraightLine }; var settings = new SugiyamaLayoutSettings { ClusterMargin = 50, PackingAspectRatio = 3, PackingMethod = Microsoft.Msagl.Core.Layout.PackingMethod.Columns, RepetitionCoefficientForOrdering = 0, EdgeRoutingSettings = routingSettings, NodeSeparation = 50, LayerSeparation = 150 }; LayoutHelpers.CalculateLayout(drawingGraph.GeometryGraph, settings, null); _run(); }
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(); }
void LayoutOneComponent(GeometryGraph component) { PrepareGraphForLayout(component); if (component.RootCluster.Clusters.Any()) { var layoutSettings = new SugiyamaLayoutSettings { FallbackLayoutSettings = new FastIncrementalLayoutSettings { AvoidOverlaps = true }, NodeSeparation = lgLayoutSettings.NodeSeparation, LayerSeparation = lgLayoutSettings.NodeSeparation, EdgeRoutingSettings = lgLayoutSettings.EdgeRoutingSettings, LayeringOnly = true }; var initialBc = new InitialLayoutByCluster(component, a => layoutSettings); initialBc.Run(); } else { LayoutHelpers.CalculateLayout(component, GetMdsLayoutSettings(), cancelToken); } var box = component.BoundingBox; box.Pad(lgLayoutSettings.NodeSeparation / 2); component.BoundingBox = box; }
static void Main(string[] args) { var drawingGraph = new Graph(); drawingGraph.AddNode("A").LabelText = "AText"; drawingGraph.AddNode("B").LabelText = "BText"; var e = drawingGraph.AddEdge("A", "B"); // now the drawing graph has nodes A,B and and an edge A -> B\ // the geometry graph is still null, so we are going to create it e.LabelText = "from " + e.SourceNode.LabelText + " to " + e.TargetNode.LabelText; drawingGraph.CreateGeometryGraph(); // Now the drawing graph elements point to the corresponding geometry elements, // however the node boundary curves are not set. // Setting the node boundaries foreach (var n in drawingGraph.Nodes) { // Ideally we should look at the drawing node attributes, and figure out, the required node size // I am not sure how to find out the size of a string rendered in SVG. Here, we just blindly assign to each node a rectangle with width 60 and height 40, and round its corners. n.GeometryNode.BoundaryCurve = CurveFactory.CreateRectangleWithRoundedCorners(60, 40, 3, 2, new Point(0, 0)); } AssignLabelsDimensions(drawingGraph); LayoutHelpers.CalculateLayout(drawingGraph.GeometryGraph, new SugiyamaLayoutSettings(), null); PrintSvgAsString(drawingGraph); }
/// <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); }
private static void LayoutGraph(GeometryGraph geometryGraph) { var settings = new Microsoft.Msagl.Layout.MDS.MdsLayoutSettings(); settings.EdgeRoutingSettings.EdgeRoutingMode = Microsoft.Msagl.Core.Routing.EdgeRoutingMode.StraightLine; LayoutHelpers.CalculateLayout(geometryGraph, settings, null); Console.WriteLine("layout done"); }
private void LayoutLinkedShapes(Graph graph, out Microsoft.Msagl.Core.Layout.GeometryGraph geometry) { var geomGraph = new Microsoft.Msagl.Core.Layout.GeometryGraph(); var layoutSettings = new Microsoft.Msagl.Layout.Layered.SugiyamaLayoutSettings(); Microsoft.Msagl.Core.Layout.Node EnsureNode(Node node) { var res = geomGraph.Nodes.FirstOrDefault(n => n.UserData == node); if (res == null) { var geomNode = new Microsoft.Msagl.Core.Layout.Node( Microsoft.Msagl.Core.Geometry.Curves.CurveFactory.CreateRectangle( ShapeSize, ShapeSize, new Microsoft.Msagl.Core.Geometry.Point() ), node ); geomGraph.Nodes.Add(geomNode); return(geomNode); } return(res); } foreach (var l in graph.Links) { var n1 = EnsureNode(l.Node1); var n2 = EnsureNode(l.Node2); var e = new Microsoft.Msagl.Core.Layout.Edge(n1, n2); geomGraph.Edges.Add(e); } LayoutHelpers.CalculateLayout(geomGraph, layoutSettings, null); foreach (var kv in nodesAndShapes) { var pos = geomGraph.Nodes.FirstOrDefault(n => n.UserData == kv.Key)?.Center; if (pos.HasValue) { kv.Value.X = (int)pos.Value.X + ShapeSize; kv.Value.Y = Diagram.Height - (int)pos.Value.Y + ShapeSize / 2; } } geometry = geomGraph; }
private void Update() { if (reposition) { PositionNodes(); Center(); reposition = false; } if (redraw) { RedrawEdges(); redraw = false; } if (relayout) { if (graphTask == null) { UpdateNodes(); graphTask = Task.Run(() => { LayoutHelpers.CalculateLayout(graph, settings, null); LayoutHelpers.RouteAndLabelEdges(graph, settings, graph.Edges); }); Forget(graphTask, () => { graphTask = null; reposition = true; redraw = true; }); relayout = false; } } if (reroute) { if (graphTask == null) { UpdateNodes(); graphTask = Task.Run(() => LayoutHelpers.RouteAndLabelEdges(graph, settings, graph.Edges)); Forget(graphTask, () => { graphTask = null; redraw = true; }); reroute = false; } } }
private static void DoCustomLayout(List <NodeShape> nodeShapes, List <BinaryLinkShape> linkShapes, ModelRoot modelRoot) { GeometryGraph graph = new GeometryGraph(); CreateDiagramNodes(nodeShapes, graph); CreateDiagramLinks(linkShapes, graph); AddDesignConstraints(linkShapes, modelRoot, graph); LayoutHelpers.CalculateLayout(graph, modelRoot.LayoutAlgorithmSettings, null); // Move model to positive axis. graph.UpdateBoundingBox(); graph.Translate(new Point(-graph.Left, -graph.Bottom)); UpdateNodePositions(graph); UpdateConnectors(graph); }
private void LayoutGraph(GeometryGraph geometryGraph) { if (this.NeedToCalculateLayout) { try { LayoutHelpers.CalculateLayout(geometryGraph, this.Graph.LayoutAlgorithmSettings, this.CancelToken); //if (MsaglFileToSave != null) //{ // drawingGraph.Write(MsaglFileToSave); // Console.WriteLine("saved into {0}", MsaglFileToSave); // Environment.Exit(0); //} } catch (OperationCanceledException) { //swallow this exception } } }
int ProcessMsaglFile(string inputFile) { try { LayoutAlgorithmSettings ls; var geomGraph = GeometryGraphReader.CreateFromFile(inputFile, out ls); if (ls == null) { ls = PickLayoutAlgorithmSettings(geomGraph.Edges.Count, geomGraph.Nodes.Count); } LayoutHelpers.CalculateLayout(geomGraph, ls, null); inputFile = SetMsaglOutputFileName(inputFile); WriteGeomGraph(inputFile, geomGraph); DumpFileToConsole(inputFile); } catch (Exception e) { //Console.WriteLine(e.ToString()); Console.WriteLine(e.Message); return(-1); } return(0); }
void ProcessGraph() { try { selectedObject = null; if (drawingGraph == null) { return; } graphCanvas.Visibility = Visibility.Hidden; // hide canvas while we lay it out asynchronously. ClearGraphViewer(); // Make nodes and edges if (LargeGraphBrowsing) { LayoutEditingEnabled = false; var lgsettings = new LgLayoutSettings( () => new Rectangle(0, 0, graphCanvas.RenderSize.Width, graphCanvas.RenderSize.Height), () => Transform, DpiX, DpiY, () => 0.1 * DpiX / CurrentScale /*0.1 inch*/); drawingGraph.LayoutAlgorithmSettings = lgsettings; lgsettings.ViewerChangeTransformAndInvalidateGraph += OGraphChanged; } if (NeedToCalculateLayout) { drawingGraph.CreateGeometryGraph(); //forcing the layout recalculation geometryGraphUnderLayout = drawingGraph.GeometryGraph; } PushGeometryIntoLayoutGraph(); graphCanvas.RaiseEvent(new RoutedEventArgs(LayoutStartEvent)); LayoutHelpers.CalculateLayout(geometryGraphUnderLayout, drawingGraph.LayoutAlgorithmSettings); TransferLayoutDataToWpf(); } catch (Exception e) { MessageBox.Show(e.ToString()); } }
static internal 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); }
//takes a target Tree object and builds actual game nodes for it - using MSAGL as an intermediate representation for layout public void BuildTree(Tree target) { if (target == null) { return; //do not attempt to build a null tree - this means that a syntax error happened in a newick file } GameManager.DebugLog("Building tree with layout: " + eventManager.Current_Tree_State); ResetTreeContainer(); GeometryGraph asMSAGL = ToMSALGraph(target); List <GameObject> generatedNodes = new List <GameObject>(); //define how we want the tree to be layed out LayoutAlgorithmSettings settings; switch (eventManager.Current_Tree_State) { default: case InputEventManager.TreeState.BasicTree: settings = new Microsoft.Msagl.Layout.Layered.SugiyamaLayoutSettings(); break; case InputEventManager.TreeState.TerrainTree: case InputEventManager.TreeState.CircularTree: settings = new FastIncrementalLayoutSettings(); break; } //apply some extra settings and layout the graph according to all settings settings.EdgeRoutingSettings.EdgeRoutingMode = Microsoft.Msagl.Core.Routing.EdgeRoutingMode.StraightLine; settings.PackingMethod = PackingMethod.Compact; LayoutHelpers.CalculateLayout(asMSAGL, settings, null); //we want world space 0, 0 to be equivalent to graph space 0, 0 float offSetX = (float)asMSAGL.Nodes[0].Center.X; float offSetY = (float)asMSAGL.Nodes[0].Center.Y; //msal graph edge weights are enormous, like hundreds of meters of in world space - scale it down float scaleFactor = eventManager.Current_Tree_State == InputEventManager.TreeState.TerrainTree ? 1f / 30f : 1f / 250f; //build game objects using msagl graph as spatial layout foreach (Microsoft.Msagl.Core.Layout.Node node in asMSAGL.Nodes) { float x = ((float)node.Center.X - offSetX) * scaleFactor; float y = ((float)node.Center.Y - offSetY) * scaleFactor; GameObject newNode = Instantiate(nodePrefab, new Vector3( treeContainer.transform.position.x + x, treeContainer.transform.position.y + y, treeContainer.transform.position.z ), Quaternion.identity); newNode.transform.SetParent(treeContainer.transform); newNode.GetComponent <BasicNodeBehaviour>().attachNodeInfo((Node)node.UserData); newNode.GetComponentInChildren <InfoOnHover>()._Init(); node.UserData = newNode; generatedNodes.Add(newNode); foreach (Edge edge in node.Edges) { if (edge.Target == node) { Line l = newNode.transform.Find("Renderer").gameObject.AddComponent <Line>(); l.gameObject1 = newNode; l.gameObject2 = (GameObject)edge.Source.UserData; newNode.transform.SetParent(((GameObject)edge.Source.UserData).transform); if (newNode.GetComponent <BasicNodeBehaviour>().getAttachedNodeInfo().weightToParentSet()) { l.setLabel("" + newNode.GetComponent <BasicNodeBehaviour>().getAttachedNodeInfo().getWeightToParent()); } else { l.setLabel(""); } } } } if (eventManager.Current_Tree_State == InputEventManager.TreeState.TerrainTree) { Vector3 translation = new Vector3(0, -4, 0); treeContainer.transform.Translate(translation); treeContainer.transform.Rotate(90, 0, 0); List <GameObject> leafNodesList = new List <GameObject>(); foreach (GameObject node in generatedNodes) { //scale up the nodes a bit to make them clearer within the terrain float scale = node.GetComponentInChildren <MaintainNodeScale>().targetScale; node.GetComponentInChildren <MaintainNodeScale>().targetScale = 2 * scale; node.transform.hasChanged = true; if (node.GetComponent <BasicNodeBehaviour>().getAttachedNodeInfo().getNumberOfChildren() == 0) { leafNodesList.Add(node); } } generatedNodes[0].GetComponentInChildren <MaintainNodeScale>().targetScale = 4f; GameManager.DebugLog("Found " + leafNodesList.Count + " leaf nodes"); TreeTerrainBehaviour.instance.Activate(); TreeTerrainBehaviour.instance.ResetHeights(); leafNodes = leafNodesList.ToArray(); EnableOnFrameLoading(); } else { DisableOnFrameLoading(); //the player won't be moving while we aren't looking at terrain so we'll reset their position } if (eventManager.Draw_Tree_In_3D) { TreeConverter3D.Convert(treeContainer.transform.GetChild(0).gameObject); } //as a last step, reset the position of the player so they are close to the root of whatever tree they built player.GetComponent <PositionResetter>().ResetPosition(); }
public static void LayeredLayoutAbc() { double w = 30; double h = 20; GeometryGraph graph = new GeometryGraph(); MSAGLNode a = new MSAGLNode(new Ellipse(w, h, new Point()), "a"); MSAGLNode b = new MSAGLNode(CurveFactory.CreateRectangle(w, h, new Point()), "b"); MSAGLNode c = new MSAGLNode(CurveFactory.CreateRectangle(w, h, new Point()), "c"); graph.Nodes.Add(a); graph.Nodes.Add(b); graph.Nodes.Add(c); Edge ab = new Edge(a, b) { Length = 10 }; Edge bc = new Edge(b, c) { Length = 3 }; graph.Edges.Add(ab); graph.Edges.Add(bc); var settings = new SugiyamaLayoutSettings(); LayoutHelpers.CalculateLayout(graph, settings, null); WriteMessage("Layout progressed"); /*WriteMessage(""); * WriteMessage("Segments A->B"); * //OutputCurve(ab.Curve); * * WriteMessage(""); * WriteMessage("Segments B->C"); * //OutputCurve(bc.Curve); * * WriteMessage(""); * WriteMessage("Segments C->A"); * //OutputCurve(ca.Curve); * * foreach (var node in graph.Nodes) * { * WriteMessage(string.Format("{0}: {1} {2}", node.UserData, node.Center.X, node.Center.Y)); * }*/ /*var canvas = HtmlContext.document.getElementById("drawing").As<HtmlCanvasElement>(); * var ctx = canvas.getContext("2d").As<CanvasRenderingContext2D>(); * * var canvasHeight = canvas.height; * * var bounds = calcBounds(graph.Nodes); * * var xScale = canvas.width / bounds.Width; * var yScale = canvas.height / bounds.Height; * * var xShift = -bounds.Left * xScale; * var yShift = -(canvas.height - bounds.Top) * yScale; * * WriteMessage(string.Format("Scaling : {0} {1}", xScale, yScale)); * WriteMessage(string.Format("Shifting : {0} {1}", xShift, yShift)); * * foreach (var msaglEdge in graph.Edges) * { * DrawEdge(ctx, msaglEdge, xShift, yShift, xScale, yScale, canvasHeight); * } * * foreach (var msaglNode in graph.Nodes) * { * DrawNode(ctx, msaglNode, xShift, yShift, xScale, yScale, canvasHeight); * }*/ }
public static void MDS() { WriteMessage("Starting test..."); WriteMessage("Create GeometryGraph"); GeometryGraph graph = AbcdeGraph(); //graph.Save("c:\\tmp\\saved.msagl"); var settings = new MdsLayoutSettings(); settings.RunInParallel = false; settings.EdgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.Rectilinear; LayoutHelpers.CalculateLayout(graph, settings, null); WriteMessage("Layout progressed"); WriteMessage(""); WriteMessage("Segments A->B"); //OutputCurve(ab.Curve); WriteMessage(""); WriteMessage("Segments B->C"); //OutputCurve(bc.Curve); WriteMessage(""); WriteMessage("Segments C->A"); //OutputCurve(ca.Curve); foreach (var node in graph.Nodes) { WriteMessage(string.Format("{0}: {1} {2}", node.UserData, node.Center.X, node.Center.Y)); } var canvas = HtmlContext.document.getElementById("drawing").As <HtmlCanvasElement>(); var ctx = canvas.getContext("2d").As <CanvasRenderingContext2D>(); var canvasHeight = canvas.height; var bounds = calcBounds(graph.Nodes); var xScale = canvas.width / bounds.Width; var yScale = canvas.height / bounds.Height; var xShift = -bounds.Left * xScale; var yShift = -(canvas.height - bounds.Top) * yScale; WriteMessage(string.Format("Scaling : {0} {1}", xScale, yScale)); WriteMessage(string.Format("Shifting : {0} {1}", xShift, yShift)); foreach (var msaglEdge in graph.Edges) { DrawEdge(ctx, msaglEdge, xShift, yShift, xScale, yScale, canvasHeight); } foreach (var msaglNode in graph.Nodes) { DrawNode(ctx, msaglNode, xShift, yShift, xScale, yScale, canvasHeight); } * / }
public Form1() { InitializeComponent(); GViewer gViewer = new GViewer() { Dock = DockStyle.Fill }; SuspendLayout(); Controls.Add(gViewer); ResumeLayout(); var graph = new Graph(); var sugiyamaSettings = (SugiyamaLayoutSettings)graph.LayoutAlgorithmSettings; var json = System.IO.File.ReadAllText("fluentGraph.json"); var typeName = "com.microsoft.azure.management.redis.RedisCache"; var nodes = JsonConvert.DeserializeObject <List <FluentNode> >(json); foreach (var node in nodes) { if (node is FluentInterface || node is FluentMethod) { AddNode(graph, node); } else { throw new NotImplementedException($"'{node.GetType()}' is not supported"); } } var path = ShortestPathByDijkstra.GetPath(nodes, $"{typeName}.Definition.Blank", typeName); // now add edges for (int i = 0; i < nodes.Count; i++) { var node = nodes[i]; foreach (var childId in node.ChildrenIds) { graph.AddEdge(node.Id, childId); } } graph.CreateGeometryGraph(); foreach (var n in graph.Nodes) { CreateBoundaryCurve(n); } // This one throws index out of bounds exception sugiyamaSettings.AddUpDownVerticalConstraints(path.Select(nodeId => graph.FindNode(nodeId).GeometryNode).ToArray()); for (int i = 0; i < path.Count - 1; i++) { var u = graph.FindNode(path[i]); var v = graph.FindNode(path[i + 1]); foreach (var e in u.OutEdges) { if (e.TargetNode == v) { e.Attr.Color = Microsoft.Msagl.Drawing.Color.Red; } } } LayoutHelpers.CalculateLayout(graph.GeometryGraph, sugiyamaSettings, new Microsoft.Msagl.Core.CancelToken()); gViewer.NeedToCalculateLayout = false; gViewer.Graph = graph; }
public void CalculateLayout(GeometryGraph geometryGraph) { LayoutHelpers.CalculateLayout(geometryGraph, LayoutAlgorithmSettings, null); }
private void Layout() { try { var scenes = Controller.Instance.ChapterList.getSelectedChapterDataControl().getScenesList(); // Layout algorithm var settings = new Microsoft.Msagl.Layout.Layered.SugiyamaLayoutSettings(); settings.MaxAspectRatioEccentricity = 1.6; settings.NodeSeparation = 10; settings.PackingMethod = PackingMethod.Compact; // Graph GeometryGraph graph = new GeometryGraph(); graph.BoundingBox = new Microsoft.Msagl.Core.Geometry.Rectangle(0, 0, SpaceWidth, SpaceHeight); graph.UpdateBoundingBox(); Dictionary <SceneDataControl, Node> sceneToNode = new Dictionary <SceneDataControl, Node>(); Dictionary <UniRx.Tuple <Node, Node>, bool> present = new Dictionary <UniRx.Tuple <Node, Node>, bool>(); foreach (var scene in scenes.getScenes()) { sizes.Remove(scene.getPreviewBackground()); var rect = GetSceneRect(scene); var node = new Node(CurveFactory.CreateRectangle(rect.width, rect.height, new Point()), scene.getId()); graph.Nodes.Add(node); sceneToNode.Add(scene, node); } foreach (var scene in scenes.getScenes()) { var node = sceneToNode[scene]; foreach (var exit in scene.getExitsList().getExits()) { var index = scenes.getSceneIndexByID(exit.getNextSceneId()); // If the exit points to a cutscene it normally is out of the array if (index < 0 || index >= scenes.getScenes().Count) { continue; } var nextScene = scenes.getScenes()[index]; var t = new UniRx.Tuple <Node, Node>(node, sceneToNode[nextScene]); if (!present.ContainsKey(t)) { present.Add(t, true); graph.Edges.Add(new Edge(node, sceneToNode[nextScene])); var exitOrigin = GetExitArea(scene, exit).ToRect().center; var originRect = GetSceneRect(scene); var pos = exitOrigin - originRect.position; pos.x = Mathf.Clamp01(pos.x / originRect.width); pos.y = Mathf.Clamp01(pos.y / originRect.height); // Positioning constraints if (pos.x < 0.3) { settings.AddLeftRightConstraint(t.Item2, t.Item1); } if (pos.x > 0.7) { settings.AddLeftRightConstraint(t.Item1, t.Item2); } } } } // Do the layouting LayoutHelpers.CalculateLayout(graph, settings, null); // Extract the results var graphRect = new Rect((float)graph.Left, (float)graph.Bottom, (float)graph.Width, (float)graph.Height); var canvasRect = new Rect(0, 0, SpaceWidth, SpaceHeight); foreach (var scene in scenes.getScenes()) { var n = sceneToNode[scene]; positions[scene.getId()] = TransformPoint(new Vector2((float)(n.Center.X - n.Width / 2f), (float)(n.Center.Y + n.Height / 2f)), graphRect, canvasRect, true); // Update in the project data var id = GetScenePropertyId(Controller.Instance.SelectedChapterDataControl, scene); ProjectConfigData.setProperty(id + ".X", ((int)positions[scene.getId()].x).ToString()); ProjectConfigData.setProperty(id + ".Y", ((int)positions[scene.getId()].y).ToString()); } } catch (Exception ex) { Debug.LogError(ex.Message + " : " + ex.StackTrace); } }