/* * Copyright 2012 Odysseus Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.odysseus.ithaka.digraph.layout.sugiyama; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; import de.odysseus.ithaka.digraph.Digraph; import de.odysseus.ithaka.digraph.Digraphs; /** * Order nodes by minimizing arc crossings. */ public class SugiyamaStep2<V,E> { private static final Comparator<SugiyamaArc<?,?>> CMP_ARCS = new Comparator<SugiyamaArc<?,?>>() { @Override public int compare(SugiyamaArc<?,?> arc1, SugiyamaArc<?,?> arc2) { if (arc1.getSource().getPosition() < arc2.getSource().getPosition()) { return -1; } if (arc1.getSource().getPosition() > arc2.getSource().getPosition()) { return 1; } if (arc1.getTarget().getPosition() < arc2.getTarget().getPosition()) { return -1; } if (arc1.getTarget().getPosition() > arc2.getTarget().getPosition()) { return 1; } return 0; } }; private static final Comparator<SugiyamaNode<?>> CMP_CENTER = new Comparator<SugiyamaNode<?>>() { @Override public int compare(SugiyamaNode<?> node1, SugiyamaNode<?> node2) { if (node1.getTemporary() < node2.getTemporary()) { return -1; } if (node1.getTemporary() > node2.getTemporary()) { return 1; } return Double.compare(node1.getPosition(), node2.getPosition()); } }; private static final Comparator<SugiyamaNode<?>> CMP_TEMPORARY = new Comparator<SugiyamaNode<?>>() { @Override public int compare(SugiyamaNode<?> node1, SugiyamaNode<?> node2) { return node1.getTemporary() < node2.getTemporary() ? -1 : node1.getTemporary() > node2.getTemporary() ? 1 : 0; } }; private final int FORGIVENESS = 2; private final int MAX_ROUNDS = 16; /** * Minimize crossings. */ public void minimizeCrossings(Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph, List<List<SugiyamaNode<V>>> layers) { // long startTime = System.currentTimeMillis(); List<List<SugiyamaArc<V,E>>> arcs = createArcLayers(graph, layers); computeTemporary(graph, layers); int score = Integer.MAX_VALUE; Random random = new Random(7); // long currentTime; // System.out.println("preprocessed in " + ((currentTime = System.currentTimeMillis()) - startTime) + " ms"); // startTime = currentTime; for (int i = 0; i < MAX_ROUNDS && score > 0; i++) { for (List<SugiyamaNode<V>> layer : layers) { Collections.shuffle(layer, random); Collections.sort(layer, CMP_TEMPORARY); } int crossings = Integer.MAX_VALUE; sweepDown(graph, layers); for (int forgiveness = FORGIVENESS; forgiveness >= 0;) { sweepUp(graph, layers); sweepDown(graph, layers); int newCrossings = countCrossings(layers, arcs); if (newCrossings >= crossings) { forgiveness--; } else { crossings = newCrossings; if (crossings < score) { for (List<SugiyamaNode<V>> layer : layers) { for (int j = 0; j < layer.size(); j++) { layer.get(j).setIndex(j); } } score = crossings; } } // System.out.print(" " + crossings); } // System.out.println(); } // System.out.println("sweeped (-> " + score + ") in " + ((currentTime = System.currentTimeMillis()) - startTime) + " ms"); // startTime = currentTime; for (List<SugiyamaNode<V>> layer : layers) { Collections.sort(layer, SugiyamaNode.CMP_INDEX); } for (List<SugiyamaNode<V>> layer : layers) { for (int j = 0; j < layer.size(); j++) { layer.get(j).setPosition(j); } } boolean changedByFirstStep, changedBySecondStep = false; do { changedByFirstStep = swapNeighbors(layers, arcs); // score = countCrossings(layers, arcs); // System.out.println("swapped (-> " + score + ") in " + ((currentTime = System.currentTimeMillis()) - startTime) + " ms"); // startTime = currentTime; if (changedByFirstStep || !changedBySecondStep) { changedBySecondStep = combArcs(graph, true); changedBySecondStep |= combArcs(graph.reverse(), false); for (List<SugiyamaNode<V>> layer : layers) { Collections.sort(layer, SugiyamaNode.CMP_INDEX); } for (List<SugiyamaNode<V>> layer : layers) { for (int j = 0; j < layer.size(); j++) { layer.get(j).setPosition(j); } } // score = countCrossings(layers, arcs); // System.out.println("combed (-> " + score + ") in " + ((currentTime = System.currentTimeMillis()) - startTime) + " ms"); // startTime = currentTime; } else { changedBySecondStep = false; } } while (changedBySecondStep); } private boolean swapNeighbors(List<List<SugiyamaNode<V>>> nodes, List<List<SugiyamaArc<V,E>>> arcs) { // TODO changed to go through layers only once (cbe); is this ok? // if (true) return false; boolean changed = false; // boolean swapped; // do { // swapped = false; Iterator<List<SugiyamaNode<V>>> nodeLayers = nodes.iterator(); List<SugiyamaNode<V>> lowerNodes = nodeLayers.next(); Iterator<List<SugiyamaArc<V, E>>> arcLayers = arcs.iterator(); List<SugiyamaArc<V,E>> lowerArcs = null; while (lowerNodes != null) { List<SugiyamaNode<V>> upperNodes = lowerNodes; lowerNodes = nodeLayers.hasNext() ? nodeLayers.next() : null; List<SugiyamaArc<V,E>> upperArcs = lowerArcs; lowerArcs = arcLayers.hasNext() ? arcLayers.next() : null; int before = countCrossings(upperArcs, upperNodes, lowerArcs, lowerNodes); for (int left = 0, right = 1; right < upperNodes.size(); left++, right++) { SugiyamaNode<V> leftNode = upperNodes.get(left); SugiyamaNode<V> rightNode = upperNodes.get(right); double leftPosition = leftNode.getPosition(); double rightPosition = rightNode.getPosition(); leftNode.setPosition(rightPosition); rightNode.setPosition(leftPosition); int after = countCrossings(upperArcs, upperNodes, lowerArcs, lowerNodes); if (before <= after) { leftNode.setPosition(leftPosition); rightNode.setPosition(rightPosition); } else { int leftIndex = leftNode.getIndex(); leftNode.setIndex(rightNode.getIndex()); rightNode.setIndex(leftIndex); upperNodes.set(left, rightNode); upperNodes.set(right, leftNode); changed = true; // swapped = true; before = after; } } } // } while (swapped); return changed; } private int countCrossings(List<SugiyamaArc<V, E>> upperArcs, List<SugiyamaNode<V>> upperNodes, List<SugiyamaArc<V, E>> lowerArcs, List<SugiyamaNode<V>> lowerNodes) { int crossings = 0; if (upperArcs != null) { crossings += countCrossings(upperArcs, upperNodes.size()); } if (lowerArcs != null) { crossings += countCrossings(lowerArcs, lowerNodes.size()); } return crossings; } private boolean combArcs(Digraph<SugiyamaNode<V>,SugiyamaArc<V, E>> graph, boolean downwards) { // if (true) return false; boolean changed = false; for(SugiyamaNode<V> source : graph.vertices()) { if (graph.getOutDegree(source) > 1) { List<List<SugiyamaNode<V>>> targetLayers = new ArrayList<List<SugiyamaNode<V>>>(); List<SugiyamaNode<V>> targetLayer = new ArrayList<SugiyamaNode<V>>(); for (SugiyamaNode<V> target : graph.targets(source)) { targetLayer.add(target); } List<SugiyamaNode<V>> targetList = new ArrayList<SugiyamaNode<V>>(); while (!targetLayer.isEmpty()) { Collections.sort(targetLayer, SugiyamaNode.CMP_INDEX); targetLayers.add(targetLayer); Iterator<SugiyamaNode<V>> targets = targetLayer.iterator(); targetLayer = new ArrayList<SugiyamaNode<V>>(); for (int position = 0, insertIndex = 0; targets.hasNext(); position++) { SugiyamaNode<V> node = targets.next(); if (node.isDummy()) { node.setPosition(-1); SugiyamaNode<V> next = downwards ? node.getLower() : node.getUpper(); targetLayer.add(next); if (!next.isDummy()) { assert (downwards ? next.getLower() : next.getUpper()) == null; if ((downwards ? next.getUpper() : next.getLower()) == null) { if (downwards) next.setUpper(node); else next.setLower(node); } else { if (downwards) next.setLower(node); else next.setUpper(node); } } } else { node.setPosition((downwards ? node.getLower() : node.getUpper()) == null ? position : position - 1); targetList.add(insertIndex++, node); } } }; int[] maxDummyPositions = new int[targetLayers.size()]; Arrays.fill(maxDummyPositions, -1); for (SugiyamaNode<V> target : targetList) { setPositions(target, targetLayers, maxDummyPositions, source, downwards); } for (List<SugiyamaNode<V>> layer : targetLayers) { List<Integer> indices = new ArrayList<Integer>(layer.size()); for (SugiyamaNode<V> node : layer) { indices.add(node.getIndex()); } for (SugiyamaNode<V> node : layer) { int position = (int)Math.round(node.getPosition()); // node.setIndex(indices.get(position)); int index = indices.get(position); if (node.getIndex() != index) { node.setIndex(index); changed = true; } } } } } return changed; } private void setPositions(SugiyamaNode<V> node, List<List<SugiyamaNode<V>>> targetLayers, int[] maxDummyPositions, SugiyamaNode<V> source, boolean downwards) { int firstLayer = source.getLayer() + (downwards ? 1 : -1); int position = (int)Math.round(node.getPosition()); while (downwards ? node.getUpper() != null && node.getUpper() != source : node.getLower() != null && node.getLower() != source) { SugiyamaNode<V> next = downwards ? node.getUpper() : node.getLower(); assert next.isDummy(); if (!node.isDummy()) { if ((downwards ? node.getLower() : node.getUpper()) == null) { if (downwards) node.setUpper(null); else node.setLower(null); } else { if (downwards) { node.setUpper(node.getLower()); node.setLower(null); } else { node.setLower(node.getUpper()); node.setUpper(null); } node.setPosition(node.getPosition() + 1); } } node = next; int layerIndex = Math.abs(node.getLayer() - firstLayer); List<SugiyamaNode<V>> layer = targetLayers.get(layerIndex); while (!layer.get(position).isDummy() || position <= maxDummyPositions[layerIndex]) { if(!layer.get(position).isDummy()) { setPositions(layer.get(position), targetLayers, maxDummyPositions, source, downwards); } position++; } node.setPosition(position); maxDummyPositions[layerIndex] = position; } } private void computeTemporary(Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph, List<List<SugiyamaNode<V>>> layers) { int group = 0; for (Set<SugiyamaNode<V>> component : Digraphs.wcc(graph)) { group++; for (SugiyamaNode<V> node : component) { node.setTemporary(group); } } } private void sweepUp(Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph, List<List<SugiyamaNode<V>>> layers) { // sweep up: sort layers size-2, ..., 0 by down-barycenter (average target position) for (int i = layers.size() - 1; i > 0; i--) { List<SugiyamaNode<V>> lower = layers.get(i); List<SugiyamaNode<V>> upper = layers.get(i-1); for (int j = 0; j < lower.size(); j++) { lower.get(j).setPosition(j); } reorder(graph, upper); } List<SugiyamaNode<V>> top = layers.get(0); for (int j = 0; j < top.size(); j++) { top.get(j).setPosition(j); } } private void sweepDown(Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph, List<List<SugiyamaNode<V>>> layers) { // sweep down: sort layers 1, ..., size-1 by up-barycenter (average source position) for (int i = 0; i < layers.size() - 1; i++) { List<SugiyamaNode<V>> upper = layers.get(i); List<SugiyamaNode<V>> lower = layers.get(i+1); for (int j = 0; j < upper.size(); j++) { upper.get(j).setPosition(j); } reorder(graph.reverse(), lower); } List<SugiyamaNode<V>> bottom = layers.get(layers.size() - 1); for (int j = 0; j < bottom.size(); j++) { bottom.get(j).setPosition(j); } } private List<List<SugiyamaArc<V,E>>> createArcLayers(Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph, List<List<SugiyamaNode<V>>> layers) { List<List<SugiyamaArc<V,E>>> result = new ArrayList<List<SugiyamaArc<V,E>>>(layers.size() - 1); for (int i = 0; i < layers.size() - 1; i++) { List<SugiyamaArc<V,E>> list = new ArrayList<SugiyamaArc<V,E>>(); for (SugiyamaNode<V> source : layers.get(i)) { for (SugiyamaNode<V> target : graph.targets(source)) { list.add(graph.get(source, target)); } } result.add(list); } return result; } private int countCrossings(List<List<SugiyamaNode<V>>> nodes, List<List<SugiyamaArc<V,E>>> arcs) { int crossings = 0; Iterator<List<SugiyamaNode<V>>> south = nodes.iterator(); south.next(); for (List<SugiyamaArc<V,E>> list : arcs) { crossings += countCrossings(list, south.next().size()); } return crossings; } /** * W. Barth et al., Bilayer Cross Counting, JGAA, 8(2) 179-194 (2004) * @param q number of southern nodes (max. target position) */ private int countCrossings(List<SugiyamaArc<V,E>> arcs, int q) { Collections.sort(arcs, CMP_ARCS); int crossings = 0; int firstLeafIndex = 1; while (firstLeafIndex < q) { firstLeafIndex *= 2; } firstLeafIndex -= 1; int[] tree = new int[firstLeafIndex + q + 2]; for (SugiyamaArc<V,E> arc : arcs) { int index = firstLeafIndex + (int)arc.getTarget().getPosition(); tree[index]++; while (index > 0) { if (index % 2 != 0) { crossings += tree[index + 1]; } index = (index - 1) / 2; tree[index]++; } } return crossings; } /** * Compute center of specified layer node. * This implementation computes the baricenter. * May be overridden to use another method, e.g. median. * @param graph * @param source * @return center of specified layer node */ private double center(Digraph<SugiyamaNode<V>,?> graph, SugiyamaNode<V> source) { double weight = 0; for (SugiyamaNode<V> target : graph.targets(source)) { weight += target.getPosition(); } return weight / graph.getOutDegree(source); } /** * Reorder a level by centric method by setting positions. * @param layer the level to be reordered by barycenter */ private void reorder(Digraph<SugiyamaNode<V>,?> graph, List<SugiyamaNode<V>> layer) { double maxCenter = 0.0; for (SugiyamaNode<V> source : layer) { double center = graph.getOutDegree(source) > 0 ? center(graph, source) : maxCenter; if (center > maxCenter) { maxCenter = center; } source.setPosition(center); } Collections.sort(layer, CMP_CENTER); } }