コード例 #1
0
        /// <summary>
        /// This method repeatedly takes a selected node in the combined graph and
        /// uses breadth-first search to find all other nodes in the same subgraph
        /// until all selected nodes have been processed.
        /// </summary>
        /// <param name="nodes">A cluster of nodes to be separated into subgraphs.</param>
        /// <param name="layoutSubgraphs">A collection of layout subgraphs.</param>
        private static void GenerateSeparateSubgraphs(HashSet <GraphLayout.Node> nodes, List <GraphLayout.Graph> layoutSubgraphs)
        {
            int processed     = 0;
            var combinedGraph = layoutSubgraphs.First();

            GraphLayout.Graph        graph = new GraphLayout.Graph();
            Queue <GraphLayout.Node> queue = new Queue <GraphLayout.Node>();

            while (nodes.Count(n => n.IsSelected) > 0)
            {
                GraphLayout.Node currentNode;

                if (queue.Count == 0)
                {
                    if (graph.Nodes.Count > 0)
                    {
                        // Save the subgraph and subtract these nodes from the combined graph

                        layoutSubgraphs.Add(graph);
                        nodes.ExceptWith(graph.Nodes);
                        combinedGraph.Nodes.ExceptWith(graph.Nodes);
                        graph = new GraphLayout.Graph();
                    }
                    if (nodes.Count(n => n.IsSelected) == 0)
                    {
                        break;
                    }

                    currentNode = nodes.FirstOrDefault(n => n.IsSelected);
                    graph.Nodes.Add(currentNode);
                }
                else
                {
                    currentNode = queue.Dequeue();
                }

                // Find all nodes in the selection which are connected directly
                // to the left or to the right to the currentNode

                var selectedNodes = currentNode.RightEdges.Select(e => e.EndNode)
                                    .Union(currentNode.LeftEdges.Select(e => e.StartNode))
                                    .Where(x => nodes.Contains(x) && x.IsSelected)
                                    .Except(graph.Nodes).ToList();
                graph.Nodes.UnionWith(selectedNodes);
                graph.Edges.UnionWith(currentNode.RightEdges);
                graph.Edges.UnionWith(currentNode.LeftEdges);

                // If any of the incident edges are connected to unselected (outside) nodes
                // then mark these edges as anchors.

                graph.AnchorRightEdges.UnionWith(currentNode.RightEdges.Where(e => !e.EndNode.IsSelected));
                graph.AnchorLeftEdges.UnionWith(currentNode.LeftEdges.Where(e => !e.StartNode.IsSelected));

                foreach (var node in selectedNodes)
                {
                    queue.Enqueue(node);
                    processed++;
                }
            }
        }
コード例 #2
0
        /// <summary>
        /// This method pushes changes from the GraphLayout.Graph objects
        /// back to the workspace models, but only for nodes placed by NodeAutocomplete.
        /// </summary>
        private static void SaveLayoutGraphForNodeAutoComplete(this WorkspaceModel workspace, List <GraphLayout.Graph> layoutSubgraphs, Guid?originalNodeGUID)
        {
            // Assign coordinates to nodes outside groups
            foreach (var node in workspace.Nodes)
            {
                GraphLayout.Graph graph = layoutSubgraphs
                                          .FirstOrDefault(g => g.FindNode(node.GUID) != null);

                if (graph != null)
                {
                    GraphLayout.Node n       = graph.FindNode(node.GUID);
                    double           offsetY = graph.OffsetY;
                    //skipping the original node to avoid jumping of node
                    if (node.GUID != originalNodeGUID)
                    {
                        node.X = n.X;
                        node.Y = n.Y + n.NotesHeight + offsetY;
                    }
                    node.ReportPosition();
                    workspace.HasUnsavedChanges = true;

                    double noteOffset = -n.NotesHeight;
                    foreach (NoteModel note in n.LinkedNotes)
                    {
                        if (note.IsSelected || DynamoSelection.Instance.Selection.Count == 0)
                        {
                            note.X      = node.X;
                            note.Y      = node.Y + noteOffset;
                            noteOffset += note.Height + GraphLayout.Graph.VerticalNoteDistance;
                            note.ReportPosition();
                        }
                    }
                }
            }
        }
コード例 #3
0
        private void AssertGraphLayoutLayers(object[] subgraphLayerCount)
        {
            for (int i = 0; i < subgraphLayerCount.Length; i++)
            {
                GraphLayout.Graph g = ViewModel.CurrentSpace.LayoutSubgraphs.ElementAt(i + 1);

                if (!g.Layers.Select(layer => layer.Count).AsEnumerable()
                    .SequenceEqual(subgraphLayerCount[i] as IEnumerable <int>))
                {
                    Assert.Fail(String.Format("Layout subgraph [{0}] should be {{ {1} }} but is actually {{ {2} }}",
                                              i, String.Join(", ", subgraphLayerCount[i] as IEnumerable <int>),
                                              String.Join(", ", g.Layers.Select(layer => layer.Count))));
                }
            }
        }
コード例 #4
0
        /// <summary>
        /// If a connector has connectorPins, their edges get added to the combined graph.
        /// </summary>
        /// <param name="combinedGraph"></param>
        /// <param name="connector"></param>
        private static void AddConnectorEdgesIncludingPinEdges(GraphLayout.Graph combinedGraph, ConnectorModel connector, Guid?start = null, Guid?end = null)
        {
            ///Bail if there are no connectorPins
            if (connector.ConnectorPinModels.Count < 1)
            {
                Guid startGuid = start == null ? connector.Start.Owner.GUID : (Guid)start;
                Guid endGuid   = end == null ? connector.End.Owner.GUID : (Guid)end;

                combinedGraph.AddEdge(startGuid, endGuid,
                                      connector.Start.Center.X, connector.Start.Center.Y, connector.End.Center.X, connector.End.Center.Y);
                return;
            }

            ///Add an edge between the left-most (start) node
            ///(its corresponding port) to which this connector connects, and the first connectorPin.
            combinedGraph.AddEdge(connector.Start.Owner.GUID,
                                  connector.ConnectorPinModels[0].GUID,
                                  connector.Start.Center.X,
                                  connector.Start.Center.Y,
                                  connector.ConnectorPinModels[0].CenterX,
                                  connector.ConnectorPinModels[0].CenterY);

            ///Add an edge between all other connectorPins that follow,
            ///from left to right (except for last one)
            for (int i = 0; i < connector.ConnectorPinModels.Count; i++)
            {
                if (i != connector.ConnectorPinModels.Count - 1)
                {
                    combinedGraph.AddEdge(connector.ConnectorPinModels[i].GUID,
                                          connector.ConnectorPinModels[i + 1].GUID,
                                          connector.ConnectorPinModels[i].CenterX,
                                          connector.ConnectorPinModels[i].CenterY,
                                          connector.ConnectorPinModels[i + 1].CenterX,
                                          connector.ConnectorPinModels[i + 1].CenterY);
                }
            }

            ///Add an edge between the last connectorPin and the right-most (end) node
            ///(its corresponding port) to which this connector connects.
            combinedGraph.AddEdge(connector.ConnectorPinModels[connector.ConnectorPinModels.Count - 1].GUID,
                                  connector.End.Owner.GUID,
                                  connector.ConnectorPinModels[connector.ConnectorPinModels.Count - 1].CenterX,
                                  connector.ConnectorPinModels[connector.ConnectorPinModels.Count - 1].CenterY,
                                  connector.End.Center.X,
                                  connector.End.Center.Y);
        }
コード例 #5
0
        private void DoGraphAutoLayout(object o)
        {
            if (Model.Nodes.Count == 0)
            {
                return;
            }

            var graph  = new GraphLayout.Graph();
            var models = new Dictionary <ModelBase, UndoRedoRecorder.UserAction>();

            foreach (NodeModel x in Model.Nodes)
            {
                graph.AddNode(x.GUID, x.Width, x.Height, x.Y);
                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            foreach (ConnectorModel x in Model.Connectors)
            {
                graph.AddEdge(x.Start.Owner.GUID, x.End.Owner.GUID, x.Start.Center.Y, x.End.Center.Y);
                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            Model.RecordModelsForModification(new List <ModelBase>(Model.Nodes));

            // Sugiyama algorithm steps
            graph.RemoveCycles();
            graph.AssignLayers();
            graph.OrderNodes();

            // Assign coordinates to node models
            graph.NormalizeGraphPosition();
            foreach (var x in Model.Nodes)
            {
                var id = x.GUID;
                x.X = graph.FindNode(id).X;
                x.Y = graph.FindNode(id).Y;
                x.ReportPosition();
            }

            // Fit view to the new graph layout
            DynamoSelection.Instance.ClearSelection();
            ResetFitViewToggle(null);
            FitViewInternal();
        }
コード例 #6
0
        /// <summary>
        /// This function calls the graph layout algorithm methods.
        /// </summary>
        /// <param name="graph">The subgraph to be processed.</param>
        /// <param name="isGroupLayout">True if all selected models are groups.</param>
        private static void RunLayoutSubgraph(GraphLayout.Graph graph, bool isGroupLayout)
        {
            // Select relevant nodes
            graph.Nodes.ToList().ForEach(x => x.IsSelected = true);

            // Save subgraph position before running the layout
            graph.RecordInitialPosition();

            // Sugiyama algorithm steps
            graph.RemoveCycles();
            graph.AssignLayers();
            graph.OrderNodes();

            // Node and graph positioning
            graph.DistributeNodePosition();
            graph.SetGraphPosition(isGroupLayout);

            // Reset layer information and deselect nodes
            graph.ResetLayers();
            graph.Nodes.ToList().ForEach(x => x.IsSelected = false);
        }
コード例 #7
0
        private void DoGraphAutoLayout(object o)
        {
            if (Model.Nodes.Count() == 0)
            {
                return;
            }

            var graph  = new GraphLayout.Graph();
            var models = new Dictionary <ModelBase, UndoRedoRecorder.UserAction>();

            foreach (AnnotationModel x in Model.Annotations)
            {
                // Treat a group as a graph layout node/vertex
                graph.AddNode(x.GUID, x.Width, x.Height, x.Y);
                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            foreach (NodeModel x in Model.Nodes)
            {
                AnnotationModel group = Model.Annotations.Where(
                    s => s.SelectedModels.Contains(x)).ToList().FirstOrDefault();

                // Do not process nodes within groups
                if (group == null)
                {
                    graph.AddNode(x.GUID, x.Width, x.Height, x.Y);
                    models.Add(x, UndoRedoRecorder.UserAction.Modification);
                }
            }

            foreach (ConnectorModel x in Model.Connectors)
            {
                AnnotationModel startGroup = null, endGroup = null;
                startGroup = Model.Annotations.Where(
                    s => s.SelectedModels.Contains(x.Start.Owner)).ToList().FirstOrDefault();
                endGroup = Model.Annotations.Where(
                    s => s.SelectedModels.Contains(x.End.Owner)).ToList().FirstOrDefault();

                // Connector does not belong to any group
                if ((startGroup == null) && (endGroup == null))
                {
                    graph.AddEdge(x.Start.Owner.GUID, x.End.Owner.GUID, x.Start.Center.Y, x.End.Center.Y);
                }

                // Connector starts from a node within a group
                else if ((startGroup != null) && (endGroup == null))
                {
                    graph.AddEdge(startGroup.GUID, x.End.Owner.GUID, x.Start.Center.Y, x.End.Center.Y);
                }

                // Connector ends at a node within a group
                else if ((startGroup == null) && (endGroup != null))
                {
                    graph.AddEdge(x.Start.Owner.GUID, endGroup.GUID, x.Start.Center.Y, x.End.Center.Y);
                }

                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            // Support undo for graph layout command
            WorkspaceModel.RecordModelsForModification(new List <ModelBase>(Model.Nodes), Model.UndoRecorder);

            // Sugiyama algorithm steps
            graph.RemoveCycles();
            graph.AssignLayers();
            graph.OrderNodes();

            graph.NormalizeGraphPosition();

            // Assign coordinates to nodes inside groups
            foreach (var x in Model.Annotations)
            {
                var    id     = x.GUID;
                double deltaX = graph.FindNode(id).X - x.X;
                double deltaY = graph.FindNode(id).Y - x.Y;
                foreach (var n in x.SelectedModels)
                {
                    n.X += deltaX;
                    n.Y += deltaY;
                    n.ReportPosition();
                }
            }

            // Assign coordinates to nodes outside groups
            foreach (var x in Model.Nodes)
            {
                var n = graph.FindNode(x.GUID);
                if (n != null)
                {
                    x.X = n.X;
                    x.Y = n.Y;
                    x.ReportPosition();
                }
            }

            // Fit view to the new graph layout
            DynamoSelection.Instance.ClearSelection();
            ResetFitViewToggle(null);
            FitViewInternal();
        }
コード例 #8
0
ファイル: WorkspaceModel.cs プロジェクト: mikeyforrest/Dynamo
        /// <summary>
        /// This method repeatedly takes a selected node in the combined graph and
        /// uses breadth-first search to find all other nodes in the same subgraph
        /// until all selected nodes have been processed.
        /// </summary>
        /// <param name="nodes">A cluster of nodes to be separated into subgraphs.</param>
        private void GenerateSeparateSubgraphs(HashSet<GraphLayout.Node> nodes)
        {
            int processed = 0;
            var combinedGraph = LayoutSubgraphs.First();
            GraphLayout.Graph graph = new GraphLayout.Graph();
            Queue<GraphLayout.Node> queue = new Queue<GraphLayout.Node>();

            while (nodes.Count(n => n.IsSelected) > 0)
            {
                GraphLayout.Node currentNode;

                if (queue.Count == 0)
                {
                    if (graph.Nodes.Count > 0)
                    {
                        // Save the subgraph and subtract these nodes from the combined graph

                        LayoutSubgraphs.Add(graph);
                        nodes.ExceptWith(graph.Nodes);
                        combinedGraph.Nodes.ExceptWith(graph.Nodes);
                        graph = new GraphLayout.Graph();
                    }
                    if (nodes.Count(n => n.IsSelected) == 0) break;

                    currentNode = nodes.FirstOrDefault(n => n.IsSelected);
                    graph.Nodes.Add(currentNode);
                }
                else
                {
                    currentNode = queue.Dequeue();
                }

                // Find all nodes in the selection which are connected directly
                // to the left or to the right to the currentNode

                var selectedNodes = currentNode.RightEdges.Select(e => e.EndNode)
                    .Union(currentNode.LeftEdges.Select(e => e.StartNode))
                    .Where(x => nodes.Contains(x) && x.IsSelected)
                    .Except(graph.Nodes).ToList();
                graph.Nodes.UnionWith(selectedNodes);
                graph.Edges.UnionWith(currentNode.RightEdges);
                graph.Edges.UnionWith(currentNode.LeftEdges);

                // If any of the incident edges are connected to unselected (outside) nodes
                // then mark these edges as anchors.

                graph.AnchorRightEdges.UnionWith(currentNode.RightEdges.Where(e => !e.EndNode.IsSelected));
                graph.AnchorLeftEdges.UnionWith(currentNode.LeftEdges.Where(e => !e.StartNode.IsSelected));

                foreach (var node in selectedNodes)
                {
                    queue.Enqueue(node);
                    processed++;
                }
            }
        }
コード例 #9
0
ファイル: WorkspaceViewModel.cs プロジェクト: RobertiF/Dynamo
        private void DoGraphAutoLayout(object o)
        {
            if (Model.Nodes.Count == 0)
                return;

            var graph = new GraphLayout.Graph();
            var models = new Dictionary<ModelBase, UndoRedoRecorder.UserAction>();
            
            foreach (NodeModel x in Model.Nodes)
            {
                graph.AddNode(x.GUID, x.Width, x.Height, x.Y);
                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            foreach (ConnectorModel x in Model.Connectors)
            {
                graph.AddEdge(x.Start.Owner.GUID, x.End.Owner.GUID, x.Start.Center.Y, x.End.Center.Y);
                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            Model.RecordModelsForModification(new List<ModelBase>(Model.Nodes));
            
            // Sugiyama algorithm steps
            graph.RemoveCycles();
            graph.AssignLayers();
            graph.OrderNodes();
            
            // Assign coordinates to node models
            graph.NormalizeGraphPosition();
            foreach (var x in Model.Nodes)
            {
                var id = x.GUID;
                x.X = graph.FindNode(id).X;
                x.Y = graph.FindNode(id).Y;
                x.ReportPosition();
            }

            // Fit view to the new graph layout
            DynamoSelection.Instance.ClearSelection();
            ResetFitViewToggle(null);
            FitViewInternal();
        }
コード例 #10
0
        private void DoGraphAutoLayout(object o)
        {
            if (Model.Nodes.Count() == 0)
                return;

            var graph = new GraphLayout.Graph();
            var models = new Dictionary<ModelBase, UndoRedoRecorder.UserAction>();

            foreach (AnnotationModel x in Model.Annotations)
            {
                // Treat a group as a graph layout node/vertex
                graph.AddNode(x.GUID, x.Width, x.Height, x.Y);
                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            foreach (NodeModel x in Model.Nodes)
            {
                AnnotationModel group = Model.Annotations.Where(
                    s => s.SelectedModels.Contains(x)).ToList().FirstOrDefault();

                // Do not process nodes within groups
                if (group == null)
                {
                    graph.AddNode(x.GUID, x.Width, x.Height, x.Y);
                    models.Add(x, UndoRedoRecorder.UserAction.Modification);
                }
            }

            foreach (ConnectorModel x in Model.Connectors)
            {
                AnnotationModel startGroup = null, endGroup = null;
                startGroup = Model.Annotations.Where(
                    s => s.SelectedModels.Contains(x.Start.Owner)).ToList().FirstOrDefault();
                endGroup = Model.Annotations.Where(
                    s => s.SelectedModels.Contains(x.End.Owner)).ToList().FirstOrDefault();

                // Connector does not belong to any group
                if ((startGroup == null) && (endGroup == null))
                    graph.AddEdge(x.Start.Owner.GUID, x.End.Owner.GUID, x.Start.Center.Y, x.End.Center.Y);

                // Connector starts from a node within a group
                else if ((startGroup != null) && (endGroup == null))
                    graph.AddEdge(startGroup.GUID, x.End.Owner.GUID, x.Start.Center.Y, x.End.Center.Y);

                // Connector ends at a node within a group
                else if ((startGroup == null) && (endGroup != null))
                    graph.AddEdge(x.Start.Owner.GUID, endGroup.GUID, x.Start.Center.Y, x.End.Center.Y);

                models.Add(x, UndoRedoRecorder.UserAction.Modification);
            }

            // Support undo for graph layout command
            WorkspaceModel.RecordModelsForModification(new List<ModelBase>(Model.Nodes), Model.UndoRecorder);

            // Sugiyama algorithm steps
            graph.RemoveCycles();
            graph.AssignLayers();
            graph.OrderNodes();

            graph.NormalizeGraphPosition();

            // Assign coordinates to nodes inside groups
            foreach (var x in Model.Annotations)
            {
                var id = x.GUID;
                double deltaX = graph.FindNode(id).X - x.X;
                double deltaY = graph.FindNode(id).Y - x.Y;
                foreach (var n in x.SelectedModels)
                {
                    n.X += deltaX;
                    n.Y += deltaY;
                    n.ReportPosition();
                }
            }

            // Assign coordinates to nodes outside groups
            foreach (var x in Model.Nodes)
            {
                var n = graph.FindNode(x.GUID);
                if (n != null)
                {
                    x.X = n.X;
                    x.Y = n.Y;
                    x.ReportPosition();
                }
            }

            // Fit view to the new graph layout
            DynamoSelection.Instance.ClearSelection();
            ResetFitViewToggle(null);
            FitViewInternal();
        }
コード例 #11
0
        /// <summary>
        /// This method pushes changes from the GraphLayout.Graph objects
        /// back to the workspace models.
        /// </summary>
        private static void SaveLayoutGraph(this WorkspaceModel workspace, List <GraphLayout.Graph> layoutSubgraphs)
        {
            // Assign coordinates to nodes inside groups
            foreach (var group in workspace.Annotations)
            {
                GraphLayout.Graph graph = layoutSubgraphs
                                          .FirstOrDefault(g => g.FindNode(group.GUID) != null);

                if (graph != null)
                {
                    GraphLayout.Node n = graph.FindNode(group.GUID);

                    double deltaX = n.X - group.X;
                    double deltaY = n.Y - group.Y + graph.OffsetY;
                    foreach (var node in group.Nodes.OfType <NodeModel>())
                    {
                        node.X += deltaX;
                        node.Y += deltaY;
                        node.ReportPosition();
                    }

                    foreach (NoteModel note in n.LinkedNotes)
                    {
                        if (note.IsSelected || DynamoSelection.Instance.Selection.Count == 0)
                        {
                            note.X += deltaX;
                            note.Y += deltaY;
                            note.ReportPosition();
                        }
                    }
                }
            }

            // Assign coordinates to nodes outside groups
            foreach (var node in workspace.Nodes)
            {
                GraphLayout.Graph graph = layoutSubgraphs
                                          .FirstOrDefault(g => g.FindNode(node.GUID) != null);

                if (graph != null)
                {
                    GraphLayout.Node n       = graph.FindNode(node.GUID);
                    double           offsetY = graph.OffsetY;

                    node.X = n.X;
                    node.Y = n.Y + n.NotesHeight + offsetY;
                    node.ReportPosition();
                    workspace.HasUnsavedChanges = true;

                    double noteOffset = -n.NotesHeight;
                    foreach (NoteModel note in n.LinkedNotes)
                    {
                        if (note.IsSelected || DynamoSelection.Instance.Selection.Count == 0)
                        {
                            note.X      = node.X;
                            note.Y      = node.Y + noteOffset;
                            noteOffset += note.Height + GraphLayout.Graph.VerticalNoteDistance;
                            note.ReportPosition();
                        }
                    }
                }
            }
        }
コード例 #12
0
        /// <summary>
        /// This method pushes changes from the GraphLayout.Graph objects
        /// back to the workspace models.
        /// </summary>
        private static void SaveLayoutGraph(this WorkspaceModel workspace, List <GraphLayout.Graph> layoutSubgraphs)
        {
            // Assign coordinates to nodes inside groups
            foreach (var group in workspace.Annotations)
            {
                GraphLayout.Graph graph = layoutSubgraphs
                                          .FirstOrDefault(g => g.FindNode(group.GUID) != null);

                if (graph != null)
                {
                    GraphLayout.Node n = graph.FindNode(group.GUID);

                    double deltaX = n.X - group.X;
                    double deltaY = n.Y - group.Y + graph.OffsetY;

                    // We update the posistion of all nodes in the
                    // parent group + all nodes in any potential
                    // nested groups.
                    foreach (var node in group.Nodes
                             .OfType <NodeModel>()
                             .Union(group.Nodes.OfType <AnnotationModel>().SelectMany(x => x.Nodes.OfType <NodeModel>())))
                    {
                        node.X += deltaX;
                        node.Y += deltaY;
                        node.ReportPosition();
                    }

                    foreach (NoteModel note in n.LinkedNotes)
                    {
                        if (note.PinnedNode != null)
                        {
                            continue;
                        }
                        if (note.IsSelected || DynamoSelection.Instance.Selection.Count == 0)
                        {
                            note.X += deltaX;
                            note.Y += deltaY;
                            note.ReportPosition();
                        }
                    }

                    group.ReportPosition();
                }
            }

            // Assign coordinates to nodes outside groups
            foreach (var node in workspace.Nodes)
            {
                GraphLayout.Graph graph = layoutSubgraphs
                                          .FirstOrDefault(g => g.FindNode(node.GUID) != null);

                if (graph != null)
                {
                    GraphLayout.Node n       = graph.FindNode(node.GUID);
                    double           offsetY = graph.OffsetY;

                    node.X = n.X;
                    node.Y = n.Y + n.NotesHeight + offsetY;
                    node.ReportPosition();
                    workspace.HasUnsavedChanges = true;

                    double noteOffset = -n.NotesHeight;
                    foreach (NoteModel note in n.LinkedNotes)
                    {
                        if (note.PinnedNode != null)
                        {
                            continue;
                        }
                        if (note.IsSelected || DynamoSelection.Instance.Selection.Count == 0)
                        {
                            note.X      = node.X;
                            note.Y      = node.Y + noteOffset;
                            noteOffset += note.Height + GraphLayout.Graph.VerticalNoteDistance;
                            note.ReportPosition();
                        }
                    }
                }
            }
            // Assign coordinates to connectors outside of groups
            foreach (var connector in workspace.Connectors)
            {
                foreach (var pin in connector.ConnectorPinModels)
                {
                    GraphLayout.Graph graph = layoutSubgraphs
                                              .FirstOrDefault(g => g.FindNode(pin.GUID) != null);

                    if (graph != null)
                    {
                        GraphLayout.Node n = graph.FindNode(pin.GUID);

                        pin.CenterX = n.X;
                        pin.CenterY = n.Y;
                        pin.ReportPosition();
                        workspace.HasUnsavedChanges = true;
                    }
                }
            }
        }