/// <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++; } } }
/// <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(); } } } } }
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)))); } } }
/// <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); }
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(); }
/// <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); }
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(); }
/// <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++; } } }
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(); }
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(); }
/// <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(); } } } } }
/// <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; } } } }