Search in sources :

Example 1 with NodeContext

use of org.eclipse.elk.alg.common.nodespacing.internal.NodeContext in project elk by eclipse.

the class NodeLabelAndSizeCalculator method process.

/**
 * Processes the given node which is assumed to be a child of the given graph. Note that this method does not check
 * whether or not this is the case. The worst that can happen, however, is that wrong spacing values are applied
 * during processing.
 *
 * @param graph
 *            the node's parent graph.
 * @param node
 *            the node to process.
 * @param applyStuff
 *            {@code true} if the node should actually be resized and have its ports and labels positioned,
 *            {@code false} if we should only return the size that would be applied.
 * @param ignoreInsidePortLabels
 *            if {@code true}, we don't place port labels that should be placed inside. This is usually used in
 *            conjunction with {@code applyStuff} by layout algorithms that want to get a lower bound on a
 *            hierarchical node's size, but that handle inside port labels themselves.
 * @return the node's size that was or would be applied.
 */
public static KVector process(final GraphAdapter<?> graph, final NodeAdapter<?> node, final boolean applyStuff, final boolean ignoreInsidePortLabels) {
    // Note that, upon Miro's request, each phase of the algorithm was given a code name in the first version of
    // this code. We happily carry on fulfilling this request in this, the second version.
    /* PREPARATORY PREPARATIONS
         * 
         * Create the context objects that hold all of the information relevant to our calculations, including pointers
         * to all the components of the cell system. The different method calls will often just update information in
         * the context object (or nested objects) that subsequent method calls will make use of. Creating the port
         * contexts will also create label cells for each port that has labels.
         */
    NodeContext nodeContext = new NodeContext(graph, node);
    PortContextCreator.createPortContexts(nodeContext, ignoreInsidePortLabels);
    /* PHASE 1: WONDEROUS WATERFOWL
         *          Setup All Cells
         * 
         * Create all the label cells that will hold node labels, as well as the cell containers that will hold them,
         * for both inside and outside node labels. Also, assign node labels to the relevant label cells. If port
         * labels are to be placed on the inside, setup the inside port label cells with the appropriate paddings, and
         * set the width of the eastern and western ones to the maximum width of the labels they will contain. We can't
         * do that for the northern and southern cells yet because port label placement is more complicated there.
         */
    boolean horizontalLayoutMode = true;
    // use horizontal layout mode (which yields vertically stacked labels).
    if (graph != null && graph.hasProperty(CoreOptions.DIRECTION)) {
        final Direction layoutDirection = graph.getProperty(CoreOptions.DIRECTION);
        horizontalLayoutMode = layoutDirection == Direction.UNDEFINED || layoutDirection.isHorizontal();
    }
    NodeLabelCellCreator.createNodeLabelCells(nodeContext, false, horizontalLayoutMode);
    InsidePortLabelCellCreator.createInsidePortLabelCells(nodeContext);
    /* PHASE 2: DEFECTIVE DUCK
         *          Setup Client Area Space and Node Cell Padding
         * 
         * Apply the minimum client area size to the central grid container cell. Also, reserve space for ports that
         * extend inside the node due to negative port border offsets by setting up appropriate paddings on the main
         * node container cell.
         */
    NodeLabelAndSizeUtilities.setupMinimumClientAreaSize(nodeContext);
    NodeLabelAndSizeUtilities.setupNodePaddingForPortsWithOffset(nodeContext);
    /* PHASE 3: SALVAGEABLE SWAN
         *          Minimum Space Required to Place Ports
         * 
         * It is now time to find out how large the node needs to be if all ports are to be placed in a way that
         * satisfies all spacing constraints. This may or may not include the labels of ports. We remember these
         * information by setting the minimum width of north / south inside port label cells and the minimum height of
         * east / west inside port label cells. Since the east / west cells are surrounded by the north / south cells,
         * their height may be updated later once we know how high the north / south cells will be.
         */
    HorizontalPortPlacementSizeCalculator.calculateHorizontalPortPlacementSize(nodeContext);
    VerticalPortPlacementSizeCalculator.calculateVerticalPortPlacementSize(nodeContext);
    /* PHASE 4: DAMNABLE DUCKLING
         *          Setup Cell System Size Contribution Flags
         * 
         * Depending on the size constraints, the different cells may contribute to the height or to the width of the
         * node. In this phase, we setup the size contribution flags according to the size constraints. This lays the
         * groundwork for letting the cell system calculate stuff.
         */
    CellSystemConfigurator.configureCellSystemSizeContributions(nodeContext);
    /* PHASE 5: DUCK AND COVER
         *          Set Node Width and Place Horizontal Ports
         * 
         * We can now set the node's width and place the ports (and port labels) along the horizontal sides. Since we
         * have no idea how high the node is going to be yet, we place southern ports with the assumption that the
         * node has a height of 0. We will later have to offset those ports by the node's height. Setting the node
         * width has the side effect of computing a horizontal layout for the cell system.
         */
    NodeSizeCalculator.setNodeWidth(nodeContext);
    PortPlacementCalculator.placeHorizontalPorts(nodeContext);
    PortLabelPlacementCalculator.placeHorizontalPortLabels(nodeContext);
    /* PHASE 6: GIGANTIC GOOSE
         *          Set Node Height and Place Vertical Ports
         * 
         * We can now calculate the node's height and place the ports (and port labels) along the vertical sides. Also,
         * since we now know the node's height, we can finally correct the southern port positions. Before we can do
         * all that, however, we might need to update the height and padding of the eastern and western inside port
         * label cells to be sure that free ports are positioned properly.
         * 
         * Note that if we are to not apply stuff, we're done once we know the node's height. Which is why we stop at
         * that point.
         */
    CellSystemConfigurator.updateVerticalInsidePortLabelCellPadding(nodeContext);
    NodeSizeCalculator.setNodeHeight(nodeContext);
    if (!applyStuff) {
        return nodeContext.nodeSize;
    }
    NodeLabelAndSizeUtilities.offsetSouthernPortsByNodeSize(nodeContext);
    PortPlacementCalculator.placeVerticalPorts(nodeContext);
    PortLabelPlacementCalculator.placeVerticalPortLabels(nodeContext);
    /* PHASE 7: THANKSGIVING
         *          Place Labels and Apply Stuff
         * 
         * Since we now have the node's final size, we can now calculate the positions of the containers for outer node
         * labels and place all inner and outer node labels. Also, the port label cells have positions assigned to them
         * and can be told to apply positions to their labels. Finally, we can apply the node's size and all of the
         * port positions we have calculated.
         */
    LabelPlacer.placeLabels(nodeContext);
    NodeLabelAndSizeUtilities.setNodePadding(nodeContext);
    NodeLabelAndSizeUtilities.applyStuff(nodeContext);
    // Return the size
    return nodeContext.nodeSize;
}
Also used : NodeContext(org.eclipse.elk.alg.common.nodespacing.internal.NodeContext) Direction(org.eclipse.elk.core.options.Direction)

Example 2 with NodeContext

use of org.eclipse.elk.alg.common.nodespacing.internal.NodeContext in project elk by eclipse.

the class NodeLabelAndSizeCalculator method computeInsideNodeLabelPadding.

/**
 * Computes the padding required to place inside non-center node labels. This can be used to reserve space around
 * a hierarchical node while laying out its content. The layout direction is interesting here: usually, one would
 * probably either use the graph's layout direction, or the layout direction used to lay out the node's children.
 * However, the way the node's labels are placed may not have anything to do with either direction.
 *
 * @param graph
 *            the node's parent graph which may specify spacings inherited by the node.
 * @param node
 *            the node to process.
 * @param layoutDirection
 *            the layout direction to assume when computing the paddings.
 * @return the padding required for inside node labels.
 */
public static ElkPadding computeInsideNodeLabelPadding(final GraphAdapter<?> graph, final NodeAdapter<?> node, final Direction layoutDirection) {
    // Create a node context and fill it with all the inside node labels
    NodeContext nodeContext = new NodeContext(null, node);
    NodeLabelCellCreator.createNodeLabelCells(nodeContext, true, !layoutDirection.isVertical());
    GridContainerCell labelCellContainer = nodeContext.insideNodeLabelContainer;
    ElkPadding padding = new ElkPadding();
    // Top
    for (ContainerArea col : ContainerArea.values()) {
        Cell labelCell = labelCellContainer.getCell(ContainerArea.BEGIN, col);
        if (labelCell != null) {
            padding.top = Math.max(padding.top, labelCell.getMinimumHeight());
        }
    }
    // Bottom
    for (ContainerArea col : ContainerArea.values()) {
        Cell labelCell = labelCellContainer.getCell(ContainerArea.END, col);
        if (labelCell != null) {
            padding.bottom = Math.max(padding.bottom, labelCell.getMinimumHeight());
        }
    }
    // Left
    for (ContainerArea row : ContainerArea.values()) {
        Cell labelCell = labelCellContainer.getCell(row, ContainerArea.BEGIN);
        if (labelCell != null) {
            padding.left = Math.max(padding.left, labelCell.getMinimumWidth());
        }
    }
    // Right
    for (ContainerArea row : ContainerArea.values()) {
        Cell labelCell = labelCellContainer.getCell(row, ContainerArea.END);
        if (labelCell != null) {
            padding.right = Math.max(padding.right, labelCell.getMinimumWidth());
        }
    }
    // Apply insets and gap where necessary
    if (padding.top > 0) {
        padding.top += labelCellContainer.getPadding().top;
        padding.top += labelCellContainer.getGap();
    }
    if (padding.bottom > 0) {
        padding.bottom += labelCellContainer.getPadding().bottom;
        padding.bottom += labelCellContainer.getGap();
    }
    if (padding.left > 0) {
        padding.left += labelCellContainer.getPadding().left;
        padding.left += labelCellContainer.getGap();
    }
    if (padding.right > 0) {
        padding.right += labelCellContainer.getPadding().right;
        padding.right += labelCellContainer.getGap();
    }
    return padding;
}
Also used : NodeContext(org.eclipse.elk.alg.common.nodespacing.internal.NodeContext) GridContainerCell(org.eclipse.elk.alg.common.nodespacing.cellsystem.GridContainerCell) ElkPadding(org.eclipse.elk.core.math.ElkPadding) ContainerArea(org.eclipse.elk.alg.common.nodespacing.cellsystem.ContainerArea) GridContainerCell(org.eclipse.elk.alg.common.nodespacing.cellsystem.GridContainerCell) Cell(org.eclipse.elk.alg.common.nodespacing.cellsystem.Cell)

Aggregations

NodeContext (org.eclipse.elk.alg.common.nodespacing.internal.NodeContext)2 Cell (org.eclipse.elk.alg.common.nodespacing.cellsystem.Cell)1 ContainerArea (org.eclipse.elk.alg.common.nodespacing.cellsystem.ContainerArea)1 GridContainerCell (org.eclipse.elk.alg.common.nodespacing.cellsystem.GridContainerCell)1 ElkPadding (org.eclipse.elk.core.math.ElkPadding)1 Direction (org.eclipse.elk.core.options.Direction)1