private void initialize(final LGraph graph) { layeredGraph = graph; int layerCount = graph.getLayers().size(); bestNodeOrder = new LNode[layerCount][]; currentNodeOrder = new LNode[layerCount][]; originalNodeOrder = new LNode[layerCount][]; ListIterator<Layer> layerIter = graph.getLayers().listIterator(); while (layerIter.hasNext()) { Layer layer = layerIter.next(); int layerNodeCount = layer.getNodes().size(); assert layerNodeCount > 0; int layerIndex = layerIter.previousIndex(); bestNodeOrder[layerIndex] = new LNode[layerNodeCount]; currentNodeOrder[layerIndex] = new LNode[layerNodeCount]; originalNodeOrder[layerIndex] = new LNode[layerNodeCount]; ListIterator<LNode> nodeIter = layer.getNodes().listIterator(); int id = 0; while (nodeIter.hasNext()) { LNode node = nodeIter.next(); node.id = id++; currentNodeOrder[layerIndex][nodeIter.previousIndex()] = node; bestNodeOrder[layerIndex][nodeIter.previousIndex()] = node; originalNodeOrder[layerIndex][nodeIter.previousIndex()] = node; } } crossingCounter = new AllCrossingsCounter(currentNodeOrder); if (greedySwitchType.useHyperedgeCounter()) { crossingCounter.useHyperedgeCounter(); } }
/** * Creates an unbalanced placement for the sorted linear segments. * * @param layeredGraph the layered graph to create an unbalanced placement for. */ private void createUnbalancedPlacement(final LGraph layeredGraph) { // How many nodes are currently placed in each layer int[] nodeCount = new int[layeredGraph.getLayers().size()]; // The type of the node most recently placed in a given layer NodeType[] recentNodeType = new NodeType[layeredGraph.getLayers().size()]; // Iterate through the linear segments (in proper order!) and place them for (LinearSegment segment : linearSegments) { // Determine the uppermost placement for the linear segment double uppermostPlace = 0.0f; for (LNode node : segment.nodes) { NodeType nodeType = node.getType(); int layerIndex = node.getLayer().getIndex(); nodeCount[layerIndex]++; // Calculate how much space to leave between the linear segment and the last // node of the given layer float spacing = spacings.edgeEdgeSpacing * spacings.inLayerSpacingFactor; if (nodeCount[layerIndex] > 0) { if (recentNodeType[layerIndex] != null) { spacing = spacings.getVerticalSpacing(recentNodeType[layerIndex], nodeType); } } uppermostPlace = Math.max(uppermostPlace, node.getLayer().getSize().y + spacing); } // Apply the uppermost placement to all elements for (LNode node : segment.nodes) { // Set the node position node.getPosition().y = uppermostPlace + node.getMargin().top; // Adjust layer size Layer layer = node.getLayer(); layer.getSize().y = uppermostPlace + node.getMargin().top + node.getSize().y + node.getMargin().bottom; recentNodeType[layer.getIndex()] = node.getType(); } } }
/** {@inheritDoc} */ public IntermediateProcessingConfiguration getIntermediateProcessingConfiguration( final LGraph graph) { if (graph .getProperty(InternalProperties.GRAPH_PROPERTIES) .contains(GraphProperties.EXTERNAL_PORTS)) { return HIERARCHY_PROCESSING_ADDITIONS; } else { return null; } }
private void setAsGraph(final LNode[][] nodeOrder) { ListIterator<Layer> layerIter = layeredGraph.getLayers().listIterator(); while (layerIter.hasNext()) { Layer layer = layerIter.next(); LNode[] nodes = nodeOrder[layerIter.previousIndex()]; ListIterator<LNode> nodeIter = layer.getNodes().listIterator(); while (nodeIter.hasNext()) { nodeIter.next(); nodeIter.set(nodes[nodeIter.previousIndex()]); } } }
/** {@inheritDoc} */ public void process(final LGraph graph, final IElkProgressMonitor progressMonitor) { progressMonitor.begin("Greedy switch crossing reduction", 1); greedySwitchType = graph.getProperty(Properties.GREEDY_SWITCH_TYPE); int layerCount = graph.getLayers().size(); if (layerCount < 2 || greedySwitchType == GreedySwitchType.OFF) { progressMonitor.done(); return; } initialize(graph); if (greedySwitchType.useBestOfUpOrDown()) { compareSweepingUpwardOrDownward(); } else { sweepOneSidedOrTwoSided(); } setAsGraph(bestNodeOrder); progressMonitor.done(); }
/** @param graph the {@link LGraph} for which to record the spacing values. */ public Spacings(final LGraph graph) { nodeSpacing = graph.getProperty(InternalProperties.SPACING); inLayerSpacingFactor = graph.getProperty(Properties.OBJ_SPACING_IN_LAYER_FACTOR); edgeEdgeSpacing = nodeSpacing * graph.getProperty(Properties.EDGE_SPACING_FACTOR); edgeNodeSpacing = nodeSpacing * graph.getProperty(Properties.EDGE_NODE_SPACING_FACTOR); portSpacing = graph.getProperty(InternalProperties.PORT_SPACING); externalPortSpacing = graph.getProperty(InternalProperties.PORT_SPACING); labelSpacing = graph.getProperty(LayoutOptions.LABEL_SPACING); // pre calculate the spacings between pairs of node types int n = NodeType.values().length; nodeTypeSpacings = new float[n][n]; precalculateNodeTypeSpacings(); }
/** {@inheritDoc} */ public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor) { monitor.begin("Linear segments node placement", 1); spacings = layeredGraph.getProperty(InternalProperties.SPACINGS); // sort the linear segments of the layered graph sortLinearSegments(layeredGraph); // create an unbalanced placement from the sorted segments createUnbalancedPlacement(layeredGraph); // balance the placement balancePlacement(layeredGraph); // post-process the placement for small corrections postProcess(layeredGraph); // release the created resources linearSegments = null; spacings = null; monitor.done(); }
/** * Balance the initial placement by force-based movement of regions. First perform * <em>pendulum</em> iterations, where only one direction of edges is considered, then * <em>rubber</em> iterations, where both incoming and outgoing edges are considered. In each * iteration first determine the <em>deflection</em> of each linear segment, i.e. the optimal * position delta that leads to a balanced placement with respect to its adjacent segments. Then * merge regions that touch each other, building mean values of the involved deflections, and * finally apply the resulting deflection values to all segments. The iterations stop when no * further improvement is done. * * @param layeredGraph a layered graph */ private void balancePlacement(final LGraph layeredGraph) { double deflectionDampening = layeredGraph.getProperty(Properties.LINEAR_SEGMENTS_DEFLECTION_DAMPENING).doubleValue(); // Determine a suitable number of pendulum iterations int thoroughness = layeredGraph.getProperty(Properties.THOROUGHNESS); int pendulumIters = PENDULUM_ITERS; int finalIters = FINAL_ITERS; double threshold = THRESHOLD_FACTOR / thoroughness; // Iterate the balancing boolean ready = false; Mode mode = Mode.FORW_PENDULUM; double lastTotalDeflection = Integer.MAX_VALUE; do { // Calculate force for every linear segment boolean incoming = mode != Mode.BACKW_PENDULUM; boolean outgoing = mode != Mode.FORW_PENDULUM; double totalDeflection = 0; for (LinearSegment segment : linearSegments) { segment.refSegment = null; calcDeflection(segment, incoming, outgoing, deflectionDampening); totalDeflection += Math.abs(segment.deflection); } // Merge linear segments to form regions boolean merged; do { merged = mergeRegions(layeredGraph); } while (merged); // Move the nodes according to the deflection value of their region for (LinearSegment segment : linearSegments) { double deflection = segment.region().deflection; if (deflection != 0) { for (LNode node : segment.nodes) { node.getPosition().y += deflection; } } } // Update the balancing mode if (mode == Mode.FORW_PENDULUM || mode == Mode.BACKW_PENDULUM) { pendulumIters--; if (pendulumIters <= 0 && (totalDeflection < lastTotalDeflection || -pendulumIters > thoroughness)) { mode = Mode.RUBBER; lastTotalDeflection = Integer.MAX_VALUE; } else if (mode == Mode.FORW_PENDULUM) { mode = Mode.BACKW_PENDULUM; lastTotalDeflection = totalDeflection; } else { mode = Mode.FORW_PENDULUM; lastTotalDeflection = totalDeflection; } } else { ready = totalDeflection >= lastTotalDeflection || lastTotalDeflection - totalDeflection < threshold; lastTotalDeflection = totalDeflection; if (ready) { finalIters--; } } } while (!(ready && finalIters <= 0)); }
/** * Fills the dependency graph with dependencies. If a dependency would introduce a cycle, the * offending linear segment is split into two linear segments. * * @param layeredGraph the layered graph. * @param segmentList the list of segments. Updated to include the newly created linear segments. * @param outgoingList the lists of outgoing dependencies for each segment. This essentially * encodes the edges of the dependency graph. * @param incomingCountList the number of incoming dependencies for each segment. */ private void createDependencyGraphEdges( final LGraph layeredGraph, final List<LinearSegment> segmentList, final List<List<LinearSegment>> outgoingList, final List<Integer> incomingCountList) { /* * There's some <scaryVoice> faaaancy </scaryVoice> stuff going on here. Basically, we go * through all the layers, from left to right. In each layer, we go through all the nodes. * For each node, we retrieve the linear segment it's part of and add a dependency to the * next node's linear segment. So far so good. * * This works perfectly fine as long as we assume that the relative order of linear segments * doesn't change from one layer to the next. However, since the introduction of north / * south port dummies, it can. We now have to avoid creating cycles in the dependency graph. * This is done by remembering the indices of each linear segment in the previous layer. * When we encounter a segment x, we check if there is a segment y that came before x in the * previous layer. (that would introduce a cycle) If that's the case, we split x at the * current layer, resulting in two segments, x1 and x2, x2 starting at the current layer. * Now, we proceed as usual, adding a dependency from x2 to y. But we have avoided a cycle * because y does not depend on x2, but on x1. */ int nextLinearSegmentID = segmentList.size(); int layerIndex = 0; for (Layer layer : layeredGraph) { List<LNode> nodes = layer.getNodes(); if (nodes.isEmpty()) { // Ignore empty layers continue; } Iterator<LNode> nodeIter = nodes.iterator(); int indexInLayer = 0; // We carry the previous node with us for dependency management LNode previousNode = null; // Get the layer's first node LNode currentNode = nodeIter.next(); LinearSegment currentSegment = null; while (currentNode != null) { // Get the current node's segment currentSegment = segmentList.get(currentNode.id); /* * Check if we have a cycle. That's the case if the following holds: - The current * segment appeared in the previous layer as well. - In the previous layer, we find * a segment after the current segment that appears before the current segment in * the current layer. */ if (currentSegment.indexInLastLayer >= 0) { LinearSegment cycleSegment = null; Iterator<LNode> cycleNodesIter = layer.getNodes().listIterator(indexInLayer + 1); while (cycleNodesIter.hasNext()) { LNode cycleNode = cycleNodesIter.next(); cycleSegment = segmentList.get(cycleNode.id); if (cycleSegment.lastLayer == currentSegment.lastLayer && cycleSegment.indexInLastLayer < currentSegment.indexInLastLayer) { break; } else { cycleSegment = null; } } // If we have found a cycle segment, we need to split the current linear segment if (cycleSegment != null) { // Update the current segment before it's split if (previousNode != null) { incomingCountList.set(currentNode.id, incomingCountList.get(currentNode.id) - 1); outgoingList.get(previousNode.id).remove(currentSegment); } currentSegment = currentSegment.split(currentNode, nextLinearSegmentID++); segmentList.add(currentSegment); outgoingList.add(new ArrayList<LinearSegment>()); if (previousNode != null) { outgoingList.get(previousNode.id).add(currentSegment); incomingCountList.add(1); } else { incomingCountList.add(0); } } } // Now add a dependency to the next node, if any LNode nextNode = null; if (nodeIter.hasNext()) { nextNode = nodeIter.next(); LinearSegment nextSegment = segmentList.get(nextNode.id); outgoingList.get(currentNode.id).add(nextSegment); incomingCountList.set(nextNode.id, incomingCountList.get(nextNode.id) + 1); } // Update segment's layer information currentSegment.lastLayer = layerIndex; currentSegment.indexInLastLayer = indexInLayer++; // Cycle nodes previousNode = currentNode; currentNode = nextNode; } layerIndex++; } // Write debug output graph if (layeredGraph.getProperty(LayoutOptions.DEBUG_MODE)) { DebugUtil.writeDebugGraph(layeredGraph, segmentList, outgoingList); } }
/** {@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(); }