/// <summary>
        /// Adjusts the edge end points so they don't end outside the shape of the node they are attached to.
        /// </summary>
        private static void AdjustPortLocation(LayoutGraph graph, Edge e, YPointPath path, bool atSource)
        {
            Node   node     = atSource ? e.Source : e.Target;
            YPoint pointRel = atSource ? graph.GetSourcePointRel(e) : graph.GetTargetPointRel(e);
            // get offset from the node center to the end of the shape at the node side the edge connects to
            LineSegment segment = path.GetLineSegment(atSource ? 0 : path.Length() - 2);
            double      offset  = Math.Min(graph.GetWidth(node), graph.GetHeight(node)) / 2;
            double      offsetX = segment.DeltaX > 0 ^ atSource ? -offset : offset;
            double      offsetY = segment.DeltaY > 0 ^ atSource ? -offset : offset;
            // if the edge end point is at the center of this side, we use the calculated offset to put the end point on
            // the node bounds, otherwise we prolong the last segment to the center line of the node so it doesn't end
            // outside the node's shape
            YPoint newPortLocation = segment.IsHorizontal
        ? new YPoint(pointRel.Y != 0 ? 0 : offsetX, pointRel.Y)
        : new YPoint(pointRel.X, pointRel.X != 0 ? 0 : offsetY);

            if (atSource)
            {
                graph.SetSourcePointRel(e, newPortLocation);
            }
            else
            {
                graph.SetTargetPointRel(e, newPortLocation);
            }
        }
        private void AdjustNodeSize(Node node, LayoutGraph graph)
        {
            double width  = 60;
            double height = 40;

            var leftEdgeSpace  = CalcRequiredSpace(node.InEdges, graph);
            var rightEdgeSpace = CalcRequiredSpace(node.OutEdges, graph);

            if (LayoutOrientation == LayoutOrientation.TopToBottom || LayoutOrientation == LayoutOrientation.BottomToTop)
            {
                // we have to enlarge the width such that the in-/out-edges can be placed side by side without overlaps
                width = Math.Max(width, leftEdgeSpace);
                width = Math.Max(width, rightEdgeSpace);
            }
            else
            {
                // we have to enlarge the height such that the in-/out-edges can be placed side by side without overlaps
                height = Math.Max(height, leftEdgeSpace);
                height = Math.Max(height, rightEdgeSpace);
            }

            // adjust size for edges with strong port constraints
            var edgeThicknessDP = graph.GetDataProvider(HierarchicLayout.EdgeThicknessDpKey);

            if (edgeThicknessDP != null)
            {
                foreach (var edge in node.Edges)
                {
                    var thickness = edgeThicknessDP.GetDouble(edge);

                    var spc = PortConstraint.GetSPC(graph, edge);
                    if (edge.Source == node && spc != null && spc.Strong)
                    {
                        var sourcePoint = graph.GetSourcePointRel(edge);
                        width  = Math.Max(width, Math.Abs(sourcePoint.X) * 2 + thickness);
                        height = Math.Max(height, Math.Abs(sourcePoint.Y) * 2 + thickness);
                    }

                    var tpc = PortConstraint.GetTPC(graph, edge);
                    if (edge.Target == node && tpc != null && tpc.Strong)
                    {
                        var targetPoint = graph.GetTargetPointRel(edge);
                        width  = Math.Max(width, Math.Abs(targetPoint.X) * 2 + thickness);
                        height = Math.Max(height, Math.Abs(targetPoint.Y) * 2 + thickness);
                    }
                }
            }
            graph.SetSize(node, width, height);
        }
        /// <inheritdoc/>
        public virtual double GetProfit(LabelCandidate candidate)
        {
            if (candidate.Owner is IEdgeLabelLayout)
            {
                return(1);
            }
            double           profit = 0;
            INodeLabelLayout nl     = (INodeLabelLayout)candidate.Owner;
            var node            = graph.GetOwner(nl);
            var nodeLayout      = graph.GetLayout(node);
            var candidateLayout = candidate.BoundingBox;

            var isLeft   = candidateLayout.X + candidateLayout.Width / 2 < nodeLayout.X;
            var isRight  = candidateLayout.X + candidateLayout.Width / 2 > (nodeLayout.X + nodeLayout.Width);
            var isTop    = candidateLayout.Y + candidateLayout.Height / 2 < nodeLayout.Y;
            var isBottom = candidateLayout.Y + candidateLayout.Height / 2 > (nodeLayout.Y + nodeLayout.Height);

            var horizontalCenter = !isLeft && !isRight;
            var verticalCenter   = !isTop && !isBottom;

            if (horizontalCenter && verticalCenter)
            {
                // candidate is in center -> don't use
                return(0);
            }
            else if (horizontalCenter || verticalCenter)
            {
                profit = 0.95;
            }
            else
            {
                // diagonal candidates get a bit less profit
                profit = 0.9;
            }
            foreach (var edge in node.Edges)
            {
                var portLocation = edge.Source == node?graph.GetSourcePointRel(edge) : graph.GetTargetPointRel(edge);

                if (Math.Abs(portLocation.X) > Math.Abs(portLocation.Y))
                {
                    // edge at left or right
                    if (portLocation.X < 0 && isLeft || portLocation.X > 0 && isRight)
                    {
                        if (isTop || isBottom)
                        {
                            profit -= 0.03;
                        }
                        else
                        {
                            // edge at same side as candidate
                            profit -= 0.2;
                        }
                    }
                    else if (horizontalCenter)
                    {
                        // candidate is close to the edge but not on the same side
                        profit -= 0.01;
                    }
                }
                else
                {
                    // edge at top or bottom
                    if (portLocation.Y < 0 && isTop || portLocation.Y > 0 && isBottom)
                    {
                        if (isLeft || isRight)
                        {
                            profit -= 0.03;
                        }
                        else
                        {
                            profit -= 0.2;
                        }
                    }
                    else if (verticalCenter)
                    {
                        // candidate is close to the edge but not on the same side
                        profit -= 0.01;
                    }
                }
            }

            return(Math.Max(0, profit));
        }