/** * Assignes the least crowded corner loopSide to the given components. The crowding will be * recalculated after each added edge. * * @param components The components to assign a loopSide for. */ private void assignCornerSide(final List<ConnectedSelfLoopComponent> components) { for (final ConnectedSelfLoopComponent component : components) { final LoopSide side = loopSides.getLeastCrowdedCorner(); loopSides.addSize(side, component.getTextWidth(), component.getTextHeight()); component.setLoopSide(side, true); } }
/** * Sets the loopSide of all self-loops of the component individually, depending on the side(s) * already defined for at least one port in the component. At least one portSide must be defined, * otherwise this method will run infinitely. * * @param component The connected component to process. * @return The LoopSide that is equal to the assigned portSides. Undefined, if there is not a * common side for all loops. */ private LoopSide setPortSideByConstraint(final ConnectedSelfLoopComponent component) { // Iterator over the edges of the component. // The iterator is cycling, as there is no guarantee, that (at least) one port of the current // edge has a portSide defined. If both portSides of the current edge are UNDEFINED, we will // return to it later. final Iterator<LEdge> iter = Iterators.cycle(Lists.newArrayList(component.getEdges())); // LoopSides in this component will be collected here final EnumSet<LoopSide> loopSidesInComponent = EnumSet.noneOf(LoopSide.class); while (iter.hasNext()) { final LEdge edge = iter.next(); final PortSide sourceSide = edge.getSource().getSide(); final PortSide targetSide = edge.getTarget().getSide(); if (sourceSide == PortSide.UNDEFINED) { // source side is undefined if (targetSide != PortSide.UNDEFINED) { // only targetSide is defined. Edge becomes a sideLoop on the side of target. final LoopSide side = LoopSide.fromPortSides(targetSide); edge.setProperty(InternalProperties.SPLINE_LOOPSIDE, side); edge.getSource().setSide(targetSide); loopSidesInComponent.add(side); iter.remove(); } } else { // source side is defined if (targetSide == PortSide.UNDEFINED) { // only sourceSide is defined. Edge becomes a sideLoop on the side of source. final LoopSide side = LoopSide.fromPortSides(sourceSide); edge.setProperty(InternalProperties.SPLINE_LOOPSIDE, side); edge.getTarget().setSide(sourceSide); loopSidesInComponent.add(side); iter.remove(); } else { // both sides are defined, set edge side resulting from the combination final LoopSide side = LoopSide.fromPortSide(sourceSide, targetSide); edge.setProperty(InternalProperties.SPLINE_LOOPSIDE, side); loopSidesInComponent.add(side); iter.remove(); } } } // Now all edges have a LoopSide assigned. // Check if we can find a LoopSide for the whole component. LoopSide side; if (loopSidesInComponent.size() == 1) { side = loopSidesInComponent.iterator().next(); } else { side = LoopSide.UNDEFINED; } component.setLoopSide(side, false); return side; }
/** * Distributes the self-loops equally around the node. * * @param components */ public void calculate(final List<ConnectedSelfLoopComponent> components) { final List<ConnectedSelfLoopComponent> withLongText = Lists.newArrayList(); final List<ConnectedSelfLoopComponent> withShortText = Lists.newArrayList(); final List<ConnectedSelfLoopComponent> withoutText = Lists.newArrayList(); // first we are going to check for the size of possible edge labels for (final ConnectedSelfLoopComponent component : components) { if (component.getTextWidth() > MAX_TEXT_LENGTH) { withLongText.add(component); } else if (component.getTextWidth() > 0.0) { withShortText.add(component); } else { withoutText.add(component); } } // if there is only one loop with text, handle this loop as a long text loop if (withShortText.size() == 1 && withLongText.isEmpty()) { withLongText.addAll(withShortText); withShortText.clear(); } // try to put the components with long text to one of the horizontal across loop sides. // if there are no such sides available, handle long text loops as short text loops. if (!withLongText.isEmpty() && loopSides.availableSides().contains(LoopSide.N) && loopSides.availableSides().contains(LoopSide.S)) { assignAcrossSide(withLongText); } else { withShortText.addAll(withLongText); } // put the components with short text to one of the corner loop sides if (!withShortText.isEmpty()) { assignCornerSide(withShortText); } // Last but not least: loops without text get spread equally. if (!withoutText.isEmpty()) { // //////////////////////////////////////////////////////////// // First of all we try to put those ConnectedSelfLoops who have more than one edge, // to one of the available straight sides. // //////////////////////////////////////////////////////////// final Set<LoopSide> availableStraights = loopSides.availableStraightSides(); if (!availableStraights.isEmpty()) { final Iterator<ConnectedSelfLoopComponent> itrComponent = withoutText.iterator(); final Iterator<LoopSide> itrAvailable = Iterables.cycle(availableStraights).iterator(); while (itrComponent.hasNext()) { // look for a component with more than one edge ConnectedSelfLoopComponent component = itrComponent.next(); while (itrComponent.hasNext() && component.getEdges().size() < 2) { component = itrComponent.next(); } // If we have found a component with more than one edge, assign the next // straight side to the component. if (component.getEdges().size() > 1) { final LoopSide side = itrAvailable.next(); component.setLoopSide(side, true); itrComponent.remove(); loopSides.removeSide(side); } } } // //////////////////////////////////////////////////////////// // Now we can distribute the remaining components around the node. // //////////////////////////////////////////////////////////// // number of required sides final int number = withoutText.size(); // Center of the assigned pattern. final LoopSide center = findCenter(); // The list of portSides we assign to the set of connected components. final List<LoopSide> portSides = Lists.newArrayList(); // How many times must a "full set" of LoopSide-elements be constructed? final int fullSets = number / loopSides.availableNotAcrossSides().size(); // Construct the full sets. for (int i = 0; i < fullSets; i++) { portSides.addAll(loopSides.availableNotAcrossSides()); } // How many elements are remaining to be constructed after the full sets have been // constructed? int remainer = number % loopSides.availableNotAcrossSides().size(); // If more than three elements are remaining, the pattern includes all four corners // and the sides that would be included in the pattern for (n-4) elements. if (remainer > 3) { portSides.addAll(LoopSide.getAllCornerSides()); remainer -= 4; } // Create the pattern for 1-3 elements. switch (remainer) { case 3: // opposed side (from center) and the the same sides as for two elements portSides.add(center.opposite()); case 2: // the first AVAILABLE neighbors of the neighbors of the opposed side of the // center LoopSide tmpSide = center.opposite().left(); do { tmpSide = tmpSide.left(); } while (!loopSides.availableSides().contains(tmpSide)); portSides.add(tmpSide); tmpSide = center.opposite().right(); do { tmpSide = tmpSide.right(); } while (!loopSides.availableSides().contains(tmpSide)); portSides.add(tmpSide); break; case 1: // opposed side (from center) portSides.add(center.opposite()); break; default: break; } // Now assign the remaining sides to the set of portSides we just have constructed. final Iterator<LoopSide> itrSides = portSides.iterator(); final Iterator<ConnectedSelfLoopComponent> itrComponent = withoutText.iterator(); while (itrSides.hasNext() && itrComponent.hasNext()) { itrComponent.next().setLoopSide(itrSides.next(), true); } } }
/** {@inheritDoc} */ public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor) { monitor.begin("Spline SelfLoop positioning", 1); /** Stores which loop placement strategy to choose. */ final SelfLoopPlacement loopPlacement = layeredGraph.getProperty(Properties.SPLINE_SELF_LOOP_PLACEMENT); //////////////////////////////////////////////////////// // There are two main jobs to be done: // 1) Find a loop-side for each component. // 2) Position ports in the correct order around the node. //////////////////////////////////////////////////////// // Process all nodes on all layers. for (final Layer layer : layeredGraph) { for (final LNode node : layer) { // Read self-loops components. final List<ConnectedSelfLoopComponent> components = node.getProperty(InternalProperties.SPLINE_SELFLOOP_COMPONENTS); // Components to be distributed by the placement strategy. final List<ConnectedSelfLoopComponent> componentsToBePlaced = Lists.newArrayList(); for (final ConnectedSelfLoopComponent component : components) { // Re-Add all hidden edges to their ports. component.unhideEdges(); if (component.getConstrainedPorts().isEmpty()) { // If there is no constraint on any port, we have to process this component later componentsToBePlaced.add(component); } else { // If there is at least one port with a constraint to it's port-side, // we will set the port- and loop-sides by this constraint. (Job 1) setPortSideByConstraint(component); if (!component.getNonLoopPorts().isEmpty()) { // Position and re-add all ports to the node, that are part of a component // with at least one non-loop edge. (Job 2) addComponentWithNonLoopEdges(component); } } } // Now we have to find a loop-side (job 1) for the remaining components. They are all // stored in componentsToBePlaced. All these components don't have a port with a // constraint on it's portSide, so we can arrange them accordingly to the cosen strategy. switch (loopPlacement) { case EQUALLY_DISTRIBUTED: setPortSideSpreadEqually(componentsToBePlaced, node); break; case NORTH_SEQUENCE: for (final ConnectedSelfLoopComponent component : componentsToBePlaced) { component.setLoopSide(LoopSide.N, true); } break; case NORTH_STACKED: for (final ConnectedSelfLoopComponent component : componentsToBePlaced) { component.setLoopSide(LoopSide.N, true); } break; default: // Unknown strategy chosen. assert false; break; } // Position and re-add all ports to the node. // This is job 2) switch (loopPlacement) { case EQUALLY_DISTRIBUTED: case NORTH_STACKED: portStackedPositioning(components); break; case NORTH_SEQUENCE: portLinedPositioning(components); break; default: break; } } } monitor.done(); }