package org.openlca.app.editors.graphical.layout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.openlca.app.editors.graphical.model.Link;
import org.openlca.app.editors.graphical.model.ProcessNode;
public class MinimalTreeLayout {
private final Map<Integer, Integer> heights = new HashMap<>();
private final Map<Integer, Integer> widths = new HashMap<>();
private void applyLayout(ProcessNode[] processFigures, Graph graph) {
Map<Long, ProcessNode> nodes = new HashMap<>();
for (ProcessNode node : processFigures)
nodes.put(node.process.getId(), node);
// build "width-map"
for (int x = graph.minimumX; x <= graph.maximumX; x++) {
widths.put(x, 0);
for (int y = graph.minimumY; y <= graph.maximumY; y++) {
Node node = graph.coordinateSystem.get(new Point(x, y));
if (node == null)
continue;
int width = Math.max(widths.get(x), nodes.get(node.key).getSize().width);
widths.put(x, width);
}
}
// build "height-map"
for (int y = graph.minimumY; y <= graph.maximumY; y++) {
heights.put(y, 0);
for (int x = graph.minimumX; x <= graph.maximumX; x++) {
Node node = graph.coordinateSystem.get(new Point(x, y));
if (node == null)
continue;
int height = Math.max(heights.get(y), nodes.get(node.key).getSize().height);
heights.put(y, height);
}
}
int xPosition = 25;
for (int x = graph.minimumX; x <= graph.maximumX; x++) {
if (x > graph.minimumX && widths.get(x - 1) > 0)
xPosition += widths.get(x - 1) + LayoutManager.H_SPACE;
int yPosition = 25;
for (int y = graph.minimumY; y <= graph.maximumY; y++) {
Node node = graph.coordinateSystem.get(new Point(x, y));
if (y > graph.minimumY && heights.get(y - 1) > 0)
yPosition += heights.get(y - 1) + LayoutManager.V_SPACE;
if (node == null)
continue;
ProcessNode pNode = nodes.get(node.key);
pNode.setXyLayoutConstraints(new Rectangle(xPosition, yPosition,
pNode.getSize().width, pNode.getSize().height));
}
}
}
private Graph buildGraph(ProcessNode[] processNodes) {
Graph graph = new Graph();
Map<Long, Node> nodes = graph.nodes;
for (ProcessNode processNode : processNodes) {
long id = processNode.process.getId();
nodes.put(id, new Node(id));
}
for (ProcessNode processNode : processNodes) {
for (Link link : processNode.links) {
long sourceId = link.sourceNode.process.getId();
long targetId = link.targetNode.process.getId();
if (nodes.get(sourceId) == null || nodes.get(targetId) == null)
continue;
// add edge to source node
nodes.get(sourceId).outgoingEdges.add(new Edge(nodes.get(sourceId), nodes.get(targetId)));
// add edge to target node
nodes.get(targetId).incomingEdges.add(new Edge(nodes.get(sourceId), nodes.get(targetId)));
}
}
return graph;
}
public void layout(ProcessNode[] processNodes) {
Graph graph = buildGraph(processNodes);
graph.initLocations();
graph.optimize();
applyLayout(processNodes, graph);
widths.clear();
heights.clear();
}
class Edge {
private Node start;
private Node end;
public Edge(Node start, Node end) {
this.start = start;
this.end = end;
}
int getWeight() {
@SuppressWarnings("deprecation")
int weight = start.location.getDistanceOrthogonal(end.location);
if (start.location.x > end.location.x)
weight += 2;
return weight;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Edge))
return false;
Edge edge = (Edge) obj;
if (!start.equals(edge.start))
return false;
if (!end.equals(edge.end))
return false;
return true;
}
}
class Graph {
private Map<Point, Node> coordinateSystem = new HashMap<>();
private Map<Long, Node> nodes = new HashMap<>();
int maximumX = 0;
int maximumY = 0;
int minimumX = 0;
int minimumY = 0;
private void setLocation(Node node, int xStart, int yStart) {
int x = xStart;
int y = yStart;
int addition = 1;
boolean add = false;
while (coordinateSystem.get(new Point(x, y)) != null) {
if (!add) {
y = yStart - addition;
add = true;
} else {
y = yStart + addition;
add = false;
addition++;
}
}
node.location = new Point(x, y);
checkNewMinimumsAndMaximums(x, y);
coordinateSystem.put(new Point(x, y), node);
for (Edge edge : node.incomingEdges)
if (edge.start.location == null)
setLocation(edge.start, x - 1, y);
for (Edge edge : node.outgoingEdges)
if (edge.end.location == null)
setLocation(edge.end, x + 1, y);
}
void checkNewMinimumsAndMaximums(int x, int y) {
if (x < minimumX)
minimumX = x;
else if (x > maximumX)
maximumX = x;
if (y < minimumY)
minimumY = y;
else if (y > maximumY)
maximumY = y;
}
void initLocations() {
for (Node node : nodes.values()) {
if (node.location != null)
continue;
setLocation(node, 0, 0);
}
}
void optimize() {
boolean stopOptimizing = false;
while (!stopOptimizing) {
stopOptimizing = true;
for (Node node : nodes.values()) {
boolean stop = false;
while (!stop) {
boolean result = optimize(node);
if (!result) {
stopOptimizing = false;
} else {
stop = true;
}
}
}
}
}
private boolean optimize(Node node) {
Edge heaviestEdge = node.getHeaviestEdge();
if (heaviestEdge == null)
return true;
int oldWeightSum = node.getEdgesWeightSum();
Point oldLocation = node.location.getCopy();
Edge lightestEdge = node.getLightestEdge();
boolean nodeIsStart = heaviestEdge.start.equals(node);
Node n = nodeIsStart ? heaviestEdge.end : heaviestEdge.start;
int x = n.location.x + (nodeIsStart ? 1 : -1);
int y = n.location.y;
boolean stopTrying = false;
boolean canMove = false;
int divisor = 2;
while (!stopTrying) {
Node nearest = lightestEdge.start.key == node.key ? lightestEdge.end : lightestEdge.start;
Node farest = heaviestEdge.start.key == node.key ? heaviestEdge.end : heaviestEdge.start;
if (!lightestEdge.equals(heaviestEdge)) {
x = Math.min(nearest.location.x, farest.location.x);
x += Math.abs(nearest.location.x - farest.location.x) / divisor;
y = Math.min(nearest.location.y, farest.location.y);
y += Math.abs(nearest.location.y - farest.location.y) / divisor;
}
int tempY = y;
int addition = 1;
boolean add = true;
// find empty location
while (coordinateSystem.get(new Point(x, y)) != null) {
if (add) {
y = tempY + addition;
add = false;
} else {
y = tempY - addition;
add = true;
addition++;
}
}
node.location = new Point(x, y);
int newWeightSum = node.getEdgesWeightSum();
// if old weight > new weight we found a better
// location
if (oldWeightSum > newWeightSum) {
stopTrying = true;
canMove = true;
}
divisor++;
if (divisor >= Math.abs(nearest.location.y - farest.location.y)) {
stopTrying = true;
}
}
if (canMove) {
// move nodes
coordinateSystem.put(node.location.getCopy(), node);
coordinateSystem.put(oldLocation.getCopy(), null);
checkNewMinimumsAndMaximums(x, y);
return false; // stop optimizing
} else {
// no better spot found, stop optimizing, best
// location is already set
node.location = oldLocation.getCopy();
return true; // stop
}
}
}
class Node {
private final long key;
private final List<Edge> incomingEdges = new ArrayList<>();
private final List<Edge> outgoingEdges = new ArrayList<>();
private Point location;
public Node(final long key) {
this.key = key;
}
int getEdgesWeightSum() {
int weightSum = 0;
for (Edge edge : incomingEdges) {
weightSum += edge.getWeight();
}
for (Edge edge : outgoingEdges) {
weightSum += edge.getWeight();
}
return weightSum;
}
Edge getHeaviestEdge() {
Edge heaviestEdge = null;
int heaviestWeight = 0;
for (Edge edge : incomingEdges) {
int weight = edge.getWeight();
if (weight <= heaviestWeight)
continue;
heaviestWeight = weight;
heaviestEdge = edge;
}
for (Edge edge : outgoingEdges) {
int weight = edge.getWeight();
if (weight <= heaviestWeight)
continue;
heaviestWeight = weight;
heaviestEdge = edge;
}
return heaviestEdge;
}
Edge getLightestEdge() {
Edge lightestEdge = null;
int lightestWeight = Integer.MAX_VALUE;
for (Edge edge : incomingEdges) {
int weight = edge.getWeight();
if (weight >= lightestWeight)
continue;
lightestWeight = weight;
lightestEdge = edge;
}
for (Edge edge : outgoingEdges) {
int weight = edge.getWeight();
if (weight >= lightestWeight)
continue;
lightestWeight = weight;
lightestEdge = edge;
}
return lightestEdge;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj instanceof Node)
if (key == ((Node) obj).key)
return true;
return false;
}
}
}