/** * 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; } }
/** * Post-process the balanced placement by moving linear segments where obvious improvements can be * made. * * @param layeredGraph the layered graph */ private void postProcess(final LGraph layeredGraph) { // process each linear segment independently for (LinearSegment segment : linearSegments) { double minRoomAbove = Integer.MAX_VALUE, minRoomBelow = Integer.MAX_VALUE; for (LNode node : segment.nodes) { double roomAbove, roomBelow; int index = node.getIndex(); // determine the amount by which the linear segment can be moved up without overlap if (index > 0) { LNode neighbor = node.getLayer().getNodes().get(index - 1); float spacing = spacings.getVerticalSpacing(node, neighbor); roomAbove = node.getPosition().y - node.getMargin().top - (neighbor.getPosition().y + neighbor.getSize().y + neighbor.getMargin().bottom + spacing); } else { roomAbove = node.getPosition().y - node.getMargin().top; } minRoomAbove = Math.min(roomAbove, minRoomAbove); // determine the amount by which the linear segment can be moved down without // overlap if (index < node.getLayer().getNodes().size() - 1) { LNode neighbor = node.getLayer().getNodes().get(index + 1); float spacing = spacings.getVerticalSpacing(node, neighbor); roomBelow = neighbor.getPosition().y - neighbor.getMargin().top - (node.getPosition().y + node.getSize().y + node.getMargin().bottom + spacing); } else { roomBelow = 2 * node.getPosition().y; } minRoomBelow = Math.min(roomBelow, minRoomBelow); } double minDisplacement = Integer.MAX_VALUE; boolean foundPlace = false; // determine the minimal displacement that would make one incoming edge straight LNode firstNode = segment.nodes.get(0); for (LPort target : firstNode.getPorts()) { double pos = firstNode.getPosition().y + target.getPosition().y + target.getAnchor().y; for (LEdge edge : target.getIncomingEdges()) { LPort source = edge.getSource(); double d = source.getNode().getPosition().y + source.getPosition().y + source.getAnchor().y - pos; if (Math.abs(d) < Math.abs(minDisplacement) && Math.abs(d) < (d < 0 ? minRoomAbove : minRoomBelow)) { minDisplacement = d; foundPlace = true; } } } // determine the minimal displacement that would make one outgoing edge straight LNode lastNode = segment.nodes.get(segment.nodes.size() - 1); for (LPort source : lastNode.getPorts()) { double pos = lastNode.getPosition().y + source.getPosition().y + source.getAnchor().y; for (LEdge edge : source.getOutgoingEdges()) { LPort target = edge.getTarget(); double d = target.getNode().getPosition().y + target.getPosition().y + target.getAnchor().y - pos; if (Math.abs(d) < Math.abs(minDisplacement) && Math.abs(d) < (d < 0 ? minRoomAbove : minRoomBelow)) { minDisplacement = d; foundPlace = true; } } } // if such a displacement could be found, apply it to the whole linear segment if (foundPlace && minDisplacement != 0) { for (LNode node : segment.nodes) { node.getPosition().y += minDisplacement; } } } }
/** * 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; }