/* * 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.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import de.odysseus.ithaka.digraph.Digraph; import de.odysseus.ithaka.digraph.DigraphFactory; import de.odysseus.ithaka.digraph.DoubledDigraph; import de.odysseus.ithaka.digraph.DoubledDigraphAdapter; import de.odysseus.ithaka.digraph.MapDigraph; /** * Sugiyama 3: adjust node positions on layers. * Brandes/Koepf: "Fast and Simple Horizontal Coordinate Assignment" (Symposium on Graph Drawing, 2001) */ public class SugiyamaStep3<V> { static final int LEFTMOST_UPPER = 0; static final int RIGHTMOST_UPPER = 1; static final int LEFTMOST_LOWER = 2; static final int RIGHTMOST_LOWER = 3; static class BrandesKoepfNode { int id; Object data; // debug BrandesKoepfNode innerSegmentSource; BrandesKoepfNode root; BrandesKoepfNode align; BrandesKoepfNode sink, sink2; BrandesKoepfNode pred, succ; int shift; int pos; int delta; int x; int[] result = new int[4]; @Override public String toString() { return data == null ? "<>" : data.toString(); } } public static final Comparator<BrandesKoepfNode> CMP_ID = new Comparator<BrandesKoepfNode>() { @Override public int compare(BrandesKoepfNode o1, BrandesKoepfNode o2) { return o1.id < o2.id ? -1 : o1.id > o2.id ? 1 : 0; } }; public static final Comparator<BrandesKoepfNode> CMP_POS = new Comparator<BrandesKoepfNode>() { @Override public int compare(BrandesKoepfNode o1, BrandesKoepfNode o2) { return o1.pos < o2.pos ? -1 : o1.pos > o2.pos ? 1 : 0; } }; static DigraphFactory<Digraph<BrandesKoepfNode,Boolean>> FACTORY = new DigraphFactory<Digraph<BrandesKoepfNode,Boolean>>() { @Override public Digraph<BrandesKoepfNode,Boolean> create() { return new MapDigraph<BrandesKoepfNode,Boolean>(CMP_ID, CMP_POS); } }; private int delta; public SugiyamaStep3(int delta) { this.delta = delta; } public void adjustNodePositions(Digraph<SugiyamaNode<V>,?> graph, List<List<SugiyamaNode<V>>> layers) { DoubledDigraph<BrandesKoepfNode,Boolean> brandesKoepfGraph = DoubledDigraphAdapter.getAdapterFactory(FACTORY).create(); Map<SugiyamaNode<V>,BrandesKoepfNode> map = new HashMap<SugiyamaNode<V>,BrandesKoepfNode>(); int id = 0; for (SugiyamaNode<V> node : graph.vertices()) { BrandesKoepfNode v = new BrandesKoepfNode(); v.id = ++id; v.data = node.getVertex(); v.pos = node.getIndex(); v.delta = node.getDimension().w / 2; map.put(node, v); brandesKoepfGraph.add(v); } for (SugiyamaNode<V> source : graph.vertices()) { for (SugiyamaNode<V> target : graph.targets(source)) { BrandesKoepfNode v = map.get(source); BrandesKoepfNode w = map.get(target); if (source.isDummy() && target.isDummy()) { w.innerSegmentSource = v; } brandesKoepfGraph.put(v, w, Boolean.FALSE); } } List<List<BrandesKoepfNode>> brandesKoepfLayers = new ArrayList<List<BrandesKoepfNode>>(layers.size()); for (List<SugiyamaNode<V>> layer : layers) { List<BrandesKoepfNode> brandesKoepfLayer = new ArrayList<BrandesKoepfNode>(layer.size()); BrandesKoepfNode pred = null; for (SugiyamaNode<V> node : layer) { BrandesKoepfNode brandesKoepfNode = map.get(node); if (pred != null) { brandesKoepfNode.pred = pred; pred.succ = brandesKoepfNode; } brandesKoepfLayer.add(brandesKoepfNode); pred = brandesKoepfNode; } brandesKoepfLayers.add(brandesKoepfLayer); } runAlgorithm(brandesKoepfGraph, brandesKoepfLayers); int min = Integer.MAX_VALUE; for (BrandesKoepfNode v : brandesKoepfGraph.vertices()) { min = Math.min(min, v.x - v.delta); } for (SugiyamaNode<V> node : graph.vertices()) { node.setPosition(map.get(node).x - min); } } private void runAlgorithm(Digraph<BrandesKoepfNode,Boolean> graph, List<List<BrandesKoepfNode>> layers) { preprocessing(graph, layers); int[][] ranges = new int[4][]; ranges[LEFTMOST_UPPER] = computeAssignment(graph, layers, LEFTMOST_UPPER); ranges[RIGHTMOST_UPPER] = computeAssignment(graph, layers, RIGHTMOST_UPPER); ranges[LEFTMOST_LOWER] = computeAssignment(graph, layers, LEFTMOST_LOWER); ranges[RIGHTMOST_LOWER] = computeAssignment(graph, layers, RIGHTMOST_LOWER); balance(graph, ranges); } private void shift(Digraph<BrandesKoepfNode,Boolean> graph, int assignment, int value) { if (value != 0) { for (BrandesKoepfNode v : graph.vertices()) { v.result[assignment] += value; } } } private void balance(Digraph<BrandesKoepfNode,Boolean> graph, int[][] ranges) { int minWidth = Integer.MAX_VALUE; int minWidthAssignment = -1; if (ranges[LEFTMOST_UPPER][1] - ranges[LEFTMOST_UPPER][0] < minWidth) { minWidth = ranges[LEFTMOST_UPPER][1] - ranges[LEFTMOST_UPPER][0]; minWidthAssignment = LEFTMOST_UPPER; } if (ranges[RIGHTMOST_UPPER][1] - ranges[RIGHTMOST_UPPER][0] < minWidth) { minWidth = ranges[RIGHTMOST_UPPER][1] - ranges[RIGHTMOST_UPPER][0]; minWidthAssignment = RIGHTMOST_UPPER; } if (ranges[LEFTMOST_LOWER][1] - ranges[LEFTMOST_LOWER][0] < minWidth) { minWidth = ranges[LEFTMOST_LOWER][1] - ranges[LEFTMOST_LOWER][0]; minWidthAssignment = LEFTMOST_LOWER; } if (ranges[RIGHTMOST_LOWER][1] - ranges[RIGHTMOST_LOWER][0] < minWidth) { minWidth = ranges[RIGHTMOST_LOWER][1] - ranges[RIGHTMOST_LOWER][0]; minWidthAssignment = RIGHTMOST_LOWER; } shift(graph, LEFTMOST_UPPER, ranges[minWidthAssignment][0] - ranges[LEFTMOST_UPPER][0]); shift(graph, LEFTMOST_LOWER, ranges[minWidthAssignment][0] - ranges[LEFTMOST_LOWER][0]); shift(graph, RIGHTMOST_UPPER, ranges[minWidthAssignment][1] - ranges[RIGHTMOST_UPPER][1]); shift(graph, RIGHTMOST_LOWER, ranges[minWidthAssignment][1] - ranges[RIGHTMOST_LOWER][1]); for (BrandesKoepfNode v : graph.vertices()) { Arrays.sort(v.result); v.x = (v.result[1] + v.result[2]) / 2; } } private int[] computeAssignment(Digraph<BrandesKoepfNode,Boolean> graph, List<List<BrandesKoepfNode>> layers, int mode) { verticalAlignment(graph, layers, mode); horizontalCompaction(graph, mode); int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (BrandesKoepfNode v : graph.vertices()) { v.result[mode] = v.x; if (v.x > max) { max = v.x; } if (v.x < min) { min = v.x; } } return new int[]{min, max}; } private void preprocessing(Digraph<BrandesKoepfNode,Boolean> graph, List<List<BrandesKoepfNode>> layers) { Digraph<BrandesKoepfNode,Boolean> reverse = graph.reverse(); for (int i = 1; i < layers.size() - 2; i++) { List<BrandesKoepfNode> layer = layers.get(i+1); int k0 = 0; int l = 0; for (BrandesKoepfNode v : layer) { int l1 = v.pos; if (l1 == layer.size() - 1 || v.innerSegmentSource != null) { int k1 = layers.get(i).size() - 1; if (v.innerSegmentSource != null) { k1 = v.innerSegmentSource.pos; } while (l <= l1) { for (BrandesKoepfNode u : reverse.targets(layer.get(l))) { int k = u.pos; if (k < k0 || k > k1) { reverse.put(layer.get(l), u, Boolean.TRUE); } } l++; } k0 = k1; } } } } private void verticalAlignment(Digraph<BrandesKoepfNode,Boolean> graph, List<List<BrandesKoepfNode>> layers, int mode) { for (BrandesKoepfNode v : graph.vertices()) { v.root = v; v.align = v; } switch (mode) { case LEFTMOST_UPPER: for (int i = 1; i < layers.size(); i++) { verticalAlignmentLeft(graph.reverse(), layers.get(i)); } break; case RIGHTMOST_UPPER: for (int i = 1; i < layers.size(); i++) { verticalAlignmentRight(graph.reverse(), layers.get(i)); } break; case LEFTMOST_LOWER: for (int i = layers.size() - 2; i >= 0; i--) { verticalAlignmentLeft(graph, layers.get(i)); } break; case RIGHTMOST_LOWER: for (int i = layers.size() - 2; i >= 0; i--) { verticalAlignmentRight(graph, layers.get(i)); } break; } } private void verticalAlignmentLeft(Digraph<BrandesKoepfNode,Boolean> graph, List<BrandesKoepfNode> layer) { int r = Integer.MIN_VALUE; for (BrandesKoepfNode v : layer) { int d = graph.getOutDegree(v); if (d > 0) { int m = 0; BrandesKoepfNode u1 = null; for (BrandesKoepfNode u : graph.targets(v)) { // sorted if (m == (d-1)/2) { u1 = u; } if (m == d/2) { if (!graph.get(v, u1) && u1.pos > r) { u1.align = v; v.root = u1.root; v.align = v.root; r = u1.pos; } else if (u != u1 && !graph.get(v, u) && u.pos > r) { u.align = v; v.root = u.root; v.align = v.root; r = u.pos; } break; } m++; } } } } private void verticalAlignmentRight(Digraph<BrandesKoepfNode,Boolean> graph, List<BrandesKoepfNode> layer) { int r = Integer.MAX_VALUE; for (int k = layer.size() - 1; k >= 0; k--) { BrandesKoepfNode v = layer.get(k); int d = graph.getOutDegree(v); if (d > 0) { int m = 0; BrandesKoepfNode u1 = null; for (BrandesKoepfNode u : graph.targets(v)) { // sorted if (m == (d-1)/2) { u1 = u; } if (m == d/2) { if (!graph.get(v, u) && u.pos < r) { u.align = v; v.root = u.root; v.align = v.root; r = u.pos; } else if (u1 != u && !graph.get(v, u1) && u1.pos < r) { u1.align = v; v.root = u1.root; v.align = v.root; r = u1.pos; } break; } m++; } } } } private void horizontalCompaction(Digraph<BrandesKoepfNode,Boolean> graph, int mode) { switch (mode) { case LEFTMOST_LOWER: case LEFTMOST_UPPER: horizontalCompactionLeft(graph); break; case RIGHTMOST_LOWER: case RIGHTMOST_UPPER: horizontalCompactionRight(graph); break; } } private void placeBlockLeft(BrandesKoepfNode v) { if (v.x == Integer.MIN_VALUE) { v.x = 0; BrandesKoepfNode w = v; do { if (w.pred != null) { BrandesKoepfNode u = w.pred.root; placeBlockLeft(u); if (v.sink == v) { v.sink = u.sink; } int delta = this.delta + w.pred.delta + w.delta; if (v.sink != u.sink) { u.sink.sink2 = v.sink; // TODO Why was this line commented out in PREVIEW < 4? u.sink.shift = Math.min(u.sink.shift, v.x - u.x - delta); } else { v.x = Math.max(v.x, u.x + delta); } } w = w.align; } while (w != v); } } private void horizontalCompactionLeft(Digraph<BrandesKoepfNode,Boolean> graph) { for (BrandesKoepfNode v : graph.vertices()) { v.sink = v; v.sink2 = v; v.shift = Integer.MAX_VALUE; v.x = Integer.MIN_VALUE; // undefined } for (BrandesKoepfNode v : graph.vertices()) { if (v.root == v) { placeBlockLeft(v); } } for (BrandesKoepfNode v : graph.vertices()) { if (v.root == v) { BrandesKoepfNode sink = v.sink; do { if (sink.shift < Integer.MAX_VALUE) { v.x += sink.shift; } sink = sink.sink2; } while (sink.sink2 != sink); } } for (BrandesKoepfNode v : graph.vertices()) { v.x = v.root.x; } } private void placeBlockRight(BrandesKoepfNode v) { if (v.x == Integer.MIN_VALUE) { v.x = 0; BrandesKoepfNode w = v; do { if (w.succ != null) { BrandesKoepfNode u = w.succ.root; placeBlockRight(u); if (v.sink == v) { v.sink = u.sink; } int delta = this.delta + w.succ.delta + w.delta; if (v.sink != u.sink) { u.sink.sink2 = v.sink; u.sink.shift = Math.min(u.sink.shift, u.x - v.x - delta); } else { v.x = Math.min(v.x, u.x - delta); } } w = w.align; } while (w != v); } } private void horizontalCompactionRight(Digraph<BrandesKoepfNode,Boolean> graph) { for (BrandesKoepfNode v : graph.vertices()) { v.sink = v; v.sink2 = v; v.shift = Integer.MAX_VALUE; v.x = Integer.MIN_VALUE; // undefined } for (BrandesKoepfNode v : graph.vertices()) { if (v.root == v) { placeBlockRight(v); } } for (BrandesKoepfNode v : graph.vertices()) { if (v.root == v) { BrandesKoepfNode sink = v.sink; do { if (sink.shift < Integer.MAX_VALUE) { v.x -= sink.shift; } sink = sink.sink2; } while (sink.sink2 != sink); } } for (BrandesKoepfNode v : graph.vertices()) { v.x = v.root.x; } } }