/*
* 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.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import de.odysseus.ithaka.digraph.Digraph;
import de.odysseus.ithaka.digraph.layout.DigraphLayoutPoint;
/**
* Route arcs
*/
public class SugiyamaStep4<V,E> {
public void routeArcs(Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph, List<List<SugiyamaNode<V>>> layers) {
computeArcSlots(graph);
computeArcBends(graph, layers);
}
private void computeArcSlots(Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph) {
List<SugiyamaArc<V,E>> arcs = new ArrayList<SugiyamaArc<V,E>>(graph.getEdgeCount());
// collect arcs
for (SugiyamaNode<V> source : graph.vertices()) {
for (SugiyamaNode<V> target : graph.targets(source)) {
arcs.add(graph.get(source, target));
}
}
// sort arcs
Collections.sort(arcs, new Comparator<SugiyamaArc<V,E>>() {
@Override
public int compare(SugiyamaArc<V,E> e1, SugiyamaArc<V,E> e2) {
double key1 = e1.getSource().getIndex() + e1.getTarget().getIndex();
double key2 = e2.getSource().getIndex() + e2.getTarget().getIndex();
return key1 < key2 ? -1 : key1 > key2 ? 1 : 0;
}
});
// assign slot numbers
for (SugiyamaArc<V,E> arc : arcs) {
arc.setSourceSlot(arc.getSource().nextLowerSlot());
arc.setTargetSlot(arc.getTarget().nextUpperSlot());
if ((int)arc.getSource().getPosition() == (int)arc.getTarget().getPosition()) { // assign center slot
arc.getSource().setLowerCenterSlot(arc.getSourceSlot());
arc.getTarget().setUpperCenterSlot(arc.getTargetSlot());
}
arc = arc.getBackArc();
if (arc != null) {
arc.setSourceSlot(arc.getSource().nextLowerSlot());
arc.setTargetSlot(arc.getTarget().nextUpperSlot());
}
}
}
private void computeArcBends(final Digraph<SugiyamaNode<V>,SugiyamaArc<V,E>> graph, final List<List<SugiyamaNode<V>>> layers) {
List<SugiyamaArc<V,E>> arcs = new ArrayList<SugiyamaArc<V,E>>(graph.getEdgeCount());
// collect arcs
for (SugiyamaNode<V> source : graph.vertices()) {
for (SugiyamaNode<V> target : graph.targets(source)) {
SugiyamaArc<V,E> arc = graph.get(source, target);
arcs.add(arc);
if (arc.getBackArc() != null) {
arcs.add(arc.getBackArc());
}
}
}
// sort arcs: layers top down, left to right
Collections.sort(arcs, new Comparator<SugiyamaArc<V,E>>() {
@Override
public int compare(SugiyamaArc<V,E> e1, SugiyamaArc<V,E> e2) {
int i1 = e1.getSource().getLayer();
int i2 = e2.getSource().getLayer();
if (i1 < i2) {
return -1;
} else if (i1 > i2) {
return 1;
} else {
int j1 = e1.getSource().getIndex();
int j2 = e2.getSource().getIndex();
if (j1 < j2) {
return -1;
} else if (j1 > j2) {
return 1;
} else {
int k1 = e1.getSourceSlot();
int k2 = e2.getSourceSlot();
if (k1 < k2) {
return -1;
} else if (k1 > k2) {
return 1;
}
return 0;
}
}
}
});
List<SugiyamaNode<V>> layer = null;
SugiyamaNode<V> last = null;
Border border = null;
for (boolean leftToRight : new boolean[] {true, false}) {
// System.out.println("left to right: " + leftToRight);
if (!leftToRight) {
Collections.reverse(arcs);
last = null;
}
for (SugiyamaArc<V,E> arc : arcs) {
SugiyamaNode<V> source = arc.getSource();
SugiyamaNode<V> target = arc.getTarget();
DigraphLayoutPoint sourceSlotPoint = source.getLowerSlotPoint(arc.getSourceSlot()); // segment start
DigraphLayoutPoint targetSlotPoint = target.getUpperSlotPoint(arc.getTargetSlot()); // segment end
if (source != last) { // first/last arc of source
if (last == null || last.getLayer() != source.getLayer()) { // first/last arc on layer
layer = layers.get(source.getLayer());
last = null;
border = new Border(leftToRight);
}
if (leftToRight) { // add nodes from last (incl.) to source (excl.) to border
for (int index = last == null ? 0 : last.getIndex(); index < source.getIndex(); index++) {
SugiyamaNode<V> node = layer.get(index);
int borderPointX = node.getPoint().x + node.getDimension().w - 1;
int borderPointY = node.getPoint().y + node.getDimension().h - 1;
border.add(new DigraphLayoutPoint(borderPointX, borderPointY)); //, sourceSlotPoint, targetSlotPoint);
}
} else {
for (int index = last == null ? layer.size() - 1 : last.getIndex(); index > source.getIndex(); index--) {
SugiyamaNode<V> node = layer.get(index);
int borderPointX = node.getPoint().x;
int borderPointY = node.getPoint().y + node.getDimension().h - 1;
border.add(new DigraphLayoutPoint(borderPointX, borderPointY)); //, sourceSlotPoint, targetSlotPoint);
}
}
}
// System.out.println("border=" + border + ", from " + sourceSlotPoint + " to " + targetSlotPoint);
int bendY = border.getBendY(sourceSlotPoint, targetSlotPoint);
if (bendY > sourceSlotPoint.y) {
// System.out.println("bad: " + arc);
DigraphLayoutPoint bend = new DigraphLayoutPoint(sourceSlotPoint.x, bendY);
LinkedList<DigraphLayoutPoint> bends = new LinkedList<DigraphLayoutPoint>();
bends.add(bend);
arc.setBendPoints(bends);
border.add(bend);
}
last = source;
}
}
}
private class Border {
int aura = 4, sign;
List<DigraphLayoutPoint> borderPoints = new ArrayList<DigraphLayoutPoint>(); // list of points with decreasing y values and decreasing slope (i.e. convex)
Border(boolean leftToRight) {
sign = leftToRight ? 1 : -1;
}
void add(DigraphLayoutPoint borderPoint) {
int index = borderPoints.size();
if (!borderPoints.isEmpty()) {
DigraphLayoutPoint last = borderPoints.get(index - 1);
if (sign * borderPoint.x < sign * last.x) {
return; // ignore (occurs when last was a bend)
}
}
while (--index >= 0 && borderPoints.get(index).y <= borderPoint.y) {
borderPoints.remove(index); // remove trailing points with y values too small
}
while (--index > 0) {
DigraphLayoutPoint current = borderPoints.get(index);
DigraphLayoutPoint previous = borderPoints.get(index - 1);
if (sign * Line2D.relativeCCW(previous.x, previous.y, current.x, current.y, borderPoint.x, borderPoint.y) <= 0) {
borderPoints.remove(index); // remove trailing points with slope values too small
} else {
break;
}
}
borderPoints.add(borderPoint);
}
int getBendY(DigraphLayoutPoint sourceSlotPoint, DigraphLayoutPoint targetSlotPoint) {
int bendY = sourceSlotPoint.y;
if (sign * sourceSlotPoint.x > sign * targetSlotPoint.x) {
for (DigraphLayoutPoint borderPoint : borderPoints) {
if (borderPoint.y > bendY &&
sign * borderPoint.x > sign * targetSlotPoint.x &&
sign * Line2D.relativeCCW(sourceSlotPoint.x, sourceSlotPoint.y, targetSlotPoint.x, targetSlotPoint.y, borderPoint.x + sign * aura, borderPoint.y + aura) >= 0) {
bendY = borderPoint.y;
}
}
}
return bendY;
}
@Override
public String toString() {
String result = "[ ";
for (DigraphLayoutPoint borderPoint : borderPoints) {
result += borderPoint + " ";
}
return result + "]";
}
}
}