/*
* This file is part of the Trickl Open Source Libraries.
*
* Trickl Open Source Libraries - http://open.trickl.com/
*
* Copyright (C) 2011 Tim Gee.
*
* Trickl Open Source Libraries are free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Trickl Open Source Libraries are distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this project. If not, see <http://www.gnu.org/licenses/>.
*/
package com.trickl.graph.planar;
import com.trickl.graph.edges.DirectedEdge;
import com.vividsolutions.jts.geom.Coordinate;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
public class ChrobakPayneLayout<V, E> implements PlanarLayout<V> {
private static class VertexDetail<V> {
private V left = null;
private V right = null;
private int seen_as_right = 0;
private int seen = 0;
private int delta_x = 0;
private int y = 0;
private int x = 0;
private boolean installed = false;
}
private PlanarGraph<V, E> graph;
private double scale;
Map<V, Coordinate> drawing;
PlanarCanonicalOrdering ordering;
public ChrobakPayneLayout(PlanarGraph<V, E> graph) {
this(graph, 1.0);
}
public ChrobakPayneLayout(PlanarGraph<V, E> graph, double scale) {
// This feels hacky. Having to make a copy of the graph so we can
// make it maximal planar and get a canonical ordering for the algorithm
DoublyConnectedEdgeList<V, E, Object> graphCopy = new DoublyConnectedEdgeList<V, E, Object>(graph, Object.class);
MaximalPlanar<V, E> maximalPlanar = new MaximalPlanar<V, E>();
maximalPlanar.makeMaximalPlanar(graphCopy);
ordering = new MaximalPlanarCanonicalOrdering<V, E>();
this.graph = graphCopy;
this.scale = scale;
layout();
}
public ChrobakPayneLayout(PlanarGraph<V, E> graph, PlanarCanonicalOrdering<V, E> ordering, double scale) {
this.ordering = ordering;
this.graph = graph;
this.scale = scale;
layout();
}
@Override
public Coordinate getCoordinate(V vertex) {
return drawing.get(vertex);
}
private void layout() {
DirectedEdge<V> boundary = graph.getBoundary();
List<V> order = ordering.getOrder(graph, boundary.getSource());
Map<V, VertexDetail<V>> vertexDetails = new HashMap<V, VertexDetail<V>>();
for (V vertex : graph.vertexSet()) {
vertexDetails.put(vertex, new VertexDetail<V>());
}
int timestamp = 1;
List<V> installedNeighbours = new LinkedList<V>();
V firstVertex = order.get(0);
V secondVertex = order.get(1);
V thirdVertex = order.get(2);
VertexDetail<V> firstDetail = vertexDetails.get(firstVertex);
VertexDetail<V> secondDetail = vertexDetails.get(secondVertex);
VertexDetail<V> thirdDetail = vertexDetails.get(thirdVertex);
secondDetail.delta_x = 1;
thirdDetail.delta_x = 1;
firstDetail.y = 0;
secondDetail.y = 0;
thirdDetail.y = 1;
firstDetail.right = thirdVertex;
thirdDetail.right = secondVertex;
firstDetail.installed = secondDetail.installed = thirdDetail.installed = true;
for (V vertex : order.subList(3, order.size())) {
VertexDetail<V> detail = vertexDetails.get(vertex);
// First, find the leftmost and rightmost neighbor of v on the outer
// cycle of the embedding.
// Note: since we're moving clockwise through the edges adjacent to v,
// we're actually moving from right to left among v's neighbors on the
// outer face (since v will be installed above them all) looking for
// the leftmost and rightmost installed neighbours
V leftmost = null;
V rightmost = null;
installedNeighbours.clear();
V prevVertex = null;
for (E edge : graph.edgesOf(vertex)) {
V currentVertex = graph.getEdgeSource(edge).equals(vertex)
? graph.getEdgeTarget(edge) : graph.getEdgeSource(edge);
VertexDetail<V> currentDetail = vertexDetails.get(currentVertex);
// Skip any self-loops or parallel edges
if (currentVertex.equals(vertex) || currentVertex.equals(prevVertex)) {
continue;
}
if (currentDetail.installed) {
currentDetail.seen = timestamp;
if (currentDetail.right != null) {
vertexDetails.get(currentDetail.right).seen_as_right = timestamp;
}
installedNeighbours.add(currentVertex);
}
prevVertex = currentVertex;
}
for (V vi : installedNeighbours) {
VertexDetail<V> installeddetail = vertexDetails.get(vi);
if (installeddetail.right == null
|| vertexDetails.get(installeddetail.right).seen != timestamp) {
rightmost = vi;
}
if (installeddetail.seen_as_right != timestamp) {
leftmost = vi;
}
}
++timestamp;
// Stretch gaps
VertexDetail<V> rightmostDetail = vertexDetails.get(rightmost);
VertexDetail<V> leftmostDetail = vertexDetails.get(leftmost);
VertexDetail<V> leftmostRightDetail = vertexDetails.get(leftmostDetail.right);
++leftmostRightDetail.delta_x;
++rightmostDetail.delta_x;
//adjust offsets
int deltaPQ = 0;
V stopVertex = rightmostDetail.right;
for (V temp = leftmostDetail.right;
!(temp == stopVertex || (temp != null && temp.equals(stopVertex)));
temp = vertexDetails.get(temp).right) {
deltaPQ += vertexDetails.get(temp).delta_x;
}
detail.delta_x = ((rightmostDetail.y + deltaPQ) - leftmostDetail.y) / 2;
detail.y = leftmostDetail.y + detail.delta_x;
rightmostDetail.delta_x = deltaPQ - detail.delta_x;
boolean areLeftmostAndRightmostAdjacent = leftmostDetail.right.equals(rightmost);
if (!areLeftmostAndRightmostAdjacent) {
leftmostRightDetail.delta_x -= detail.delta_x;
}
// install v
if (!areLeftmostAndRightmostAdjacent) {
detail.left = leftmostDetail.right;
V nextToRightMost = null;
for (V temp = leftmost; !temp.equals(rightmost);
temp = vertexDetails.get(temp).right) {
nextToRightMost = temp;
}
vertexDetails.get(nextToRightMost).right = null;
} else {
detail.left = null;
}
leftmostDetail.right = vertex;
detail.right = rightmost;
detail.installed = true;
}
accumulateOffsets(order.iterator().next(), 0, vertexDetails);
drawing = new HashMap<V, Coordinate>();
for (V vi : graph.vertexSet()) {
VertexDetail<V> drawdetail = vertexDetails.get(vi);
drawing.put(vi, new Coordinate(drawdetail.x * scale, drawdetail.y * scale));
}
}
private void accumulateOffsets(V v,
int offset,
Map<V, VertexDetail<V>> details) {
if (v != null) {
VertexDetail<V> detail = details.get(v);
detail.x += detail.delta_x + offset;
accumulateOffsets(detail.left, detail.x, details);
accumulateOffsets(detail.right, detail.x, details);
}
}
}