/// <summary> /// Returns the best suited edge in <paramref name="edges"/> for use as in-flow edge or <see langword="null"/> /// if no such edge could be found. /// </summary> private Edge GetBestFlowEdge(IEnumerable <Edge> edges, ILayoutDataProvider ldp, LayoutGraph graph) { List <Edge> weakCandidates = new List <Edge>(); List <Edge> candidates = new List <Edge>(); foreach (var edge in edges) { var originalEdge = GetOriginalEdge(edge, ldp); if ((LaneCrossing)edge2LaneCrossing.Get(edge) != LaneCrossing.None || BpmnLayout.IsBoundaryInterrupting(originalEdge, graph) || IsSameLayerEdge(originalEdge, ldp) || edge.SelfLoop) { // an edge should not be aligned if: // - it crosses stripe borders // - it is boundary interrupting // - it is a same-layer edge // - it is a self-loop continue; } if (ldp.GetEdgeData(edge).Reversed || !BpmnLayout.IsSequenceFlow(originalEdge, graph)) { // it is only a weak candidate if: // - it is reversed // - it is no sequence flow weakCandidates.Add(edge); } else { candidates.Add(edge); } } if (candidates.Count > 0) { // if there are several candidates, choose the one that would keep the LaneAlignment // of its source and target node consistent candidates.Sort((edge1, edge2) => { var ac1 = GetAlignmentConsistency(edge1); var ac2 = GetAlignmentConsistency(edge2); return(ac2 - ac1); }); return(candidates[0]); } if (weakCandidates.Count > 0) { return(weakCandidates[(int)Math.Floor(weakCandidates.Count / 2.0)]); } return(null); }
/// <inheritdoc/> public override void AssignLayers(LayoutGraph graph, ILayers layers, ILayoutDataProvider ldp) { // get core layer assignment base.AssignLayers(graph, layers, ldp); // Hide all edges that are no sequence flows var graphHider = new LayoutGraphHider(graph); foreach (var edge in graph.GetEdgeArray()) { if (!BpmnLayout.IsSequenceFlow(edge, graph)) { graphHider.Hide(edge); } } // determine current layer of all nodes currentLayers = new int[graph.NodeCount]; for (int i = 0; i < layers.Size(); i++) { for (INodeCursor nc = layers.GetLayer(i).List.Nodes(); nc.Ok; nc.Next()) { currentLayers[nc.Node.Index] = i; } } // mark nodes on a back-loop and candidates that may be on a back loop if other back-loop nodes are reassigned nodeStates = new NodeState[graph.NodeCount]; NodeList candidates = new NodeList(); NodeList backLoopNodes = new NodeList(); for (int i = layers.Size() - 1; i >= 0; i--) { // check from last to first layer to detect candidates as well NodeList nodes = layers.GetLayer(i).List; UpdateNodeStates(nodes, backLoopNodes, candidates); } // swap layer for back-loop nodes while (backLoopNodes.Count > 0) { for (INodeCursor nc = backLoopNodes.Nodes(); nc.Ok; nc.Next()) { Node node = nc.Node; int currentLayer = currentLayers[node.Index]; // the target layer is the next layer after the highest fixed target node layer int targetLayer = 0; for (Edge edge = node.FirstOutEdge; edge != null; edge = edge.NextOutEdge) { int targetNodeIndex = edge.Target.Index; if (nodeStates[targetNodeIndex] == NodeState.Fixed) { targetLayer = Math.Max(targetLayer, currentLayers[targetNodeIndex] + 1); } } if (targetLayer == 0) { // no fixed target found, so all targets must be candidates // -> we skip the node as we don't know where the candidates will be placed at the end continue; } if (targetLayer < currentLayer) { layers.GetLayer(currentLayer).Remove(node); layers.GetLayer(targetLayer).Add(node); currentLayers[node.Index] = targetLayer; nodeStates[node.Index] = NodeState.Fixed; } } backLoopNodes.Clear(); // update states of the candidates candidates = UpdateNodeStates(candidates, backLoopNodes, new NodeList()); } // remove empty layers for (int i = layers.Size() - 1; i >= 0; i--) { if (layers.GetLayer(i).List.Count == 0) { layers.Remove(i); } } // cleanup graphHider.UnhideAll(); nodeStates = null; currentLayers = null; }