Search in sources :

Example 1 with ElkRectangle

use of org.eclipse.elk.core.math.ElkRectangle in project elk by eclipse.

the class PortLabelPlacementCalculator method constrainedOutsidePortLabelPlacement.

// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constrained Outside Port Labels
/**
 * Place the port label cells outside of the node in the knowledge that there might not be enough space to place
 * them without overlaps.
 */
private static void constrainedOutsidePortLabelPlacement(final NodeContext nodeContext, final PortSide portSide) {
    Collection<PortContext> portContexts = nodeContext.portContexts.get(portSide);
    // simply revert to simple port label placement
    if (portContexts.size() <= 2 || portSide == PortSide.EAST || portSide == PortSide.WEST) {
        simpleOutsidePortLabelPlacement(nodeContext, portSide);
        return;
    }
    // If space-efficient port labels are active, the leftmost / topmost port's label must be placed to its left /
    // above it
    boolean portWithSpecialNeeds = nodeContext.portLabelsPlacement.contains(PortLabelPlacement.SPACE_EFFICIENT);
    // Prepare things
    OverlapRemovalDirection overlapRemovalDirection = portSide == PortSide.NORTH ? OverlapRemovalDirection.UP : OverlapRemovalDirection.DOWN;
    VerticalLabelAlignment verticalLabelAlignment = portSide == PortSide.NORTH ? VerticalLabelAlignment.BOTTOM : VerticalLabelAlignment.TOP;
    // Obtain a rectangle strip overlap remover, which will actually do most of the work
    RectangleStripOverlapRemover overlapRemover = RectangleStripOverlapRemover.createForDirection(overlapRemovalDirection).withGap(nodeContext.portLabelSpacing);
    // Iterate over our ports and add rectangles to the overlap remover. Also, calculate the start coordinate
    double startCoordinate = portSide == PortSide.NORTH ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
    for (PortContext portContext : portContexts) {
        if (portContext.portLabelCell == null || !portContext.portLabelCell.hasLabels()) {
            continue;
        }
        KVector portSize = portContext.port.getSize();
        KVector portPosition = portContext.portPosition;
        LabelCell portLabelCell = portContext.portLabelCell;
        ElkRectangle portLabelCellRect = portLabelCell.getCellRectangle();
        // Setup the label cell's cell rectangle
        portLabelCellRect.width = portLabelCell.getMinimumWidth();
        portLabelCellRect.height = portLabelCell.getMinimumHeight();
        if (portWithSpecialNeeds) {
            portLabelCellRect.x = portPosition.x - portLabelCell.getMinimumWidth() - nodeContext.portLabelSpacing;
            portWithSpecialNeeds = false;
        } else {
            portLabelCellRect.x = portPosition.x + portSize.x + nodeContext.portLabelSpacing;
        }
        portLabelCell.setVerticalAlignment(verticalLabelAlignment);
        portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.RIGHT);
        // Add the rectangle to the overlap remover
        overlapRemover.addRectangle(portLabelCellRect);
        // Update start coordinate
        startCoordinate = portSide == PortSide.NORTH ? Math.min(startCoordinate, portPosition.y) : Math.max(startCoordinate, portPosition.y + portContext.port.getSize().y);
    }
    // The start coordinate needs to be offset by the port-label space
    startCoordinate += portSide == PortSide.NORTH ? -nodeContext.portLabelSpacing : nodeContext.portLabelSpacing;
    // Invoke the overlap remover
    overlapRemover.withStartCoordinate(startCoordinate).removeOverlaps();
    // We need to update the label cell's coordinates to be relative to the ports
    for (PortContext portContext : portContexts) {
        if (portContext.portLabelCell == null || !portContext.portLabelCell.hasLabels()) {
            continue;
        }
        ElkRectangle portLabelCellRect = portContext.portLabelCell.getCellRectangle();
        // Setup the label cell's cell rectangle
        portLabelCellRect.x -= portContext.portPosition.x;
        portLabelCellRect.y -= portContext.portPosition.y;
    }
}
Also used : RectangleStripOverlapRemover(org.eclipse.elk.alg.common.overlaps.RectangleStripOverlapRemover) LabelCell(org.eclipse.elk.alg.common.nodespacing.cellsystem.LabelCell) VerticalLabelAlignment(org.eclipse.elk.alg.common.nodespacing.cellsystem.VerticalLabelAlignment) OverlapRemovalDirection(org.eclipse.elk.alg.common.overlaps.RectangleStripOverlapRemover.OverlapRemovalDirection) KVector(org.eclipse.elk.core.math.KVector) ElkRectangle(org.eclipse.elk.core.math.ElkRectangle) PortContext(org.eclipse.elk.alg.common.nodespacing.internal.PortContext)

Example 2 with ElkRectangle

use of org.eclipse.elk.core.math.ElkRectangle in project elk by eclipse.

the class PortPlacementCalculator method placeVerticalFreePorts.

/**
 * Places ports on the eastern and western side for the unconstrained cases.
 */
private static void placeVerticalFreePorts(final NodeContext nodeContext, final PortSide portSide) {
    // If there are no ports on the given side, abort
    if (nodeContext.portContexts.get(portSide).isEmpty()) {
        return;
    }
    // Retrieve the proper inside port label cell, which will give us hints as to where to place our ports
    AtomicCell insidePortLabelCell = nodeContext.insidePortLabelCells.get(portSide);
    ElkRectangle insidePortLabelCellRectangle = insidePortLabelCell.getCellRectangle();
    ElkPadding insidePortLabelCellPadding = insidePortLabelCell.getPadding();
    // Note that we don't have to distinguish any cases here because the port margins already include space
    // required for labels, if such space is to be reserved. Yay!
    PortAlignment portAlignment = nodeContext.getPortAlignment(portSide);
    double availableSpace = insidePortLabelCellRectangle.height - insidePortLabelCellPadding.top - insidePortLabelCellPadding.bottom;
    double calculatedPortPlacementHeight = insidePortLabelCell.getMinimumContentAreaSize().y;
    double currentYPos = insidePortLabelCellRectangle.y + insidePortLabelCellPadding.top;
    double spaceBetweenPorts = nodeContext.portPortSpacing;
    double nodeWidth = nodeContext.nodeSize.x;
    // to center to keep things from looking stupid
    if ((portAlignment == PortAlignment.DISTRIBUTED || portAlignment == PortAlignment.JUSTIFIED) && nodeContext.portContexts.get(portSide).size() == 1) {
        calculatedPortPlacementHeight = modifiedPortPlacementSize(nodeContext, portAlignment, calculatedPortPlacementHeight);
        portAlignment = PortAlignment.CENTER;
    }
    if (availableSpace < calculatedPortPlacementHeight && !nodeContext.sizeOptions.contains(SizeOptions.PORTS_OVERHANG)) {
        // the space between them to cram them into the available space.
        if (portAlignment == PortAlignment.DISTRIBUTED) {
            spaceBetweenPorts += (availableSpace - calculatedPortPlacementHeight) / (nodeContext.portContexts.get(portSide).size() + 1);
            currentYPos += spaceBetweenPorts;
        } else {
            spaceBetweenPorts += (availableSpace - calculatedPortPlacementHeight) / (nodeContext.portContexts.get(portSide).size() - 1);
        }
    } else {
        // case where we should fall back to centered alignment
        if (availableSpace < calculatedPortPlacementHeight) {
            calculatedPortPlacementHeight = modifiedPortPlacementSize(nodeContext, portAlignment, calculatedPortPlacementHeight);
            portAlignment = PortAlignment.CENTER;
        }
        // otherwise expect)
        switch(portAlignment) {
            case BEGIN:
                // There's nothing to do here
                break;
            case CENTER:
                currentYPos += (availableSpace - calculatedPortPlacementHeight) / 2;
                break;
            case END:
                currentYPos += availableSpace - calculatedPortPlacementHeight;
                break;
            case DISTRIBUTED:
                // In this case, if there is not enough space available to place the ports, we are allowed to overhang.
                // We thus need to ensure that we're only ever increasing the port spacing here
                double additionalSpaceBetweenPorts = (availableSpace - calculatedPortPlacementHeight) / (nodeContext.portContexts.get(portSide).size() + 1);
                spaceBetweenPorts += Math.max(0, additionalSpaceBetweenPorts);
                currentYPos += spaceBetweenPorts;
                break;
            case JUSTIFIED:
                // In this case, if there is not enough space available to place the ports, we are allowed to overhang.
                // We thus need to ensure that we're only ever increasing the port spacing here
                additionalSpaceBetweenPorts = (availableSpace - calculatedPortPlacementHeight) / (nodeContext.portContexts.get(portSide).size() - 1);
                spaceBetweenPorts += Math.max(0, additionalSpaceBetweenPorts);
                break;
        }
    }
    // Iterate over all ports and place them
    for (PortContext portContext : nodeContext.portContexts.get(portSide)) {
        portContext.portPosition.x = calculateVerticalPortXCoordinate(portContext, nodeWidth);
        portContext.portPosition.y = currentYPos + portContext.portMargin.top;
        // Update the y coordinate for the next port
        currentYPos += portContext.portMargin.top + portContext.port.getSize().y + portContext.portMargin.bottom + spaceBetweenPorts;
    }
}
Also used : PortAlignment(org.eclipse.elk.core.options.PortAlignment) AtomicCell(org.eclipse.elk.alg.common.nodespacing.cellsystem.AtomicCell) ElkRectangle(org.eclipse.elk.core.math.ElkRectangle) ElkPadding(org.eclipse.elk.core.math.ElkPadding) PortContext(org.eclipse.elk.alg.common.nodespacing.internal.PortContext)

Example 3 with ElkRectangle

use of org.eclipse.elk.core.math.ElkRectangle in project elk by eclipse.

the class PortPlacementCalculator method placeHorizontalFreePorts.

/**
 * Places ports on the northern and southern side for the unconstrained cases.
 */
private static void placeHorizontalFreePorts(final NodeContext nodeContext, final PortSide portSide) {
    // If there are no ports on the given side, abort
    if (nodeContext.portContexts.get(portSide).isEmpty()) {
        return;
    }
    // Retrieve the proper inside port label cell, which will give us hints as to where to place our ports
    AtomicCell insidePortLabelCell = nodeContext.insidePortLabelCells.get(portSide);
    ElkRectangle insidePortLabelCellRectangle = insidePortLabelCell.getCellRectangle();
    ElkPadding insidePortLabelCellPadding = insidePortLabelCell.getPadding();
    // Note that we don't have to distinguish any cases here because the port margins already include space
    // required for labels, if such space is to be reserved. Yay!
    PortAlignment portAlignment = nodeContext.getPortAlignment(portSide);
    double availableSpace = insidePortLabelCellRectangle.width - insidePortLabelCellPadding.left - insidePortLabelCellPadding.right;
    double calculatedPortPlacementWidth = insidePortLabelCell.getMinimumContentAreaSize().x;
    double currentXPos = insidePortLabelCellRectangle.x + insidePortLabelCellPadding.left;
    double spaceBetweenPorts = nodeContext.portPortSpacing;
    // to center to keep things from looking stupid
    if ((portAlignment == PortAlignment.DISTRIBUTED || portAlignment == PortAlignment.JUSTIFIED) && nodeContext.portContexts.get(portSide).size() == 1) {
        calculatedPortPlacementWidth = modifiedPortPlacementSize(nodeContext, portAlignment, calculatedPortPlacementWidth);
        portAlignment = PortAlignment.CENTER;
    }
    if (availableSpace < calculatedPortPlacementWidth && !nodeContext.sizeOptions.contains(SizeOptions.PORTS_OVERHANG)) {
        // the space between them to cram them into the available space.
        if (portAlignment == PortAlignment.DISTRIBUTED) {
            spaceBetweenPorts += (availableSpace - calculatedPortPlacementWidth) / (nodeContext.portContexts.get(portSide).size() + 1);
            currentXPos += spaceBetweenPorts;
        } else {
            spaceBetweenPorts += (availableSpace - calculatedPortPlacementWidth) / (nodeContext.portContexts.get(portSide).size() - 1);
        }
    } else {
        // case where we should fall back to centered alignment
        if (availableSpace < calculatedPortPlacementWidth) {
            calculatedPortPlacementWidth = modifiedPortPlacementSize(nodeContext, portAlignment, calculatedPortPlacementWidth);
            portAlignment = PortAlignment.CENTER;
        }
        // otherwise expect)
        switch(portAlignment) {
            case BEGIN:
                // There's nothing to do here
                break;
            case CENTER:
                currentXPos += (availableSpace - calculatedPortPlacementWidth) / 2;
                break;
            case END:
                currentXPos += availableSpace - calculatedPortPlacementWidth;
                break;
            case DISTRIBUTED:
                // In this case, if there is not enough space available to place the ports, we are allowed to overhang.
                // We thus need to ensure that we're only ever increasing the port spacing here
                double additionalSpaceBetweenPorts = (availableSpace - calculatedPortPlacementWidth) / (nodeContext.portContexts.get(portSide).size() + 1);
                spaceBetweenPorts += Math.max(0, additionalSpaceBetweenPorts);
                currentXPos += spaceBetweenPorts;
                break;
            case JUSTIFIED:
                // In this case, if there is not enough space available to place the ports, we are allowed to overhang.
                // We thus need to ensure that we're only ever increasing the port spacing here
                additionalSpaceBetweenPorts = (availableSpace - calculatedPortPlacementWidth) / (nodeContext.portContexts.get(portSide).size() - 1);
                spaceBetweenPorts += Math.max(0, additionalSpaceBetweenPorts);
                break;
        }
    }
    // Iterate over all ports and place them
    for (PortContext portContext : nodeContext.portContexts.get(portSide)) {
        portContext.portPosition.x = currentXPos + portContext.portMargin.left;
        portContext.portPosition.y = calculateHorizontalPortYCoordinate(portContext);
        // Update the x coordinate for the next port
        currentXPos += portContext.portMargin.left + portContext.port.getSize().x + portContext.portMargin.right + spaceBetweenPorts;
    }
}
Also used : PortAlignment(org.eclipse.elk.core.options.PortAlignment) AtomicCell(org.eclipse.elk.alg.common.nodespacing.cellsystem.AtomicCell) ElkRectangle(org.eclipse.elk.core.math.ElkRectangle) ElkPadding(org.eclipse.elk.core.math.ElkPadding) PortContext(org.eclipse.elk.alg.common.nodespacing.internal.PortContext)

Example 4 with ElkRectangle

use of org.eclipse.elk.core.math.ElkRectangle in project elk by eclipse.

the class VerticalPortPlacementSizeCalculator method computeVerticalPortMargins.

/**
 * Sets the vertical margins of all ports such that they include the space necessary to place their labels.
 */
private static void computeVerticalPortMargins(final NodeContext nodeContext, final PortSide portSide, final boolean portLabelsOutside) {
    for (PortContext portContext : nodeContext.portContexts.get(portSide)) {
        double labelHeight = portContext.portLabelCell != null ? portContext.portLabelCell.getMinimumHeight() : 0;
        if (labelHeight > 0) {
            if (portContext.labelsNextToPort) {
                // The label is placed next to the port
                double portHeight = portContext.port.getSize().y;
                if (labelHeight > portHeight) {
                    if (nodeContext.portLabelsTreatAsGroup || portContext.portLabelCell.getLabels().size() == 1) {
                        // We are to center all of the labels
                        double overhang = (labelHeight - portHeight) / 2;
                        portContext.portMargin.top = overhang;
                        portContext.portMargin.bottom = overhang;
                    } else {
                        // Simulate centering the first port label
                        double firstLabelHeight = portContext.portLabelCell.getLabels().get(0).getSize().y;
                        double firstLabelOverhang = (firstLabelHeight - portHeight) / 2;
                        portContext.portMargin.top = Math.max(0, firstLabelOverhang);
                        portContext.portMargin.bottom = labelHeight - firstLabelOverhang - portHeight;
                    }
                }
            } else {
                // The label is either placed outside (below the port) or possibly inside, but for a compound node,
                // which means that it is placed below the port as well to keep it from overlapping with inside
                // edges
                portContext.portMargin.bottom = nodeContext.portLabelSpacing + labelHeight;
            }
        } else if (PortLabelPlacement.isFixed(nodeContext.portLabelsPlacement)) {
            // The fixed port label is not considered with portContext.portLabelCell. Nevertheless, a port margin
            // must be added if necessary.
            ElkRectangle labelsBounds = ElkUtil.getLabelsBounds(portContext.port);
            if (labelsBounds.y < 0) {
                // Add the part of the label that is above the port to the top margin
                portContext.portMargin.top = -labelsBounds.y;
            }
            if (labelsBounds.y + labelsBounds.height > portContext.port.getSize().y) {
                // Add the part of the label that is below the port to the bottom margin
                portContext.portMargin.bottom = labelsBounds.y + labelsBounds.height - portContext.port.getSize().y;
            }
        }
    }
}
Also used : ElkRectangle(org.eclipse.elk.core.math.ElkRectangle) PortContext(org.eclipse.elk.alg.common.nodespacing.internal.PortContext)

Example 5 with ElkRectangle

use of org.eclipse.elk.core.math.ElkRectangle in project elk by eclipse.

the class RectangleStripOverlapRemover method exportRectangle.

/**
 * Applies the movement computed for the given rectangle node's rectangle to its original rectangle, paying
 * attention to the overlap removal direction.
 *
 * @param rectangleNode
 *            the rectangle node whose results to apply to the original rectangle.
 * @param stripSize
 *            strip size computed by the overlap removal algorithm.
 */
private void exportRectangle(final RectangleNode rectangleNode, final double stripSize) {
    ElkRectangle rectangle = rectangleNode.rectangle;
    ElkRectangle originalRectangle = rectangleNode.originalRectangle;
    switch(overlapRemovalDirection) {
        case UP:
            originalRectangle.y = startCoordinate - rectangle.height - rectangle.y;
            break;
        case DOWN:
            originalRectangle.y += startCoordinate;
            break;
        case LEFT:
            originalRectangle.x = startCoordinate - rectangle.height - rectangle.y;
            break;
        case RIGHT:
            originalRectangle.x = startCoordinate + rectangle.y;
            break;
    }
}
Also used : ElkRectangle(org.eclipse.elk.core.math.ElkRectangle)

Aggregations

ElkRectangle (org.eclipse.elk.core.math.ElkRectangle)82 KVector (org.eclipse.elk.core.math.KVector)33 Test (org.junit.Test)27 Direction (org.eclipse.elk.core.options.Direction)18 CGraph (org.eclipse.elk.alg.layered.compaction.oned.CGraph)17 ElkPadding (org.eclipse.elk.core.math.ElkPadding)9 PortContext (org.eclipse.elk.alg.common.nodespacing.internal.PortContext)8 LabelCell (org.eclipse.elk.alg.common.nodespacing.cellsystem.LabelCell)7 CGroup (org.eclipse.elk.alg.layered.compaction.oned.CGroup)6 Point (org.eclipse.elk.alg.common.Point)4 LMargin (org.eclipse.elk.alg.layered.graph.LMargin)4 LPort (org.eclipse.elk.alg.layered.graph.LPort)4 ElkNode (org.eclipse.elk.graph.ElkNode)4 RectilinearConvexHull (org.eclipse.elk.alg.common.RectilinearConvexHull)3 AtomicCell (org.eclipse.elk.alg.common.nodespacing.cellsystem.AtomicCell)3 RectangleStripOverlapRemover (org.eclipse.elk.alg.common.overlaps.RectangleStripOverlapRemover)3 LEdge (org.eclipse.elk.alg.layered.graph.LEdge)3 LLabel (org.eclipse.elk.alg.layered.graph.LLabel)3 LNode (org.eclipse.elk.alg.layered.graph.LNode)3 Random (java.util.Random)2