/** * Put a node into the given linear segment and check for following parts of a long edge. * * @param node the node to put into the linear segment * @param segment a linear segment * @return {@code true} if the given node was not already part of another segment and was thus * added to the given segment. */ private boolean fillSegment(final LNode node, final LinearSegment segment) { NodeType nodeType = node.getType(); // handle initial big nodes as big node type if (node.getProperty(InternalProperties.BIG_NODE_INITIAL)) { nodeType = NodeType.BIG_NODE; } if (node.id >= 0) { // The node is already part of another linear segment return false; } else if (segment.nodeType != null && (nodeType == NodeType.BIG_NODE && nodeType != segment.nodeType)) { // Big nodes are not allowed to share a linear segment with other dummy nodes return false; } else { // Add the node to the given linear segment node.id = segment.id; segment.nodes.add(node); } segment.nodeType = nodeType; if (nodeType == NodeType.LONG_EDGE || nodeType == NodeType.NORTH_SOUTH_PORT || nodeType == NodeType.BIG_NODE) { // This is a LONG_EDGE, NORTH_SOUTH_PORT or BIG_NODE dummy; check if any of its // successors are of one of these types too. If so, we can form a linear segment // with one of them. (not with more than one, though) // Note 1: LONG_EDGES and NORTH_SOUTH_PORTs can share a common linear segment // Note 2: we must take care not to make a segment out of nodes that are in the same layer // Note 3: for BIG_NODEs also the first BIG_NODE_INITIAL which is no actual dummy node has // to be considered here for (LPort sourcePort : node.getPorts()) { for (LPort targetPort : sourcePort.getSuccessorPorts()) { LNode targetNode = targetPort.getNode(); NodeType targetNodeType = targetNode.getType(); if (node.getLayer() != targetNode.getLayer()) { if (nodeType == NodeType.BIG_NODE) { // current AND the next node are BIG_NODE dummies if (targetNodeType == NodeType.BIG_NODE) { if (fillSegment(targetNode, segment)) { // We just added another node to this node's linear segment. // That's quite enough. return true; } } } else { // current no bignode and next node is LONG_EDGE and NORTH_SOUTH_PORT if (targetNodeType == NodeType.LONG_EDGE || targetNodeType == NodeType.NORTH_SOUTH_PORT) { if (fillSegment(targetNode, segment)) { // We just added another node to this node's linear segment. // That's quite enough. return true; } } } } } } } return true; }
/** * Calculate the force acting on the given linear segment. The force is stored in the segment's * deflection field. * * @param segment the linear segment whose force is to be calculated * @param incoming whether incoming edges should be considered * @param outgoing whether outgoing edges should be considered * @param deflectionDampening factor by which deflections are dampened */ private void calcDeflection( final LinearSegment segment, final boolean incoming, final boolean outgoing, final double deflectionDampening) { double segmentDeflection = 0; int nodeWeightSum = 0; for (LNode node : segment.nodes) { double nodeDeflection = 0; int edgeWeightSum = 0; int inputPrio = incoming ? node.getProperty(INPUT_PRIO) : Integer.MIN_VALUE; int outputPrio = outgoing ? node.getProperty(OUTPUT_PRIO) : Integer.MIN_VALUE; int minPrio = Math.max(inputPrio, outputPrio); // Calculate force for every port/edge for (LPort port : node.getPorts()) { double portpos = node.getPosition().y + port.getPosition().y + port.getAnchor().y; if (outgoing) { for (LEdge edge : port.getOutgoingEdges()) { LPort otherPort = edge.getTarget(); LNode otherNode = otherPort.getNode(); if (segment != linearSegments[otherNode.id]) { int otherPrio = Math.max(otherNode.getProperty(INPUT_PRIO), otherNode.getProperty(OUTPUT_PRIO)); int prio = edge.getProperty(InternalProperties.PRIORITY); if (prio >= minPrio && prio >= otherPrio) { nodeDeflection += otherNode.getPosition().y + otherPort.getPosition().y + otherPort.getAnchor().y - portpos; edgeWeightSum++; } } } } if (incoming) { for (LEdge edge : port.getIncomingEdges()) { LPort otherPort = edge.getSource(); LNode otherNode = otherPort.getNode(); if (segment != linearSegments[otherNode.id]) { int otherPrio = Math.max(otherNode.getProperty(INPUT_PRIO), otherNode.getProperty(OUTPUT_PRIO)); int prio = edge.getProperty(InternalProperties.PRIORITY); if (prio >= minPrio && prio >= otherPrio) { nodeDeflection += otherNode.getPosition().y + otherPort.getPosition().y + otherPort.getAnchor().y - portpos; edgeWeightSum++; } } } } } // Avoid division by zero if (edgeWeightSum > 0) { segmentDeflection += nodeDeflection / edgeWeightSum; nodeWeightSum++; } } if (nodeWeightSum > 0) { segment.deflection = deflectionDampening * segmentDeflection / nodeWeightSum; segment.weight = nodeWeightSum; } else { segment.deflection = 0; segment.weight = 0; } }
/** {@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(); }