/// <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);
        }
        protected override void OptimizeAfterSequencing(IComparer <object> inEdgeOrder, IComparer <object> outEdgeOrder, LayoutGraph graph, ILayers layers,
                                                        ILayoutDataProvider ldp, IItemFactory itemFactory)
        {
            edge2LaneCrossing  = Maps.CreateHashedEdgeMap();
            node2LaneAlignment = Maps.CreateHashedNodeMap();

            var criticalEdges = Maps.CreateHashedEdgeMap();

            // determine whether an edge crosses a swim lane border and if so in which direction
            foreach (var edge in graph.Edges)
            {
                var originalEdge = GetOriginalEdge(edge, ldp);

                // now we have a 'real' edge with valid valid source and target nodes
                var          originalSourceId = GetLaneId(originalEdge.Source, ldp);
                var          originalTargetId = GetLaneId(originalEdge.Target, ldp);
                LaneCrossing crossing         = LaneCrossing.None;
                if (originalSourceId != originalTargetId)
                {
                    // check if we need to flip the sides because edge and original edge have different directions
                    var flipSides = edge.Source != originalEdge.Source;
                    var sourceId  = flipSides ? originalTargetId : originalSourceId;
                    var targetId  = flipSides ? originalSourceId : originalTargetId;

                    crossing = sourceId > targetId ? LaneCrossing.ToWest : LaneCrossing.ToEast;
                }
                edge2LaneCrossing.Set(edge, crossing);
            }

            // determine basic node alignment
            foreach (var n in graph.Nodes)
            {
                LaneAlignment alignment = CalculateLaneAlignment(n);
                node2LaneAlignment.Set(n, alignment);
            }

            foreach (var n in graph.Nodes)
            {
                // sort the edges with the provided comparer
                n.SortInEdges(inEdgeOrder);
                n.SortOutEdges(outEdgeOrder);

                // calculate 'critical' in and out-edges whose nodes should be aligned in flow
                var bestInEdge  = n.InDegree > 0 ? GetBestFlowEdge(n.InEdges, ldp, graph) : null;
                var bestOutEdge = n.OutDegree > 0 ? GetBestFlowEdge(n.OutEdges, ldp, graph) : null;
                if (bestInEdge != null)
                {
                    criticalEdges.SetDouble(bestInEdge, criticalEdges.GetDouble(bestInEdge) + 0.5);
                }
                if (bestOutEdge != null)
                {
                    criticalEdges.SetDouble(bestOutEdge, criticalEdges.GetDouble(bestOutEdge) + 0.5);
                }
                if (n.Degree <= 4)
                {
                    // should usually be the case and we can distribute each edge to its own side

                    // remember which node side is already taken by an in- or out-edge
                    bool westTakenByInEdge  = false;
                    bool eastTakenByInEdge  = false;
                    bool westTakenByOutEdge = false;
                    bool eastTakenByOutEdge = false;

                    if (n.InDegree > 0 && n.OutDegree < 3)
                    {
                        // if there are at least three out-edges, we distribute those first, otherwise we start with the in-edges

                        var firstInEdge = n.FirstInEdge;
                        var lastInEdge  = n.LastInEdge;
                        if (GetLaneCrossing(firstInEdge) == LaneCrossing.ToEast &&
                            (n.InDegree > 1 || IsSameLayerEdge(firstInEdge, ldp)))
                        {
                            // the first in-edge comes from west and is either a same layer edge or there are other in-edges
                            ConstrainWest(firstInEdge, false, itemFactory);
                            westTakenByInEdge = true;
                        }
                        if (!westTakenByInEdge || n.OutDegree < 2)
                        {
                            // don't use west and east side for in-edges if there are at least 2 out-edges
                            if (GetLaneCrossing(lastInEdge) == LaneCrossing.ToWest &&
                                (n.InDegree > 1 || IsSameLayerEdge(lastInEdge, ldp)))
                            {
                                // the last in-edge comes from east and is either
                                // a same-layer edge or there are other in-edges
                                ConstrainEast(lastInEdge, false, itemFactory);
                                eastTakenByInEdge = true;
                            }
                        }
                    }

                    if (n.OutDegree > 0)
                    {
                        var firstOutEdge = n.FirstOutEdge;
                        var lastOutEdge  = n.LastOutEdge;

                        if (!westTakenByInEdge)
                        {
                            // the west side is still free
                            if (BpmnLayout.IsBoundaryInterrupting(firstOutEdge, graph) ||
                                (GetLaneCrossing(firstOutEdge) == LaneCrossing.ToWest) &&
                                (n.OutDegree > 1 || IsSameLayerEdge(firstOutEdge, ldp)))
                            {
                                // the first out-edge is either boundary interrupting or goes to west and
                                // is either a same layer edge or there are other out-edges
                                ConstrainWest(firstOutEdge, true, itemFactory);
                                westTakenByOutEdge = true;
                            }
                            else if (eastTakenByInEdge && n.OutDegree >= 2 && !IsSameLayerEdge(firstOutEdge.NextOutEdge, ldp))
                            {
                                // the east side is already taken but we have more then one out edge.
                                // if the second out edge is a same layer edge, constraining the firstOutEdge could lead to
                                // no in-flow edge
                                ConstrainWest(firstOutEdge, true, itemFactory);
                                westTakenByOutEdge = true;
                            }
                        }
                        if (!eastTakenByInEdge)
                        {
                            // the east side is still free
                            if (GetLaneCrossing(lastOutEdge) == LaneCrossing.ToEast &&
                                (n.OutDegree > 1 || IsSameLayerEdge(lastOutEdge, ldp)))
                            {
                                // the last out-edge goes to east and
                                // is either a same layer edge or there are other out-edges
                                ConstrainEast(lastOutEdge, true, itemFactory);
                                eastTakenByOutEdge = true;
                            }
                            else if (westTakenByInEdge && n.OutDegree >= 2 && !IsSameLayerEdge(lastOutEdge.PrevOutEdge, ldp))
                            {
                                // the west side is already taken but we have more then one out edge.
                                // if the second last out edge is a same layer edge, constraining the lastOutEdge could lead to
                                // no in-flow edge
                                ConstrainEast(lastOutEdge, true, itemFactory);
                                eastTakenByOutEdge = true;
                            }
                        }
                    }

                    // distribute remaining in-edges
                    if (n.InDegree == 2 &&
                        !(eastTakenByInEdge || westTakenByInEdge))
                    {
                        // two in-edges but none distributed, yet
                        if (bestInEdge == n.FirstInEdge && !eastTakenByOutEdge)
                        {
                            // first in-edge is in-flow edge and east side is still free
                            ConstrainEast(n.LastInEdge, false, itemFactory);
                            eastTakenByInEdge = true;
                        }
                        else if (bestInEdge == n.LastInEdge && !westTakenByOutEdge)
                        {
                            // last in-edge is in-flow edge and west side is still free
                            ConstrainWest(n.FirstInEdge, false, itemFactory);
                            westTakenByInEdge = true;
                        }
                    }
                    else if (n.InDegree == 3 &&
                             !(eastTakenByInEdge && westTakenByInEdge) &&
                             !(IsSameLayerEdge(n.FirstInEdge.NextInEdge, ldp)))
                    {
                        // three in-edges but not both sides taken, yet and the middle edge is no same layer edge
                        if (!eastTakenByOutEdge)
                        {
                            // if not already taken, constraint the last in-edge to east
                            ConstrainEast(n.LastInEdge, false, itemFactory);
                            eastTakenByInEdge = true;
                        }
                        if (!westTakenByOutEdge)
                        {
                            // if not already taken, constraint the first in-edge to west
                            ConstrainWest(n.FirstInEdge, false, itemFactory);
                            westTakenByInEdge = true;
                        }
                    }

                    // distribute remaining out-edges
                    if (n.OutDegree == 2 && !(eastTakenByOutEdge || westTakenByOutEdge))
                    {
                        // two out-edges but none distributed, yet
                        if (bestOutEdge == n.FirstOutEdge && !eastTakenByInEdge)
                        {
                            // first out-edge is in-flow edge and east side is still free
                            ConstrainEast(n.LastOutEdge, true, itemFactory);
                            eastTakenByOutEdge = true;
                        }
                        else if (bestOutEdge == n.LastOutEdge && !westTakenByInEdge)
                        {
                            // last out-edge is in-flow edge and west side is still free
                            ConstrainWest(n.FirstOutEdge, true, itemFactory);
                            westTakenByOutEdge = true;
                        }
                    }
                    else if (n.OutDegree == 3 &&
                             !(eastTakenByOutEdge && westTakenByOutEdge) &&
                             !(IsSameLayerEdge(n.FirstOutEdge.NextOutEdge, ldp)))
                    {
                        // three out-edges but not both sides taken, yet and the middle edge is no same layer edge
                        if (!eastTakenByInEdge)
                        {
                            // if not already taken, constraint the last out-edge to east
                            ConstrainEast(n.LastOutEdge, true, itemFactory);
                            eastTakenByOutEdge = true;
                        }
                        if (!westTakenByInEdge)
                        {
                            // if not already taken, constraint the first out-edge to west
                            ConstrainWest(n.FirstOutEdge, true, itemFactory);
                            westTakenByOutEdge = true;
                        }
                    }
                }
            }

            // register the data provider for critical edge paths. It is deregistered again by BpmnLayout itself
            graph.AddDataProvider(HierarchicLayout.CriticalEdgePriorityDpKey, criticalEdges);

            sameLayerData      = null;
            edge2LaneCrossing  = null;
            node2LaneAlignment = null;
        }
Пример #3
0
        /// <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;
        }