package org.openlca.app.results.analysis.sankey;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.Layer;
import org.eclipse.draw2d.LayeredPane;
import org.eclipse.draw2d.StackLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.KeyStroke;
import org.eclipse.gef.MouseWheelHandler;
import org.eclipse.gef.MouseWheelZoomHandler;
import org.eclipse.gef.editparts.ScalableRootEditPart;
import org.eclipse.gef.editparts.ZoomListener;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.ui.actions.GEFActionConstants;
import org.eclipse.gef.ui.actions.ZoomInAction;
import org.eclipse.gef.ui.actions.ZoomOutAction;
import org.eclipse.gef.ui.parts.GraphicalEditor;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.SWT;
import org.openlca.app.App;
import org.openlca.app.results.analysis.sankey.actions.SankeyMenu;
import org.openlca.app.results.analysis.sankey.model.ConnectionLink;
import org.openlca.app.results.analysis.sankey.model.ProcessNode;
import org.openlca.app.results.analysis.sankey.model.ProductSystemNode;
import org.openlca.core.math.CalculationSetup;
import org.openlca.core.math.data_quality.DQResult;
import org.openlca.core.matrix.ProcessLinkSearchMap;
import org.openlca.core.model.ProcessLink;
import org.openlca.core.model.ProductSystem;
import org.openlca.core.model.descriptors.FlowDescriptor;
import org.openlca.core.model.descriptors.ImpactCategoryDescriptor;
import org.openlca.core.model.descriptors.ProcessDescriptor;
import org.openlca.core.results.FullResultProvider;
public class SankeyDiagram extends GraphicalEditor implements
PropertyChangeListener {
public static final String ID = "editor.ProductSystemSankeyDiagram";
private SankeyResult sankeyResult;
private ProcessLinkSearchMap linkSearchMap;
private Map<ProcessLink, ConnectionLink> createdLinks = new HashMap<>();
private Map<Long, ProcessNode> createdProcesses = new HashMap<>();
private ProductSystemNode systemNode;
private ProductSystem productSystem;
private FullResultProvider result;
private DQResult dqResult;
private double zoom = 1;
public SankeyDiagram(CalculationSetup setUp, FullResultProvider result, DQResult dqResult) {
this.dqResult = dqResult;
setEditDomain(new DefaultEditDomain(this));
this.result = result;
productSystem = setUp.productSystem;
linkSearchMap = new ProcessLinkSearchMap(
productSystem.getProcessLinks());
sankeyResult = new SankeyResult(productSystem, result);
if (productSystem != null)
setPartName(productSystem.getName());
}
public FullResultProvider getResult() {
return result;
}
public DQResult getDqResult() {
return dqResult;
}
public ProcessLinkSearchMap getLinkSearchMap() {
return linkSearchMap;
}
private void createConnections(long startProcessId) {
Set<Long> processed = new HashSet<>();
Stack<Long> processes = new Stack<>();
processes.add(startProcessId);
while (!processes.isEmpty()) {
long nextId = processes.pop();
processed.add(nextId);
for (ProcessLink processLink : linkSearchMap.getIncomingLinks(nextId)) {
ProcessNode sourceNode = createdProcesses.get(processLink.providerId);
ProcessNode targetNode = createdProcesses.get(processLink.processId);
if (sourceNode == null || targetNode == null)
continue;
if (createdLinks.containsKey(processLink))
continue;
double ratio = sankeyResult.getLinkContribution(processLink);
ConnectionLink link = new ConnectionLink(sourceNode, targetNode, processLink, ratio);
createdLinks.put(processLink, link);
if (processed.contains(sourceNode.process.getId()))
continue;
processes.add(sourceNode.process.getId());
}
}
}
private ProcessNode createNode(ProcessDescriptor process) {
ProcessNode node = new ProcessNode(process);
long processId = process.getId();
node.directContribution = sankeyResult.getDirectContribution(processId);
node.directResult = sankeyResult.getDirectResult(processId);
node.upstreamContribution = sankeyResult
.getUpstreamContribution(processId);
node.upstreamResult = sankeyResult.getUpstreamResult(processId);
createdProcesses.put(process.getId(), node);
return node;
}
/**
* Updates the connection links
*/
private void updateConnections() {
createConnections(productSystem.getReferenceProcess().getId());
for (final ConnectionLink link : createdLinks.values()) {
link.link();
}
}
private void updateModel(double cutoff) {
Map<Long, ProcessDescriptor> descriptors = new HashMap<>();
for (ProcessDescriptor descriptor : result.getProcessDescriptors())
descriptors.put(descriptor.getId(), descriptor);
if (cutoff == 0) {
for (Long processId : productSystem.getProcesses()) {
ProcessDescriptor descriptor = descriptors.get(processId);
if (descriptor != null) {
systemNode.addChild(createNode(descriptor));
}
}
} else {
long refProcess = productSystem.getReferenceProcess().getId();
Set<Long> processesToDraw = SankeyProcessList.calculate(
sankeyResult, refProcess, cutoff, linkSearchMap);
for (final Long processId : processesToDraw) {
ProcessDescriptor process = descriptors.get(processId);
if (process != null) {
systemNode.addChild(createNode(process));
}
}
}
}
@Override
protected void configureGraphicalViewer() {
ArrayList<String> zoomContributions;
// configure viewer
super.configureGraphicalViewer();
MenuManager menu = SankeyMenu.create(this);
getGraphicalViewer().setContextMenu(menu);
GraphicalViewer viewer = getGraphicalViewer();
viewer.setEditPartFactory(new SankeyEditPartFactory());
ScalableRootEditPart rootEditPart = new ScalableRootEditPart();
viewer.setRootEditPart(rootEditPart);
ZoomManager zoomManager = rootEditPart.getZoomManager();
// append zoom actions to action registry
getActionRegistry().registerAction(new ZoomInAction(zoomManager));
getActionRegistry().registerAction(new ZoomOutAction(zoomManager));
zoomContributions = new ArrayList<>();
zoomContributions.add(ZoomManager.FIT_ALL);
zoomContributions.add(ZoomManager.FIT_HEIGHT);
zoomContributions.add(ZoomManager.FIT_WIDTH);
zoomManager.setZoomLevelContributions(zoomContributions);
// create key handler
KeyHandler keyHandler = new KeyHandler();
keyHandler.put(KeyStroke.getPressed('+', SWT.KEYPAD_ADD, 0),
getActionRegistry().getAction(GEFActionConstants.ZOOM_IN));
keyHandler.put(KeyStroke.getPressed('-', SWT.KEYPAD_SUBTRACT, 0),
getActionRegistry().getAction(GEFActionConstants.ZOOM_OUT));
viewer.setKeyHandler(keyHandler);
viewer.setProperty(MouseWheelHandler.KeyGenerator.getKey(SWT.NONE),
MouseWheelZoomHandler.SINGLETON);
}
@Override
protected void initializeGraphicalViewer() {
// create new root edit part with switched layers (connection layer
// under the process layer)
getGraphicalViewer().setRootEditPart(new ScalableRootEditPart() {
@Override
protected LayeredPane createPrintableLayers() {
LayeredPane pane = new LayeredPane();
Layer layer = new ConnectionLayer();
layer.setPreferredSize(new Dimension(5, 5));
pane.add(layer, CONNECTION_LAYER);
layer = new Layer();
layer.setOpaque(false);
layer.setLayoutManager(new StackLayout());
pane.add(layer, PRIMARY_LAYER);
return pane;
}
});
// zoom listener
((ScalableRootEditPart) getGraphicalViewer().getRootEditPart())
.getZoomManager().addZoomListener(new ZoomListener() {
@Override
public void zoomChanged(final double arg0) {
zoom = arg0;
}
});
double[] zoomLevels = new double[] { 0.005, 0.01, 0.02, 0.0375, 0.075,
0.125, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0,
10.0, 20.0, 40.0, 80.0, 150.0, 300.0, 500.0, 1000.0 };
((ScalableRootEditPart) getGraphicalViewer().getRootEditPart())
.getZoomManager().setZoomLevels(zoomLevels);
initContent();
}
// TODO: avoid double calculation here
private void initContent() {
Object defaultSelection = getDefaultSelection();
if (defaultSelection == null) {
getGraphicalViewer().setContents(
new ProductSystemNode(productSystem, this, null, 0.1));
return;
}
sankeyResult.calculate(defaultSelection);
double cutoff = sankeyResult.findCutoff(30);
update(defaultSelection, cutoff);
}
public Object getDefaultSelection() {
if (result == null)
return null;
if (result.hasImpactResults()) {
Set<ImpactCategoryDescriptor> categories = result
.getImpactDescriptors();
if (!categories.isEmpty())
return categories.iterator().next();
}
Set<FlowDescriptor> flows = result.getFlowDescriptors();
if (!flows.isEmpty())
return flows.iterator().next();
return null;
}
@Override
public void dispose() {
if (systemNode != null)
systemNode.dispose();
result = null;
super.dispose();
}
@Override
public void doSave(IProgressMonitor monitor) {
}
public ProductSystemNode getModel() {
return systemNode;
}
public double getProductSystemResult() {
return sankeyResult.getUpstreamResult(productSystem
.getReferenceProcess().getId());
}
public double getZoom() {
return zoom;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public void propertyChange(final PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("firstTimeInitialized")) {
createdLinks.clear();
updateConnections();
}
}
@Override
public GraphicalViewer getGraphicalViewer() {
return super.getGraphicalViewer();
}
public void update(Object selection, double cutoff) {
if (selection == null || cutoff < 0d || cutoff > 1d)
return;
App.run("Calculate sankey results", () -> sankeyResult
.calculate(selection), () -> {
systemNode = new ProductSystemNode(productSystem,
SankeyDiagram.this, selection, cutoff);
createdProcesses.clear();
createdLinks.clear();
updateModel(cutoff);
getGraphicalViewer().deselectAll();
getGraphicalViewer().setContents(systemNode);
});
}
}