package org.openlca.app.results.analysis.sankey.layout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.openlca.app.results.analysis.sankey.model.ProcessFigure;
import org.openlca.app.results.analysis.sankey.model.ProcessNode;
import org.openlca.app.results.analysis.sankey.model.ProductSystemNode;
import org.openlca.core.matrix.ProcessLinkSearchMap;
import org.openlca.core.model.ProcessLink;
import org.openlca.core.model.ProductSystem;
public class TreeLayout {
/**
* set of process keys that have been added to a node already (important in
* case of loops, so no process is added twice)
*/
private final Set<Long> containing = new HashSet<>();
/**
* XY location in grid -> process key
*/
private final Map<Point, Long> locations = new HashMap<>();
/**
* The processes painted as process nodes
*/
private final Set<Long> paintedProcesses = new HashSet<>();
private ProcessLinkSearchMap linkSearchMap;
public void layout(final ProductSystemNode productSystemNode) {
linkSearchMap = productSystemNode.getEditor().getLinkSearchMap();
prepare(productSystemNode);
final List<Node> nodes = new ArrayList<>();
final Node mainNode = build(productSystemNode.getProductSystem());
mainNode.sort();
nodes.add(mainNode);
for (final Object o : productSystemNode.getChildrenArray()) {
if (o instanceof ProcessNode) {
final ProcessNode processNode = (ProcessNode) o;
if (!containing.contains(processNode.process.getId())) {
final Node node = new Node();
node.processId = processNode.process.getId();
build(productSystemNode.getProductSystem(),
new Node[] { node });
node.sort();
nodes.add(node);
}
}
}
int additionalHeight = 0;
for (final Node node : nodes) {
int newAdditionalHeight = 0;
locations.clear();
applyLayout(node, 0, node.getLeftDepth(), mainNode.getLeftDepth());
int minimumX = Integer.MAX_VALUE;
int maximumX = Integer.MIN_VALUE;
int minimumY = Integer.MAX_VALUE;
int maximumY = Integer.MIN_VALUE;
for (final Point p : locations.keySet()) {
if (p.x < minimumX) {
minimumX = p.x;
}
if (p.x > maximumX) {
maximumX = p.x;
}
if (p.y < minimumY) {
minimumY = p.y;
}
if (p.y > maximumY) {
maximumY = p.y;
}
}
final Map<Long, ProcessFigure> figures = new HashMap<>();
for (final Object n : productSystemNode.getChildrenArray()) {
if (n instanceof ProcessNode) {
final ProcessFigure figure = ((ProcessNode) n).figure;
figures.put(figure.getProcessNode().process.getId(),
figure);
}
}
// apply layout
int xPosition = GraphLayoutManager.horizontalSpacing;
for (int x = minimumX; x <= maximumX; x++) {
if (x > minimumX) {
xPosition += ProcessFigure.WIDTH
+ GraphLayoutManager.horizontalSpacing;
}
int yPosition = GraphLayoutManager.verticalSpacing;
for (int y = minimumY; y <= maximumY; y++) {
final Long processKey = locations.get(new Point(x, y));
if (y > minimumY) {
yPosition += ProcessFigure.HEIGHT
+ GraphLayoutManager.verticalSpacing;
}
if (processKey != null) {
final ProcessFigure figure = figures.get(processKey);
if (figure != null) {
figure.getProcessNode().setXyLayoutConstraints(
new Rectangle(xPosition, yPosition
+ additionalHeight, figure
.getSize().width,
figure.getSize().height));
newAdditionalHeight = Math.max(
newAdditionalHeight,
yPosition + additionalHeight
+ figure.getSize().height);
}
}
}
}
additionalHeight = newAdditionalHeight
+ GraphLayoutManager.verticalSpacing;
}
containing.clear();
locations.clear();
}
private void applyLayout(final Node node, int addition,
final int actualDepth, final int maximalDepth) {
int y = maximalDepth - actualDepth + 3;
final int x = node.getSize() / 2 + addition;
while (locations.get(new Point(x, y)) != null) {
y++;
addition++;
}
locations.put(new Point(x, y), node.processId);
for (int i = 0; i < node.leftChildren.size(); i++) {
final Node child = node.leftChildren.get(i);
int sizeAddition = 0;
for (int j = 0; j < i; j++) {
sizeAddition += node.leftChildren.get(j).getSize();
}
applyLayout(child, addition + sizeAddition, actualDepth - 1,
maximalDepth);
}
for (int i = 0; i < node.rightChildren.size(); i++) {
final Node child = node.rightChildren.get(i);
int sizeAddition = 0;
for (int a = 0; a < node.leftChildren.size(); a++) {
sizeAddition += node.leftChildren.get(a).getSize();
}
for (int j = 0; j < i; j++) {
sizeAddition += node.rightChildren.get(j).getSize();
}
applyLayout(child, addition + sizeAddition, actualDepth + 1,
maximalDepth);
}
}
private Node build(final ProductSystem productSystem) {
final Node node = new Node();
node.processId = productSystem.getReferenceProcess().getId();
build(productSystem, new Node[] { node });
return node;
}
private void build(ProductSystem system, Node[] nodes) {
List<Node> childs = new ArrayList<>();
for (Node node : nodes) {
long processId = node.processId;
for (ProcessLink link : linkSearchMap.getLinks(processId)) {
if (link.processId == processId) {
if (!containing.contains(link.providerId)
&& paintedProcesses.contains(link.providerId)) {
Node child = new Node();
child.processId = link.providerId;
node.leftChildren.add(child);
containing.add(child.processId);
childs.add(child);
}
}
}
}
if (childs.size() > 0) {
build(system, childs.toArray(new Node[childs.size()]));
}
childs.clear();
for (Node node : nodes) {
long providerId = node.processId;
for (ProcessLink link : linkSearchMap.getLinks(providerId)) {
if (link.providerId != providerId)
continue;
if (!containing.contains(link.processId)
&& paintedProcesses.contains(link.processId)) {
Node child = new Node();
child.processId = link.processId;
node.rightChildren.add(child);
containing.add(child.processId);
childs.add(child);
}
}
}
if (childs.size() > 0) {
build(system, childs.toArray(new Node[childs.size()]));
}
}
private void prepare(final ProductSystemNode productSystemNode) {
for (final Object node : productSystemNode.getChildrenArray()) {
if (node instanceof ProcessNode) {
final ProcessNode processNode = (ProcessNode) node;
paintedProcesses.add(processNode.process.getId());
processNode.setXyLayoutConstraints(new Rectangle(0, 0,
processNode.figure.getSize().width, processNode.figure.getSize().height));
}
}
}
class Node {
List<Node> leftChildren = new ArrayList<>();
long processId;
List<Node> rightChildren = new ArrayList<>();
int getLeftDepth() {
int depth = 0;
if (leftChildren.size() > 0) {
depth = 1;
int depthAdd = 0;
for (int i = 0; i < leftChildren.size(); i++) {
depthAdd = Math.max(depthAdd, leftChildren.get(i)
.getLeftDepth());
}
depth += depthAdd;
}
return depth;
}
int getSize() {
int size = 0;
if (rightChildren.size() == 0 && leftChildren.size() == 0) {
size = 1;
} else {
for (int i = 0; i < rightChildren.size(); i++) {
size += rightChildren.get(i).getSize();
}
for (int i = 0; i < leftChildren.size(); i++) {
size += leftChildren.get(i).getSize();
}
}
return size;
}
void sort() {
final List<Node> temp = new ArrayList<>();
temp.addAll(leftChildren);
Collections.sort(temp, new Comparator<Node>() {
@Override
public int compare(final Node o1, final Node o2) {
return ((Integer) o2.getSize()).compareTo(o1.getSize());
}
});
leftChildren.clear();
int count = 0;
int i = 0;
while (count < temp.size()) {
leftChildren.add(temp.get(i));
count++;
if (count < temp.size()) {
leftChildren.add(temp.get(temp.size() - i - 1));
count++;
}
i++;
}
}
}
}