Java tutorial
/* * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * * http://www.informatik.uni-kiel.de/rtsys/kieler/ * * Copyright 2014 by * + Christian-Albrechts-University of Kiel * + Department of Computer Science * + Real-Time and Embedded Systems Group * * This code is provided under the terms of the Eclipse Public License (EPL). * See the file epl-v10.html for the license text. */ package de.cau.cs.kieler.kiml.util.nodespacing; import java.util.*; import java.util.Map.Entry; import com.google.common.collect.ImmutableList; import de.cau.cs.kieler.core.math.KVector; import de.cau.cs.kieler.core.properties.IProperty; import de.cau.cs.kieler.core.properties.Property; import de.cau.cs.kieler.kiml.options.*; import de.cau.cs.kieler.kiml.util.adapters.GraphAdapters.GraphAdapter; import de.cau.cs.kieler.kiml.util.adapters.GraphAdapters.LabelAdapter; import de.cau.cs.kieler.kiml.util.adapters.GraphAdapters.NodeAdapter; import de.cau.cs.kieler.kiml.util.adapters.GraphAdapters.PortAdapter; import de.cau.cs.kieler.kiml.util.labelspacing.*; import de.cau.cs.kieler.kiml.util.nodespacing.Spacing.Insets; import de.cau.cs.kieler.kiml.util.nodespacing.Spacing.Margins; import de.cau.cs.kieler.klay.layered.intermediate.LabelSideSelector; /** * Calculates node sizes, places ports, and places node and port labels. * * @see LabelSideSelector * @author cds * @author uru * @author csp */ @SuppressWarnings("incomplete-switch") public class LabelAndNodeSizeProcessor { /** * Copy of the * {@link de.cau.cs.kieler.klay.layered.properties.InternalProperties#PORT_RATIO_OR_POSITION} * option. For further information see the documentation found there. We * added this copy here to allow a generic treatment of spacing calculations * for graph elements. See the * {@link de.cau.cs.kieler.kiml.util.nodespacing} package. [programmatically * set] */ public static final IProperty<Double> PORT_RATIO_OR_POSITION = new Property<Double>("portRatioOrPosition", 0.0); /* * Entry point */ /** * {@inheritDoc} */ public void process(final GraphAdapter<?> layeredGraph) { final double labelSpacing = layeredGraph.getProperty(LayoutOptions.LABEL_SPACING); // Iterate over all the graph's nodes for (final NodeAdapter<?> node : layeredGraph.getNodes()) { /* * Note that, upon Miro's request, each phase of the algorithm was * given a code name. */ /* * PREPARATIONS: Create new NodeData containing all relevant context * information. */ final NodeData data = new NodeData(node); data.labelSpacing = labelSpacing; data.portSpacing = node.getProperty(LayoutOptions.PORT_SPACING); /* * PHASE 1 (SAD DUCK): PLACE PORT LABELS Port labels are placed and * port margins are calculated. */ final PortLabelPlacement labelPlacement = node.getProperty(LayoutOptions.PORT_LABEL_PLACEMENT); final boolean compoundNodeMode = node.isCompoundNode(); // Place port labels and calculate the margins for (final PortAdapter<?> port : node.getPorts()) { placePortLabels(port, labelPlacement, compoundNodeMode, labelSpacing); calculateAndSetPortMargins(port); } // Count ports on each side and calculate how much space they // require calculatePortInformation(data, node.getProperty(LayoutOptions.SIZE_CONSTRAINT).contains(SizeConstraint.PORT_LABELS)); /* * PHASE 2 (DYNAMIC DONALD): CALCULATE INSETS We know the sides the * ports will be placed at and we know where node labels are to be * placed. Calculate the node's insets accordingly. Also compute the * amount of space the node labels will need if stacked vertically. * Note that we don't have to know the final position of ports and * of node labels to calculate all this stuff. * * IMPORTANT NOTE: From this point on, the labels' ID fields are * used to assign the location of the labels. */ calculateRequiredPortLabelSpace(data); calculateRequiredNodeLabelSpace(data); /* * PHASE 3 (DANGEROUS DUCKLING): RESIZE NODE If the node has labels, * the node insets might have to be adjusted to reserve space for * them, which is what this phase does. */ resizeNode(data); /* * PHASE 4 (DUCK AND COVER): PLACE PORTS The node is resized, taking * all node size constraints into account. The port spacing is not * required for port placement since the placement will be based on * the node's size (if it is not fixed anyway). */ placePorts(data); /* * PHASE 5 (HAPPY DUCK): PLACE NODE LABELS With space reserved for * the node labels, the labels are placed. */ placeNodeLabels(data); /* * CLEANUP (THANKSGIVING): SET NODE INSETS Set the node insets to * include space required for port and node labels. If the labels * were not taken into account when calculating the node's size, * this may result in insets that, taken together, are larger than * the node's actual size. */ final Insets nodeInsets = new Insets(node.getInsets()); nodeInsets.left = data.requiredNodeLabelSpace.left + data.requiredPortLabelSpace.left; nodeInsets.right = data.requiredNodeLabelSpace.right + data.requiredPortLabelSpace.right; nodeInsets.top = data.requiredNodeLabelSpace.top + data.requiredPortLabelSpace.top; nodeInsets.bottom = data.requiredNodeLabelSpace.bottom + data.requiredPortLabelSpace.bottom; node.setInsets(nodeInsets); } } // ///////////////////////////////////////////////////////////////////////////// // PORT LABEL PLACEMENT /** * Places the labels of the given port, if any. * * @param port * the port whose labels to place. * @param placement * the port label placement that applies to the port. * @param compoundNodeMode * {@code true} if the node contains further nodes in the * original graph. This influences the inner port label * placement. * @param labelSpacing * spacing between labels and other objects. */ private void placePortLabels(final PortAdapter<?> port, final PortLabelPlacement placement, final boolean compoundNodeMode, final double labelSpacing) { if (placement.equals(PortLabelPlacement.INSIDE)) { placePortLabelsInside(port, compoundNodeMode, labelSpacing); } else if (placement.equals(PortLabelPlacement.OUTSIDE)) { placePortLabelsOutside(port, labelSpacing); } } /** * Places the labels of the given port on the inside of the port's node. * * @param port * the port whose labels to place. * @param compoundNodeMode * {@code true} if the node contains further nodes in the * original graph. In this case, port labels are not placed next * to ports, but a little down as well to avoid * edge-label-crossings if the port has edges connected to the * node's insides. * @param labelSpacing * spacing between labels and other objects. */ private void placePortLabelsInside(final PortAdapter<?> port, final boolean compoundNodeMode, final double labelSpacing) { ImmutableList<LabelAdapter<?>> labels = ImmutableList.copyOf(port.getLabels()); if (labels.isEmpty()) { return; } // The initial y position we'll be starting from depends on the port // side double y = 0; switch (port.getSide()) { case WEST: case EAST: // We need the first label's size here and we know that there is // at least one label y = compoundNodeMode && port.hasCompoundConnections() ? port.getSize().y : ((port.getSize().y - labels.get(0).getSize().y) / 2.0) - labelSpacing; break; case NORTH: y = port.getSize().y; break; case SOUTH: y = 0.0; break; } // In the usual case, we simply start at a given y position and place // the labels downwards. // For southern ports, however, we actually need to start with the last // label and place them // upwards. We thus first add all labels to a list that we may need to // reverse if (port.getSide() == PortSide.SOUTH) { labels = labels.reverse(); } // Place da labels! for (final LabelAdapter<?> label : port.getLabels()) { final KVector position = new KVector(port.getPosition()); switch (port.getSide()) { case WEST: position.x = port.getSize().x + labelSpacing; position.y = y + labelSpacing; y += labelSpacing + label.getSize().y; break; case EAST: position.x = -label.getSize().x - labelSpacing; position.y = y + labelSpacing; y += labelSpacing + label.getSize().y; break; case NORTH: position.x = (port.getSize().x - label.getSize().x) / 2; position.y = y + labelSpacing; y += labelSpacing + label.getSize().y; break; case SOUTH: position.x = (port.getSize().x - label.getSize().x) / 2; position.y = y - labelSpacing - label.getSize().y; y -= labelSpacing + label.getSize().y; break; } label.setPosition(position); } } /** * Places the labels of the given port on the outside of the port's node. We * suppose that the first label has label side information. Those are then * used for all labels. We don't support having some labels above and others * below incident edges. * * @param port * the port whose label to place. * @param labelSpacing * spacing between labels and other objects. */ private void placePortLabelsOutside(final PortAdapter<?> port, final double labelSpacing) { ImmutableList<LabelAdapter<?>> labels = ImmutableList.copyOf(port.getLabels()); if (labels.isEmpty()) { return; } // Retrieve the first label's side LabelSide labelSide = labels.get(0).getSide(); // Default is BELOW. labelSide = labelSide == LabelSide.UNKNOWN ? LabelSide.BELOW : labelSide; // The initial y position we'll be starting from depends on port and // label sides double y = 0; switch (port.getSide()) { case WEST: case EAST: if (labelSide == LabelSide.BELOW) { y = port.getSize().y; } break; case SOUTH: y = port.getSize().y; break; } // If labels are below incident edges, we simply start at a given y // position and place the // labels downwards. Of they are placed above or if we have a northern // port, however, we // actually need to start with the last label and place them upwards. We // thus first add all // labels to a list that we may need to reverse if ((port.getSide() == PortSide.NORTH) || (labelSide == LabelSide.ABOVE)) { labels = labels.reverse(); } for (final LabelAdapter<?> label : labels) { final KVector position = new KVector(label.getPosition()); if (labelSide == LabelSide.ABOVE) { // Place label "above" edges switch (port.getSide()) { case WEST: position.x = -label.getSize().x - labelSpacing; position.y = y - labelSpacing - label.getSize().y; y -= labelSpacing + label.getSize().y; break; case EAST: position.x = port.getSize().x + labelSpacing; position.y = y - labelSpacing - label.getSize().y; y -= labelSpacing + label.getSize().y; break; case NORTH: position.x = -label.getSize().x - labelSpacing; position.y = y - labelSpacing - label.getSize().y; y -= labelSpacing + label.getSize().y; break; case SOUTH: position.x = -label.getSize().x - labelSpacing; position.y = y + labelSpacing; y += labelSpacing + label.getSize().y; break; } } else { // Place label "below" edges switch (port.getSide()) { case WEST: position.x = -label.getSize().x - labelSpacing; position.y = y + labelSpacing; y += labelSpacing + label.getSize().y; break; case EAST: position.x = port.getSize().x + labelSpacing; position.y = y + labelSpacing; y += labelSpacing + label.getSize().y; break; case NORTH: position.x = port.getSize().x + labelSpacing; position.y = y - labelSpacing - label.getSize().y; y -= labelSpacing + label.getSize().y; break; case SOUTH: position.x = port.getSize().x + labelSpacing; position.y = y + labelSpacing; y += labelSpacing + label.getSize().y; break; } } label.setPosition(position); } } /** * Calculates the port's margins such that its labels are part of them and * sets them accordingly. * * @param port * the port whose margins to calculate. */ private void calculateAndSetPortMargins(final PortAdapter<?> port) { // Get the port's labels, if any final Iterable<LabelAdapter<?>> labels = port.getLabels(); if (labels.iterator().hasNext()) { final Rectangle portBox = new Rectangle(0.0, 0.0, port.getSize().x, port.getSize().y); // Add all labels to the port's bounding box for (final LabelAdapter<?> label : labels) { final Rectangle labelBox = new Rectangle(label.getPosition().x, label.getPosition().y, label.getSize().x, label.getSize().y); // Calculate the union of the two bounding boxes and calculate // the margins portBox.union(labelBox); } final Margins margin = new Margins(port.getMargin()); margin.top = -portBox.y; margin.bottom = (portBox.y + portBox.height) - port.getSize().y; margin.left = -portBox.x; margin.right = (portBox.x + portBox.width) - port.getSize().x; port.setMargin(margin); } } /** * Calculates the width of ports on the northern and southern sides, and the * height of ports on the western and eastern sides of the given node. The * information are stored in the class fields and are used later on when * calculating the minimum node size and when placing ports. * * @param data * the data containing the node to calculate the port information * for. * @param accountForLabels * if {@code true}, the port labels will be taken into account * when calculating the port information. */ private void calculatePortInformation(final NodeData data, final boolean accountForLabels) { // Check if there are any ports. if (!data.node.getPorts().iterator().hasNext()) { return; } // Iterate over the ports for (final PortAdapter<?> port : data.node.getPorts()) { final int side = port.getSide().ordinal(); data.portsCount[side]++; switch (port.getSide()) { case WEST: case EAST: data.portUsedSpace[side] += port.getSize().y + (accountForLabels ? port.getMargin().bottom + port.getMargin().top : 0.0); break; case NORTH: case SOUTH: data.portUsedSpace[side] += port.getSize().x + (accountForLabels ? port.getMargin().left + port.getMargin().right : 0.0); break; } } // Get the port distribution from the node. PortAlignment portAlignment = data.node.getProperty(LayoutOptions.PORT_ALIGNMENT); // Use JUSTIFIED as default. portAlignment = portAlignment == PortAlignment.UNDEFINED ? PortAlignment.JUSTIFIED : portAlignment; // For each side get the port distribution. If it's UNDEFINED, replace // it with the nodes policy. data.portAlignment[PortSide.NORTH.ordinal()] = data.node.getProperty(LayoutOptions.PORT_ALIGNMENT_NORTH); data.portAlignment[PortSide.SOUTH.ordinal()] = data.node.getProperty(LayoutOptions.PORT_ALIGNMENT_SOUTH); data.portAlignment[PortSide.WEST.ordinal()] = data.node.getProperty(LayoutOptions.PORT_ALIGNMENT_WEST); data.portAlignment[PortSide.EAST.ordinal()] = data.node.getProperty(LayoutOptions.PORT_ALIGNMENT_EAST); for (final PortSide side : PortSide.values()) { data.portAlignment[side.ordinal()] = data.portAlignment[side.ordinal()] == PortAlignment.UNDEFINED ? portAlignment : data.portAlignment[side.ordinal()]; } data.hasAdditionalPortSpace = data.node.getProperty(LayoutOptions.ADDITIONAL_PORT_SPACE) != null; // Calculate how many gaps we have between ports: // single port --> 2 gaps // additionalPortSpace unset and alignment == JUSTIFIED --> portsCount + // 1 gaps // otherwise --> portsCount - 1 gaps for (final PortSide side : PortSide.values()) { if (data.portsCount[side.ordinal()] == 1) { data.portGapsCount[side.ordinal()] = 2; } else if (!data.hasAdditionalPortSpace && (data.portAlignment[side.ordinal()] == PortAlignment.JUSTIFIED)) { data.portGapsCount[side.ordinal()] = data.portsCount[side.ordinal()] + 1; } else { data.portGapsCount[side.ordinal()] = data.portsCount[side.ordinal()] - 1; } } } // ///////////////////////////////////////////////////////////////////////////// // INSETS CALCULATION /** * Calculates the space required to accommodate all port labels and sets * {@link #requiredPortLabelSpace}. Also counts the number of ports on each * side of the node. * * <p> * <i>Note:</i> We currently only support one label per port. * </p> * * @param data * the data containing the node whose insets to calculate and to * set. */ private void calculateRequiredPortLabelSpace(final NodeData data) { // Iterate over the ports and look at their margins for (final PortAdapter<?> port : data.node.getPorts()) { switch (port.getSide()) { case WEST: data.requiredPortLabelSpace.left = Math.max(data.requiredPortLabelSpace.left, port.getMargin().right); break; case EAST: data.requiredPortLabelSpace.right = Math.max(data.requiredPortLabelSpace.right, port.getMargin().left); break; case NORTH: data.requiredPortLabelSpace.top = Math.max(data.requiredPortLabelSpace.top, port.getMargin().bottom); break; case SOUTH: data.requiredPortLabelSpace.bottom = Math.max(data.requiredPortLabelSpace.bottom, port.getMargin().top); break; } } } // ///////////////////////////////////////////////////////////////////////////// // NODE RESIZING /** * Resizes the given node subject to the sizing constraints and options. * * @param data * the data containing the node to resize. */ private void resizeNode(final NodeData data) { final KVector nodeSize = data.node.getSize(); final KVector originalNodeSize = new KVector(nodeSize); final EnumSet<SizeConstraint> sizeConstraint = data.node.getProperty(LayoutOptions.SIZE_CONSTRAINT); final EnumSet<SizeOptions> sizeOptions = data.node.getProperty(LayoutOptions.SIZE_OPTIONS); final PortConstraints portConstraints = data.node.getProperty(LayoutOptions.PORT_CONSTRAINTS); final boolean accountForLabels = sizeConstraint.contains(SizeConstraint.PORT_LABELS); // If the size constraint is empty, we can't do anything if (sizeConstraint.isEmpty()) { return; } // It's not empty, so we will change the node size; we start by // resetting the size to zero nodeSize.x = 0.0; nodeSize.y = 0.0; // Find out how large the node will have to be to accommodate all ports. // If port // constraints are set to FIXED_RATIO, we can't do anything smart, // really; in this // case we will just assume the original node size to be the minimum for // ports KVector minSizeForPorts = null; switch (portConstraints) { case FREE: case FIXED_SIDE: case FIXED_ORDER: // Calculate the space necessary to accommodate all ports minSizeForPorts = calculatePortSpaceRequirements(data, data.portSpacing, accountForLabels); break; case FIXED_RATIO: // Keep original node size minSizeForPorts = new KVector(originalNodeSize); break; case FIXED_POS: // Find the maximum position of ports minSizeForPorts = calculateMinNodeSizeForFixedPorts(data.node, accountForLabels); break; } // If the node size should take port space requirements into account, // adjust it accordingly if (sizeConstraint.contains(SizeConstraint.PORTS)) { // Check if we have a minimum size required for all ports if (minSizeForPorts != null) { nodeSize.x = Math.max(nodeSize.x, minSizeForPorts.x); nodeSize.y = Math.max(nodeSize.y, minSizeForPorts.y); } // If we account for labels, we need to have the size account for // labels placed on the // inside of the node (this only affects port label placement // INSIDE; OUTSIDE is already // part of minSizeForPorts) if (accountForLabels) { nodeSize.x = Math.max(nodeSize.x, data.requiredPortLabelSpace.left + data.requiredPortLabelSpace.right + data.portSpacing); nodeSize.y = Math.max(nodeSize.y, data.requiredPortLabelSpace.top + data.requiredPortLabelSpace.bottom + data.portSpacing); } } // If the node label is to be accounted for, add its required space to // the node size if (sizeConstraint.contains(SizeConstraint.NODE_LABELS) && data.node.getLabels().iterator().hasNext()) { enlargeNodeSizeForLabels(data, data.labelSpacing, nodeSize); } // Respect minimum size if (sizeConstraint.contains(SizeConstraint.MINIMUM_SIZE)) { double minWidth = data.node.getProperty(LayoutOptions.MIN_WIDTH); double minHeight = data.node.getProperty(LayoutOptions.MIN_HEIGHT); // If we are to use default minima, check if the values are properly // set if (sizeOptions.contains(SizeOptions.DEFAULT_MINIMUM_SIZE)) { if (minWidth <= 0) { minWidth = 20; } if (minHeight <= 0) { minHeight = 20; } } // We might have to take the insets into account if (sizeOptions.contains(SizeOptions.MINIMUM_SIZE_ACCOUNTS_FOR_INSETS)) { if (minWidth > 0) { nodeSize.x = Math.max(nodeSize.x, minWidth + data.requiredPortLabelSpace.left + data.requiredPortLabelSpace.right); } if (minHeight > 0) { nodeSize.y = Math.max(nodeSize.y, minHeight + data.requiredPortLabelSpace.top + data.requiredPortLabelSpace.bottom); } } else { if (minWidth > 0) { nodeSize.x = Math.max(nodeSize.x, minWidth); } if (minHeight > 0) { nodeSize.y = Math.max(nodeSize.y, minHeight); } } } // apply the calculated node size back to the wrapped node data.node.setSize(nodeSize); } /** * Enlarges the node size to the required label space. * <p> * For outside labels, the minimal {width|height} results from the maximal * sum of the 3 {top|bottom|left|right} label groups' {width|height}. * </p> * <p> * For inside labels, the minimal height results from the sum of * <ul> * <li>top inset,</li> * <li>maximum of height of 3 vertical centered label groups,</li> * <li>bottom inset.</li> * </ul> * </p> * <p> * The minimal inside width results from the maximal sum of the 3 vertical * {top|centered|bottom} label groups' width. * </p> * * @param data * the data containing the node to resize. * @param labelSpacing * the amount of space to leave between labels and other objects. * @param nodeSize * the node's size to adjust. */ private void enlargeNodeSizeForLabels(final NodeData data, final double labelSpacing, final KVector nodeSize) { double sumHeightOusideLeft = 0; // sum of heights of the 3 outside left // label groups double sumHeightOusideRight = 0; // sum of heights of the 3 outside // right label groups double sumWidthOutsideTop = 0; // sum of widths of the 3 outside top // label groups double sumWidthOutsideBottom = 0; // sum of widths of the 3 outside // bottom label groups double maxHeightInsideCenter = 0; // max of heights of the 3 inside // vertical center label groups double sumWidthInsideTop = 0; // sum of widths of the 3 inside vertical // top label groups double sumWidthInsideCenter = 0; // sum of widths of the 3 inside // vertical center label groups double sumWidthInsideBottom = 0; // sum of widths of the 3 inside // vertical bottom label groups for (final Entry<LabelLocation, LabelGroup> entry : data.labelGroupsBoundingBoxes.entrySet()) { final Rectangle boundingBox = entry.getValue(); switch (entry.getKey()) { // Inside groups case IN_T_L: case IN_T_C: case IN_T_R: sumWidthInsideTop += boundingBox.width + labelSpacing; break; case IN_C_L: case IN_C_C: case IN_C_R: sumWidthInsideCenter += boundingBox.width + labelSpacing; maxHeightInsideCenter = Math.max(maxHeightInsideCenter, boundingBox.height + labelSpacing); break; case IN_B_L: case IN_B_C: case IN_B_R: sumWidthInsideBottom += boundingBox.width + labelSpacing; break; // Outside groups // Top 3 label groups case OUT_T_L: case OUT_T_C: case OUT_T_R: sumWidthOutsideTop += boundingBox.width + labelSpacing; break; // Bottom 3 label groups case OUT_B_L: case OUT_B_C: case OUT_B_R: sumWidthOutsideBottom += boundingBox.width + labelSpacing; break; // Left 3 label groups case OUT_L_T: case OUT_L_C: case OUT_L_B: sumHeightOusideLeft += boundingBox.height + labelSpacing; break; // Right 3 label groups case OUT_R_T: case OUT_R_C: case OUT_R_B: sumHeightOusideRight += boundingBox.height + labelSpacing; break; } } // remove additionally added label spacing // (possible negative values doesn't affect the outcome of the max // function below) sumHeightOusideLeft -= labelSpacing; sumHeightOusideRight -= labelSpacing; sumWidthOutsideTop -= labelSpacing; sumWidthOutsideBottom -= labelSpacing; // add missing label spacing (only if not zero) sumWidthInsideTop += sumWidthInsideTop != 0 ? labelSpacing : 0; sumWidthInsideCenter += sumWidthInsideCenter != 0 ? labelSpacing : 0; sumWidthInsideBottom += sumWidthInsideBottom != 0 ? labelSpacing : 0; double minHeightInside = data.requiredNodeLabelSpace.top + maxHeightInsideCenter + data.requiredNodeLabelSpace.bottom; minHeightInside += minHeightInside != 0 ? labelSpacing : 0; nodeSize.x = Math.max(nodeSize.x, sumWidthOutsideTop); nodeSize.x = Math.max(nodeSize.x, sumWidthInsideTop); nodeSize.x = Math.max(nodeSize.x, sumWidthInsideCenter); nodeSize.x = Math.max(nodeSize.x, sumWidthInsideBottom); nodeSize.x = Math.max(nodeSize.x, sumWidthOutsideBottom); nodeSize.y = Math.max(nodeSize.y, sumHeightOusideLeft); nodeSize.y = Math.max(nodeSize.y, minHeightInside); nodeSize.y = Math.max(nodeSize.y, sumHeightOusideRight); } /** * Calculate how much space the ports will require at a minimum if they can * be freely distributed on their respective node side. The x coordinate of * the returned vector will be the minimum width required by the ports on * the northern and southern side. The y coordinate, in turn, will be the * minimum height required by the ports on the western and eastern side. * This may include the space required for port labels placed outside of the * node. If port labels are placed inside, their space requirements are not * included in the result. * * @param data * the data containing the node to calculate the minimum size * for. * @param portSpacing * the minimum amount of space to be left between ports if there * positions are not fixed. * @param accountForLabels * if {@code true}, the port labels will be taken into account * when calculating the space requirements. * @return minimum size. */ private KVector calculatePortSpaceRequirements(final NodeData data, final double portSpacing, final boolean accountForLabels) { // Calculate the additional port space to be left around the set of // ports on each side. If // this is not set, we assume the spacing to be the minimum space left // between ports double additionalWidth; double additionalHeight; if (data.hasAdditionalPortSpace) { final Margins additionalPortSpace = data.node.getProperty(LayoutOptions.ADDITIONAL_PORT_SPACE); additionalWidth = additionalPortSpace.left + additionalPortSpace.right; additionalHeight = additionalPortSpace.top + additionalPortSpace.bottom; } else { additionalWidth = portSpacing * 2; additionalHeight = portSpacing * 2; } // Calculate the required width and height, taking the necessary spacing // between (and // around) the ports into consideration as well final double requiredWidth = Math.max(data.portsCount[PortSide.NORTH.ordinal()] > 0 ? additionalWidth + (data.portGapsCount[PortSide.NORTH.ordinal()] * portSpacing) + data.portUsedSpace[PortSide.NORTH.ordinal()] : 0.0, data.portsCount[PortSide.SOUTH.ordinal()] > 0 ? additionalWidth + (data.portGapsCount[PortSide.SOUTH.ordinal()] * portSpacing) + data.portUsedSpace[PortSide.SOUTH.ordinal()] : 0.0); final double requiredHeight = Math.max(data.portsCount[PortSide.WEST.ordinal()] > 0 ? additionalHeight + (data.portGapsCount[PortSide.WEST.ordinal()] * portSpacing) + data.portUsedSpace[PortSide.WEST.ordinal()] : 0.0, data.portsCount[PortSide.EAST.ordinal()] > 0 ? additionalHeight + (data.portGapsCount[PortSide.EAST.ordinal()] * portSpacing) + data.portUsedSpace[PortSide.EAST.ordinal()] : 0.0); return new KVector(requiredWidth, requiredHeight); } /** * For fixed port positions, returns the minimum size of the node to contain * all ports. The minimum size equals the position plus the size of the most * bottom-right port (biggest x- and y-coordinate) * * @param node * the node to calculate the minimum size for. * @param accountForLabels * whether to regard port labels. * @return the minimum size to contain all port positions. */ private KVector calculateMinNodeSizeForFixedPorts(final NodeAdapter<?> node, final boolean accountForLabels) { // Port positions must be fixed for this method to be called assert node.getProperty(LayoutOptions.PORT_CONSTRAINTS) == PortConstraints.FIXED_POS; final KVector result = new KVector(); // Iterate over the ports for (final PortAdapter<?> port : node.getPorts()) { switch (port.getSide()) { case WEST: case EAST: result.y = Math.max(result.y, port.getPosition().y + port.getSize().y + (accountForLabels ? port.getMargin().bottom : 0.0)); break; case NORTH: case SOUTH: result.x = Math.max(result.x, port.getPosition().x + port.getSize().x + (accountForLabels ? port.getMargin().right : 0.0)); break; } } return result; } // ///////////////////////////////////////////////////////////////////////////// // PORT PLACEMENT /** * Places the given node's ports. If the node wasn't resized at all and port * constraints are set to either {@link PortConstraints#FIXED_RATIO} or * {@link PortConstraints#FIXED_POS}, the port positions are not touched. * * @param data * the data containing the node whose ports to place. */ private void placePorts(final NodeData data) { // Check if there are any ports. if (!data.node.getPorts().iterator().hasNext()) { return; } final PortConstraints portConstraints = data.node.getProperty(LayoutOptions.PORT_CONSTRAINTS); if (portConstraints == PortConstraints.FIXED_POS) { // Fixed Position placeFixedPosNodePorts(data.node); } else if (portConstraints == PortConstraints.FIXED_RATIO) { // Fixed Ratio placeFixedRatioNodePorts(data.node); } else { // Free, Fixed Side, Fixed Order if (data.node.getProperty(LayoutOptions.HYPERNODE) || ((data.node.getSize().x == 0) && (data.node.getSize().y == 0))) { placeHypernodePorts(data.node); } else { placeNodePorts(data); } } } /** * Places the ports of a node assuming that the port constraints are set to * fixed port positions. Ports still need to be placed, though, because the * node may have been resized. * * @param node * the node whose ports to place. */ private void placeFixedPosNodePorts(final NodeAdapter<?> node) { final KVector nodeSize = node.getSize(); for (final PortAdapter<?> port : node.getPorts()) { Float portOffset = port.getProperty(LayoutOptions.OFFSET); if (portOffset == null) { portOffset = 0f; } final KVector position = new KVector(port.getPosition()); switch (port.getSide()) { case WEST: position.x = -port.getSize().x - portOffset; break; case EAST: position.x = nodeSize.x + portOffset; break; case NORTH: position.y = -port.getSize().y - portOffset; break; case SOUTH: position.y = nodeSize.y + portOffset; break; } port.setPosition(position); } } /** * Places the ports of a node keeping the ratio between their position and * the length of their respective side intact. * * @param node * the node whose ports to place. */ private void placeFixedRatioNodePorts(final NodeAdapter<?> node) { final KVector nodeSize = node.getSize(); // Adjust port positions depending on port side. Eastern ports have to // have their x // coordinate set to the node's current width; the same goes for the y // coordinate of // southern ports for (final PortAdapter<?> port : node.getPorts()) { Float portOffset = port.getProperty(LayoutOptions.OFFSET); if (portOffset == null) { portOffset = 0f; } switch (port.getSide()) { case WEST: port.getPosition().y = nodeSize.y * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().x = -port.getSize().x - portOffset; break; case EAST: port.getPosition().y = nodeSize.y * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().x = nodeSize.x + portOffset; break; case NORTH: port.getPosition().x = nodeSize.x * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().y = -port.getSize().y - portOffset; break; case SOUTH: port.getPosition().x = nodeSize.x * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().y = nodeSize.y + portOffset; break; } switch (port.getSide()) { case WEST: port.getPosition().y = nodeSize.y * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().x = -port.getSize().x - portOffset; break; case EAST: port.getPosition().y = nodeSize.y * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().x = nodeSize.x + portOffset; break; case NORTH: port.getPosition().x = nodeSize.x * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().y = -port.getSize().y - portOffset; break; case SOUTH: port.getPosition().x = nodeSize.x * port.getProperty(PORT_RATIO_OR_POSITION); port.getPosition().y = nodeSize.y + portOffset; break; } } } /** * Places the ports of a node, assuming that the ports are not fixed in * their position or ratio. * * @param data * the data containing the node whose ports to place. */ private void placeNodePorts(final NodeData data) { final KVector nodeSize = data.node.getSize(); final boolean accountForLabels = data.node.getProperty(LayoutOptions.SIZE_CONSTRAINT) .contains(SizeConstraint.PORT_LABELS); // Let someone compute the port placement data we'll need computePortPlacementData(data); // Arrange the ports for (final PortAdapter<?> port : data.node.getPorts()) { Float portOffset = port.getProperty(LayoutOptions.OFFSET); if (portOffset == null) { portOffset = 0f; } final KVector portSize = port.getSize(); final Margins portMargins = port.getMargin(); final KVector position = new KVector(port.getPosition()); switch (port.getSide()) { case WEST: position.x = -portSize.x - portOffset; position.y = data.westY - portSize.y - (accountForLabels ? portMargins.bottom : 0.0); data.westY -= data.getPortGapsSize(PortSide.WEST) + portSize.y + (accountForLabels ? portMargins.top + portMargins.bottom : 0.0); break; case EAST: position.x = nodeSize.x + portOffset; position.y = data.eastY + (accountForLabels ? portMargins.top : 0.0); data.eastY += data.getPortGapsSize(PortSide.EAST) + portSize.y + (accountForLabels ? portMargins.top + portMargins.bottom : 0.0); break; case NORTH: position.x = data.northX + (accountForLabels ? portMargins.left : 0.0); position.y = -port.getSize().y - portOffset; data.northX += data.getPortGapsSize(PortSide.NORTH) + portSize.x + (accountForLabels ? portMargins.left + portMargins.right : 0.0); break; case SOUTH: position.x = data.southX - portSize.x - (accountForLabels ? portMargins.right : 0.0); position.y = nodeSize.y + portOffset; data.southX -= data.getPortGapsSize(PortSide.SOUTH) + portSize.x + (accountForLabels ? portMargins.left + portMargins.right : 0.0); break; } port.setPosition(position); } } /** * Computes the port placement data for the given node. * * @param data * the data containing the node to compute the placement data * for. */ // CHECKSTYLEOFF MethodLength // There's no sensible point to separate, too much parameters would have to // be introduced. private void computePortPlacementData(final NodeData data) { final KVector nodeSize = data.node.getSize(); // The way we calculate everything depends on whether any additional // port space is specified Margins additionalPortSpace; if (data.hasAdditionalPortSpace) { additionalPortSpace = data.node.getProperty(LayoutOptions.ADDITIONAL_PORT_SPACE); } else { // No additional port space set, so we set it to port spacing. additionalPortSpace = new Margins(data.portSpacing, data.portSpacing, data.portSpacing, data.portSpacing); } // Calculate how much space on each side may actually be used by ports double usableSpaceNorth = nodeSize.x; if (data.hasAdditionalPortSpace || (data.portAlignment[PortSide.NORTH.ordinal()] != PortAlignment.JUSTIFIED)) { usableSpaceNorth -= additionalPortSpace.left + additionalPortSpace.right; } double usableSpaceSouth = nodeSize.x; if (data.hasAdditionalPortSpace || (data.portAlignment[PortSide.SOUTH.ordinal()] != PortAlignment.JUSTIFIED)) { usableSpaceSouth -= additionalPortSpace.left + additionalPortSpace.right; } double usableSpaceWest = nodeSize.y; if (data.hasAdditionalPortSpace || (data.portAlignment[PortSide.WEST.ordinal()] != PortAlignment.JUSTIFIED)) { usableSpaceWest -= additionalPortSpace.top + additionalPortSpace.bottom; } double usableSpaceEast = nodeSize.y; if (data.hasAdditionalPortSpace || (data.portAlignment[PortSide.EAST.ordinal()] != PortAlignment.JUSTIFIED)) { usableSpaceEast -= additionalPortSpace.top + additionalPortSpace.bottom; } // Compute the space to be left between the ports and the coordinate of // the first port on // each side. // Note: // If the size constraints of this node are empty, the height and width // of the ports on each // side are zero. That is intentional: if this wasn't the case, bad // things would happen if // the ports would actually need more size than the node at its current // (unchanged) size // would be able to provide. // NORTH if (data.getPortAlignment(PortSide.NORTH) == PortAlignment.JUSTIFIED) { data.portGapsSize[PortSide.NORTH.ordinal()] = (usableSpaceNorth - data.getPortUsedSpace(PortSide.NORTH)) / data.getPortGapsCount(PortSide.NORTH); data.northX = data.hasAdditionalPortSpace ? (additionalPortSpace.left + (data.getPortsCount(PortSide.NORTH) == 1 ? data.portGapsSize[PortSide.NORTH.ordinal()] : 0)) : data.portGapsSize[PortSide.NORTH.ordinal()]; } else { data.portGapsSize[PortSide.NORTH.ordinal()] = data.portSpacing; // Space occupied by all ports (including the in between gaps). final double usedPortSpace = data.getPortUsedSpace(PortSide.NORTH) + (data.portGapsSize[PortSide.NORTH.ordinal()] * (data.getPortsCount(PortSide.NORTH) - 1)); switch (data.getPortAlignment(PortSide.NORTH)) { case BEGIN: // Start at leftmost position, additionalSpace from the // edge. data.northX = additionalPortSpace.left; break; case CENTER: // centered inside the usableWith data.northX = additionalPortSpace.left + ((usableSpaceNorth - usedPortSpace) / 2.0); break; case END: // Startposition is as far from the right edge as the ports' // used space plus // additionalSpace. data.northX = nodeSize.x - usedPortSpace - additionalPortSpace.right; break; } } // SOUTH if (data.getPortAlignment(PortSide.SOUTH) == PortAlignment.JUSTIFIED) { data.portGapsSize[PortSide.SOUTH.ordinal()] = (usableSpaceSouth - data.getPortUsedSpace(PortSide.SOUTH)) / data.getPortGapsCount(PortSide.SOUTH); data.southX = nodeSize.x - (data.hasAdditionalPortSpace ? (additionalPortSpace.right + (data.getPortsCount(PortSide.SOUTH) == 1 ? data.portGapsSize[PortSide.SOUTH.ordinal()] : 0)) : data.portGapsSize[PortSide.SOUTH.ordinal()]); } else { data.portGapsSize[PortSide.SOUTH.ordinal()] = data.portSpacing; // Space occupied by all ports (including the in between gaps). final double usedPortSpace = data.getPortUsedSpace(PortSide.SOUTH) + (data.portGapsSize[PortSide.SOUTH.ordinal()] * (data.getPortsCount(PortSide.SOUTH) - 1)); switch (data.getPortAlignment(PortSide.SOUTH)) { case BEGIN: // Startposition is as far from the right edge as the ports' // used space plus // additionalSpace. data.southX = usedPortSpace + additionalPortSpace.left; break; case CENTER: // centered inside the usableWith (starting at the right) data.southX = nodeSize.x - ((usableSpaceSouth - usedPortSpace) / 2.0) - additionalPortSpace.right; break; case END: // Start at rightmost position, additionalSpace from the // edge. data.southX = nodeSize.x - additionalPortSpace.right; break; } } // WEST if (data.getPortAlignment(PortSide.WEST) == PortAlignment.JUSTIFIED) { data.portGapsSize[PortSide.WEST.ordinal()] = (usableSpaceWest - data.getPortUsedSpace(PortSide.WEST)) / data.getPortGapsCount(PortSide.WEST); data.westY = nodeSize.y - (data.hasAdditionalPortSpace ? (additionalPortSpace.bottom + (data.getPortsCount(PortSide.WEST) == 1 ? data.portGapsSize[PortSide.WEST.ordinal()] : 0)) : data.portGapsSize[PortSide.WEST.ordinal()]); } else { data.portGapsSize[PortSide.WEST.ordinal()] = data.portSpacing; // Space occupied by all ports (including the in between gaps). final double usedPortSpace = data.getPortUsedSpace(PortSide.WEST) + (data.portGapsSize[PortSide.WEST.ordinal()] * (data.getPortsCount(PortSide.WEST) - 1)); switch (data.getPortAlignment(PortSide.WEST)) { case BEGIN: // Startposition is as far from the top edge as the ports' // used space plus // additionalSpace. data.westY = usedPortSpace + additionalPortSpace.top; break; case CENTER: // centered inside the usableWith (starting at the bottom) data.westY = nodeSize.y - ((usableSpaceWest - usedPortSpace) / 2.0) - additionalPortSpace.bottom; break; case END: // Start at bottommost position, additionalSpace from the // edge. data.westY = nodeSize.y - additionalPortSpace.bottom; break; } } // EAST if (data.getPortAlignment(PortSide.EAST) == PortAlignment.JUSTIFIED) { data.portGapsSize[PortSide.EAST.ordinal()] = (usableSpaceEast - data.getPortUsedSpace(PortSide.EAST)) / data.getPortGapsCount(PortSide.EAST); data.eastY = data.hasAdditionalPortSpace ? (additionalPortSpace.top + (data.getPortsCount(PortSide.EAST) == 1 ? data.portGapsSize[PortSide.EAST.ordinal()] : 0)) : data.portGapsSize[PortSide.EAST.ordinal()]; } else { data.portGapsSize[PortSide.EAST.ordinal()] = data.portSpacing; // Space occupied by all ports (including the in between gaps). final double usedPortSpace = data.getPortUsedSpace(PortSide.EAST) + (data.portGapsSize[PortSide.EAST.ordinal()] * (data.getPortsCount(PortSide.EAST) - 1)); switch (data.getPortAlignment(PortSide.EAST)) { case BEGIN: // Start at topmost position, additionalSpace from the edge. data.eastY = additionalPortSpace.top; break; case CENTER: // centered inside the usableWith data.eastY = additionalPortSpace.top + ((usableSpaceEast - usedPortSpace) / 2.0); break; case END: // Start position is as far from the bottom edge as the // ports' used space plus // additionalSpace. data.eastY = nodeSize.y - usedPortSpace - additionalPortSpace.bottom; break; } } } // CHECKSTYLEON MethodLength /** * Places the ports of a hypernode. * * @param node * the hypernode whose ports to place. */ private void placeHypernodePorts(final NodeAdapter<?> node) { for (final PortAdapter<?> port : node.getPorts()) { final KVector position = new KVector(port.getPosition()); switch (port.getSide()) { case WEST: position.x = 0; position.y = node.getSize().y / 2; break; case EAST: position.x = node.getSize().x; position.y = node.getSize().y / 2; break; case NORTH: position.x = node.getSize().x / 2; position.y = 0; break; case SOUTH: position.x = node.getSize().x / 2; position.y = node.getSize().y; break; } port.setPosition(position); } } // ///////////////////////////////////////////////////////////////////////////// // PLACING NODE LABELS /** * Calculates the position of the node's label groups and places the labels. * * @param data * the data containing the node whose labels to place. */ private void placeNodeLabels(final NodeData data) { // Check if there are any node labels if (!data.node.getLabels().iterator().hasNext()) { return; } computeLabelGroupPositions(data); doPlaceNodeLabels(data); } /** * Computes the top left position of each label group. * * @param data * the data containing the node whose labels to place. */ private void computeLabelGroupPositions(final NodeData data) { // TODO Outside label placement doesn't take ports into account yet. // For each present location, calculate the position of the top left // corner of the label group for (final Entry<LabelLocation, LabelGroup> entry : data.labelGroupsBoundingBoxes.entrySet()) { final Rectangle boundingBox = entry.getValue(); switch (entry.getKey()) { case OUT_T_L: boundingBox.x = 0; boundingBox.y = -(boundingBox.height + data.labelSpacing); break; case OUT_T_C: boundingBox.x = (data.node.getSize().x - boundingBox.width) / 2.0; boundingBox.y = -(boundingBox.height + data.labelSpacing); break; case OUT_T_R: boundingBox.x = data.node.getSize().x - boundingBox.width; boundingBox.y = -(boundingBox.height + data.labelSpacing); break; case OUT_B_L: boundingBox.x = 0; boundingBox.y = data.node.getSize().y + data.labelSpacing; break; case OUT_B_C: boundingBox.x = (data.node.getSize().x - boundingBox.width) / 2.0; boundingBox.y = data.node.getSize().y + data.labelSpacing; break; case OUT_B_R: boundingBox.x = data.node.getSize().x - boundingBox.width; boundingBox.y = data.node.getSize().y + data.labelSpacing; break; case OUT_L_T: boundingBox.x = -(boundingBox.width + data.labelSpacing); boundingBox.y = 0; break; case OUT_L_C: boundingBox.x = -(boundingBox.width + data.labelSpacing); boundingBox.y = (data.node.getSize().y - boundingBox.height) / 2.0; break; case OUT_L_B: boundingBox.x = -(boundingBox.width + data.labelSpacing); boundingBox.y = data.node.getSize().y - boundingBox.height; break; case OUT_R_T: boundingBox.x = data.node.getSize().x + data.labelSpacing; boundingBox.y = 0; break; case OUT_R_C: boundingBox.x = data.node.getSize().x + data.labelSpacing; boundingBox.y = (data.node.getSize().y - boundingBox.height) / 2.0; break; case OUT_R_B: boundingBox.x = data.node.getSize().x + data.labelSpacing; boundingBox.y = data.node.getSize().y - boundingBox.height; break; case IN_T_L: boundingBox.x = data.requiredPortLabelSpace.left + data.labelSpacing; boundingBox.y = data.requiredPortLabelSpace.top + data.labelSpacing; break; case IN_T_C: boundingBox.x = (data.node.getSize().x - boundingBox.width) / 2.0; boundingBox.y = data.requiredPortLabelSpace.top + data.labelSpacing; break; case IN_T_R: boundingBox.x = data.node.getSize().x - data.requiredPortLabelSpace.right - boundingBox.width - data.labelSpacing; boundingBox.y = data.requiredPortLabelSpace.top + data.labelSpacing; break; case IN_C_L: boundingBox.x = data.requiredPortLabelSpace.left + data.labelSpacing; boundingBox.y = (data.node.getSize().y - boundingBox.height) / 2.0; break; case IN_C_C: boundingBox.x = (data.node.getSize().x - boundingBox.width) / 2.0; boundingBox.y = (data.node.getSize().y - boundingBox.height) / 2.0; break; case IN_C_R: boundingBox.x = data.node.getSize().x - data.requiredPortLabelSpace.right - boundingBox.width - data.labelSpacing; boundingBox.y = (data.node.getSize().y - boundingBox.height) / 2.0; break; case IN_B_L: boundingBox.x = data.requiredPortLabelSpace.left + data.labelSpacing; boundingBox.y = data.node.getSize().y - data.requiredPortLabelSpace.bottom - boundingBox.height - data.labelSpacing; break; case IN_B_C: boundingBox.x = (data.node.getSize().x - boundingBox.width) / 2.0; boundingBox.y = data.node.getSize().y - data.requiredPortLabelSpace.bottom - boundingBox.height - data.labelSpacing; break; case IN_B_R: boundingBox.x = data.node.getSize().x - data.requiredPortLabelSpace.right - boundingBox.width - data.labelSpacing; boundingBox.y = data.node.getSize().y - data.requiredPortLabelSpace.bottom - boundingBox.height - data.labelSpacing; break; } } } /** * Places the given node's labels in a vertical stack, starting at the given * position. * * @param data * the data containing the node whose labels are to be placed. */ private void doPlaceNodeLabels(final NodeData data) { // Place all labels for (final LabelAdapter<?> label : data.node.getLabels()) { final KVector position = new KVector(label.getPosition()); final LabelLocation location = LabelLocation.values()[label.getVolatileId()]; final LabelGroup boundingBox = data.labelGroupsBoundingBoxes.get(location); // Set y coordinate position.y = boundingBox.y + boundingBox.nextLabelYPos; // The x coordinate depends on the text alignment if (location.getHorizontalAlignment() == TextAlignment.LEFT) { position.x = boundingBox.x; } else if (location.getHorizontalAlignment() == TextAlignment.CENTER) { position.x = boundingBox.x + ((boundingBox.width - label.getSize().x) / 2.0); } else if (location.getHorizontalAlignment() == TextAlignment.RIGHT) { position.x = (boundingBox.x + boundingBox.width) - label.getSize().x; } // Apply new position label.setPosition(position); // Update next y coordinate boundingBox.nextLabelYPos += label.getSize().y + data.labelSpacing; } } // ///////////////////////////////////////////////////////////////////////////// // CONTEXT UTILITIES /** * Information holder to provide context information for the different * phases of the algorithm. The information are usually computed by some * phase to be made available to later phases. * * @author csp */ private static final class NodeData { /** * The currently processed node. */ private final NodeAdapter<?> node; /* * Spacing around labels. */ private double labelSpacing; /* * Spacing around ports. */ private double portSpacing; /** * Node insets required by port labels inside the node. This is always * set, but not always taken into account to calculate the node size. */ private final Insets requiredPortLabelSpace = new Insets(); /** * Node insets required by node labels placed inside the node. This is * always set, but not always taken into account to calculate the node * size. */ private final Insets requiredNodeLabelSpace = new Insets(); /** * Whether the node has additional port space set or not. */ private boolean hasAdditionalPortSpace; /** * Number of ports on the respective side. Only used if port constraints * are not {@link PortConstraints#FIXED_RATIO} or * {@link PortConstraints#FIXED_POS}. */ private final int[] portsCount = new int[PortSide.values().length]; /** * Number of gaps between ports on the respective side. Only used if * port constraints are not {@link PortConstraints#FIXED_RATIO} or * {@link PortConstraints#FIXED_POS}. */ private final int[] portGapsCount = new int[PortSide.values().length]; /** * Size of gops between ports on the respective side. Only used if port * constraints are not {@link PortConstraints#FIXED_RATIO} or * {@link PortConstraints#FIXED_POS}. */ private final double[] portGapsSize = new double[PortSide.values().length]; // The position of the next port on each side private double westY; private double eastY; private double northX; private double southX; /** * Height of the ports on the respective side. If port labels are * accounted for, the space includes the relevant port margins too. Only * used if port constraints are not {@link PortConstraints#FIXED_RATIO} * or {@link PortConstraints#FIXED_POS}. */ private final double[] portUsedSpace = new double[PortSide.values().length]; /** * Alignment of ports on the respective side. Only used if port * constraints are not {@link PortConstraints#FIXED_RATIO} or * {@link PortConstraints#FIXED_POS}. */ private final PortAlignment[] portAlignment = new PortAlignment[PortSide.values().length]; /** * Contains the size and position of the corresponding label group for * each element of {@link LabelLocation}. */ private final Map<LabelLocation, LabelGroup> labelGroupsBoundingBoxes = new EnumMap<LabelLocation, LabelGroup>( LabelLocation.class); /** * Create a new information holder with default values and the given, * currently processed node. * * @param node * the node currently processed. */ private NodeData(final NodeAdapter<?> node) { this.node = node; Arrays.fill(portsCount, 0); Arrays.fill(portGapsCount, 0); Arrays.fill(portUsedSpace, 0.0); } /** * Returns the number of ports on the given port side. * * @param side * the port side in question. * @return the number of ports on the given side. */ private int getPortsCount(final PortSide side) { return portsCount[side.ordinal()]; } /** * Returns the number of gaps between ports on the given port side. * * @param side * the port side in question. * @return the number of gaps on the given side. */ private int getPortGapsCount(final PortSide side) { return portGapsCount[side.ordinal()]; } /** * Returns the number of gaps between ports on the given port side. * * @param side * the port side in question. * @return the number of gaps on the given side. */ private double getPortGapsSize(final PortSide side) { return portGapsSize[side.ordinal()]; } /** * Returns the amount of used space by ports on the given port side. * * @param side * the port side in question. * @return the amount of used space on the given side. */ private double getPortUsedSpace(final PortSide side) { return portUsedSpace[side.ordinal()]; } /** * Returns the alignment of ports on the given port side. * * @param side * the port side in question. * @return the alignment of ports on the given side. */ private PortAlignment getPortAlignment(final PortSide side) { return portAlignment[side.ordinal()]; } } // ///////////////////////////////////////////////////////////////////////////// // LABEL PLACEMENT UTILITIES /** * @param data * the data containing the node in question. */ private void calculateRequiredNodeLabelSpace(final NodeData data) { LabelSpaceCalculation.calculateRequiredNodeLabelSpace(data.node, data.labelSpacing, data.labelGroupsBoundingBoxes, data.requiredNodeLabelSpace); } }