/// <inheritdoc /> protected override ILayoutAlgorithm CreateConfiguredLayout(GraphControl graphControl) { var transformer = new GraphTransformer(); transformer.Operation = OperationItem; transformer.SubgraphLayoutEnabled = ActOnSelectionOnlyItem; transformer.RotationAngle = RotationAngleItem; if (ApplyBestFitRotationItem && OperationItem == OperationType.Rotate) { var size = graphControl.InnerSize; ApplyBestFitRotationItem = true; var layoutGraph = new LayoutGraphAdapter(graphControl.Graph).CreateCopiedLayoutGraph(); transformer.RotationAngle = GraphTransformer.FindBestFitRotationAngle(layoutGraph, size.Width, size.Height); } else { ApplyBestFitRotationItem = false; } transformer.ScaleFactor = ScaleFactorItem; transformer.ScaleNodeSize = ScaleNodeSizeItem; transformer.TranslateX = TranslateXItem; transformer.TranslateY = TranslateYItem; return(transformer); }
/// <summary> /// Initializes the graph instance setting default styles /// and creating a small sample graph. /// </summary> protected virtual void InitializeGraph() { IGraph graph = Graph; // load a sample graph new GraphMLIOHandler().Read(graph, "Resources/sample.graphml"); // set some defaults graph.NodeDefaults.Style = Enumerable.First(graph.Nodes).Style; graph.NodeDefaults.ShareStyleInstance = true; // we start with a simple run of OrganicLayout to get a good starting result // the algorithm is optimized to "unfold" graphs quicker than // interactive organic, so we use this result as a starting solution var initialLayout = new OrganicLayout { MinimumNodeDistance = 50 }; graph.ApplyLayout(initialLayout); // center the initial graph GraphControl.FitGraphBounds(); movedNodes = new List <INode>(); // we wrap the PositionHandler for nodes so that we always have the collection of nodes // that are currently being moved available in "movedNodes". // this way we do not need to know how the node is moved and do not have to guess // what elements are currently being moved based upon selection, etc. graph.GetDecorator().NodeDecorator.PositionHandlerDecorator.SetImplementationWrapper( (item, implementation) => new CollectingPositionHandlerWrapper(item, movedNodes, implementation)); // create a copy of the graph for the layout algorithm LayoutGraphAdapter adapter = new LayoutGraphAdapter(graphControl.Graph); copiedLayoutGraph = adapter.CreateCopiedLayoutGraph(); // create and start the layout algorithm layout = StartLayout(); WakeUp(); // register a listener so that structure updates are handled automatically graph.NodeCreated += delegate(object source, ItemEventArgs <INode> args) { if (layout != null) { var center = args.Item.Layout.GetCenter(); layout.SyncStructure(true); //we nail down all newly created nodes var copiedNode = copiedLayoutGraph.GetCopiedNode(args.Item); layout.SetCenter(copiedNode, center.X, center.Y); layout.SetInertia(copiedNode, 1); layout.SetStress(copiedNode, 0); layout.WakeUp(); } }; graph.NodeRemoved += OnStructureChanged; graph.EdgeCreated += OnStructureChanged; graph.EdgeRemoved += OnStructureChanged; }
private static void AddNodeHalos(LayoutGraphAdapter adapter, IGraph graph, bool layoutOnlySelection) { var nodeHalos = new DictionaryMapper <INode, NodeHalo>(); foreach (var node in graph.Nodes) { var top = 0.0; var left = 0.0; var bottom = 0.0; var right = 0.0; // for each port with an EventPortStyle extend the node halo to cover the ports render size foreach (var port in node.Ports) { var eventPortStyle = port.Style as EventPortStyle; if (eventPortStyle != null) { var renderSize = eventPortStyle.RenderSize; var location = port.GetLocation(); top = Math.Max(top, node.Layout.Y - location.Y - renderSize.Height / 2); left = Math.Max(left, node.Layout.X - location.X - renderSize.Width / 2); bottom = Math.Max(bottom, location.Y + renderSize.Height / 2 - node.Layout.GetMaxY()); right = Math.Max(right, location.X + renderSize.Width / 2 - node.Layout.GetMaxX()); } } // for each node without incoming or outgoing edges reserve space for laid out exterior labels if (graph.InDegree(node) == 0 || graph.OutDegree(node) == 0) { foreach (var label in node.Labels) { if (IsNodeLabelAffected(label, adapter, layoutOnlySelection)) { var labelBounds = label.GetLayout().GetBounds(); if (graph.InDegree(node) == 0) { left = Math.Max(left, labelBounds.Width); top = Math.Max(top, labelBounds.Height); } if (graph.OutDegree(node) == 0) { right = Math.Max(right, labelBounds.Width); bottom = Math.Max(bottom, labelBounds.Height); } } } } nodeHalos[node] = NodeHalo.Create(top, left, bottom, right); } adapter.AddDataProvider(NodeHalo.NodeHaloDpKey, nodeHalos); }
private static bool IsNodeLabelAffected(ILabel label, LayoutGraphAdapter adapter, bool layoutOnlySelection) { var node = label.Owner as INode; if (node != null) { var isInnerLabel = node.Layout.Contains(label.GetLayout().GetCenter()); bool isPool = node.Style is PoolNodeStyle; bool isChoreography = node.Style is ChoreographyNodeStyle; var isGroupNode = adapter.AdaptedGraph.IsGroupNode(node); return(!isInnerLabel && !isPool && !isChoreography && !isGroupNode && (!layoutOnlySelection || adapter.SelectionModel.IsSelected(node))); } return(false); }
private static void AddEdgeLabelPlacementDescriptors(LayoutGraphAdapter adapter) { var atSourceDescriptor = new PreferredPlacementDescriptor { PlaceAlongEdge = LabelPlacements.AtSourcePort, SideOfEdge = LabelPlacements.LeftOfEdge | LabelPlacements.RightOfEdge, }; adapter.AddDataProvider(LayoutGraphAdapter.EdgeLabelLayoutPreferredPlacementDescriptorDpKey, Mappers.FromDelegate((ILabel label) => { var edgeType = ((BpmnEdgeStyle)((IEdge)label.Owner).Style).Type; if (edgeType == EdgeType.SequenceFlow || edgeType == EdgeType.DefaultFlow || edgeType == EdgeType.ConditionalFlow) { // labels on sequence, default and conditional flow edges should be placed at the source side. return(atSourceDescriptor); } return(null); })); }
protected override void Apply(LayoutGraphAdapter adapter, ILayoutAlgorithm layout, CopiedLayoutGraph layoutGraph) { var graph = adapter.AdaptedGraph; // check if only selected elements should be laid out var layoutOnlySelection = layout is BpmnLayout && ((BpmnLayout)layout).Scope == Scope.SelectedElements; // mark 'flow' edges, i.e. sequence flows, default flows and conditional flows adapter.AddDataProvider(BpmnLayout.SequenceFlowEdgesDpKey, Mappers.FromDelegate <IEdge, bool>(IsSequenceFlow)); // mark boundary interrupting edges for the BalancingPortOptimizer adapter.AddDataProvider(BpmnLayout.BoundaryInterruptingEdgesDpKey, Mappers.FromDelegate((IEdge edge) => edge.SourcePort.Style is EventPortStyle)); // mark conversations, events and gateways so their port locations are adjusted adapter.AddDataProvider(PortLocationAdjuster.AffectedNodesDpKey, Mappers.FromDelegate((INode node) => (node.Style is ConversationNodeStyle || node.Style is EventNodeStyle || node.Style is GatewayNodeStyle))); // add NodeHalos around nodes with event ports or specific exterior labels so the layout keeps space for the event ports and labels as well AddNodeHalos(adapter, graph, layoutOnlySelection); // add PreferredPlacementDescriptors for labels on sequence, default or conditional flows to place them at source side AddEdgeLabelPlacementDescriptors(adapter); // mark nodes, edges and labels as either fixed or affected by the layout and configure port constraints and incremental hints MarkFixedAndAffectedItems(adapter, layoutOnlySelection); // mark associations and message flows as undirected so they have less impact on layering EdgeDirectedness.Delegate = edge => (IsMessageFlow(edge) || IsAssociation(edge)) ? 0 : 1; // add layer constraints for start events, sub processes and message flows AddLayerConstraints(graph); // add EdgeLayoutDescriptor to specify minimum edge length for edges AddMinimumEdgeLength(MinimumEdgeLength); base.Apply(adapter, layout, layoutGraph); }
protected override void Apply(LayoutGraphAdapter layoutGraphAdapter, ILayoutAlgorithm layout, CopiedLayoutGraph layoutGraph) { layoutGraphAdapter.AddDataProvider(SelectedNodes.DpKey, SelectedNodes.ProvideMapper(layoutGraphAdapter, layout)); }
/// <summary> /// Executes the module on the given graph using the provided context. /// </summary> /// <remarks> /// The layout will be calculated <see cref="RunInBackground">optionally</see> /// in a separate thread in method <see cref="RunModuleAsync"/>. /// </remarks> /// <param name="graph">The graph to execute on.</param> /// <param name="newContext">The context to use. This method will query a <c>ISelectionModel<IModelItem></c></param> /// for the selected nodes and edges and the <c>GraphControl</c> to morph the layout. protected virtual async Task StartWithIGraph(IGraph graph, ILookup newContext) { this.graph = graph; if (ShouldConfigureTableLayout()) { PrepareTableLayout(); } ISelectionModel <IModelItem> selectionModel = newContext.Lookup <ISelectionModel <IModelItem> >(); LayoutGraphAdapter adapter = new LayoutGraphAdapter(graph, selectionModel); this.layoutGraph = adapter.CreateCopiedLayoutGraph(); ILookup additionalLookup = Lookups.Single(layoutGraph, typeof(LayoutGraph)); ILookup wrappedLookup = Lookups.Wrapped(newContext, additionalLookup); try { ICompoundEdit compoundEdit = graph.BeginEdit("Layout", "Layout"); CheckReentrant(wrappedLookup); ConfigureModule(); if (RunInBackground) { // without the LayoutExecutor helper class on the layout graph side of things, we register the aborthandler // to the layout graph with the utility method provided by AbortHandler var abortHandler = AbortHandler.CreateForGraph(layoutGraph); // now create the dialog that controls the abort handler abortDialog = new AbortDialog { AbortHandler = abortHandler, Owner = Application.Current.MainWindow }; // start the layout in another thread. var layoutThread = new Thread(async() => await RunModuleAsync(wrappedLookup, graph, compoundEdit)); // now if we are not doing a quick layout - and if it takes more than a few seconds, we open the dialog to // enable the user to stop or cancel the execution var showDialogTimer = new DispatcherTimer(DispatcherPriority.Normal, abortDialog.Dispatcher) { Interval = TimeSpan.FromSeconds(2) }; showDialogTimer.Tick += delegate { // it could be that the layout is already done - so check whether we still // need to open the dialog var dialogInstance = abortDialog; if (dialogInstance != null) { // open the abort dialog dialogInstance.Show(); } // we only want to let it go off once - so stop the timer showDialogTimer.Stop(); }; // kick-off the timer and the layout showDialogTimer.Start(); layoutThread.Start(); } else { await RunModuleAsync(wrappedLookup, graph, compoundEdit); } } catch (Exception e) { FreeReentrant(); TableLayoutConfigurator.CleanUp(graph); OnDone(new LayoutEventArgs(e)); //optionally do something here... } }
private void MarkFixedAndAffectedItems(LayoutGraphAdapter adapter, bool layoutOnlySelection) { if (layoutOnlySelection) { var affectedEdges = Mappers.FromDelegate((IEdge edge) => adapter.SelectionModel.IsSelected(edge) || adapter.SelectionModel.IsSelected(edge.GetSourceNode()) || adapter.SelectionModel.IsSelected(edge.GetTargetNode())); adapter.AddDataProvider(LayoutKeys.AffectedEdgesDpKey, affectedEdges); // fix ports of unselected edges and edges at event ports adapter.AddDataProvider(PortConstraintKeys.SourcePortConstraintDpKey, Mappers.FromDelegate <IEdge, PortConstraint>( edge => (!affectedEdges[edge] || edge.SourcePort.Style is EventPortStyle) ? PortConstraint.Create(GetSide(edge, true)) : null)); adapter.AddDataProvider(PortConstraintKeys.TargetPortConstraintDpKey, Mappers.FromDelegate <IEdge, PortConstraint>( edge => !affectedEdges[edge] ? PortConstraint.Create(GetSide(edge, false)) : null)); // give core layout hints that selected nodes and edges should be incremental IncrementalHints.ContextDelegate = (item, factory) => { if (item is INode && adapter.SelectionModel.IsSelected(item)) { return(factory.CreateLayerIncrementallyHint(item)); } else if (item is IEdge && affectedEdges[(IEdge)item]) { return(factory.CreateSequenceIncrementallyHint(item)); } return(null); }; adapter.AddDataProvider(BpmnLayout.AffectedLabelsDpKey, Mappers.FromDelegate <ILabel, bool>(label => { var edge = label.Owner as IEdge; if (edge != null) { return(affectedEdges[edge]); } var node = label.Owner as INode; if (node != null) { var isInnerLabel = node.Layout.Contains(label.GetLayout().GetCenter()); bool isPool = node.Style is PoolNodeStyle; bool isChoreography = node.Style is ChoreographyNodeStyle; return(!isInnerLabel && !isPool && !isChoreography && adapter.SelectionModel.IsSelected(node)); } return(false); })); } else { // fix source port of edges at event ports adapter.AddDataProvider(PortConstraintKeys.SourcePortConstraintDpKey, Mappers.FromDelegate <IEdge, PortConstraint>( edge => edge.SourcePort.Style is EventPortStyle ? PortConstraint.Create(GetSide(edge, true)) : null)); adapter.AddDataProvider(BpmnLayout.AffectedLabelsDpKey, Mappers.FromDelegate <ILabel, bool>(label => { if (label.Owner is IEdge) { return(true); } var node = label.Owner as INode; if (node != null) { var isInnerLabel = node.Layout.Contains(label.GetLayout().GetCenter()); bool isPool = node.Style is PoolNodeStyle; bool isChoreography = node.Style is ChoreographyNodeStyle; return(!isInnerLabel && !isPool && !isChoreography); } return(false); })); } }
protected override void Apply(LayoutGraphAdapter layoutGraphAdapter, ILayoutAlgorithm layout, CopiedLayoutGraph layoutGraph) { layoutGraphAdapter.AddDataProvider(SelectedLabelsStage.SelectedLabelsAtItemKey, SelectedLabelsAtItem.ProvideMapper(layoutGraphAdapter, layout)); }
private void DoLayout() { LayoutGraphAdapter.ApplyLayout(Graph, layout); graphControl.FitGraphBounds(); }