package org.openlca.app.results; import java.util.ArrayList; import java.util.List; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.FormPage; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.openlca.app.M; import org.openlca.app.components.ContributionImage; import org.openlca.app.rcp.images.Images; import org.openlca.app.results.ContributionCutoff.CutoffContentProvider; import org.openlca.app.util.Actions; import org.openlca.app.util.Controls; import org.openlca.app.util.DQUI; import org.openlca.app.util.Labels; import org.openlca.app.util.Numbers; import org.openlca.app.util.UI; import org.openlca.app.util.trees.TreeClipboard; import org.openlca.app.util.trees.TreeClipboard.ClipboardLabelProvider; import org.openlca.app.util.trees.Trees; import org.openlca.app.util.viewers.Viewers; import org.openlca.core.math.data_quality.DQResult; import org.openlca.core.model.ModelType; 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.ContributionResultProvider; public class TotalImpactResultPage extends FormPage { private final ContributionResultProvider<?> result; private final DQResult dqResult; private final ImpactFactorProvider impactFactors; private FormToolkit toolkit; private TreeViewer viewer; private ContributionCutoff spinner; private boolean subgroupByProcesses = true; public TotalImpactResultPage(FormEditor editor, ContributionResultProvider<?> result, DQResult dqResult, ImpactFactorProvider impactFactors) { super(editor, "ImpactTreePage", M.ImpactAnalysis); this.result = result; this.dqResult = dqResult; this.impactFactors = impactFactors; } @Override protected void createFormContent(IManagedForm managedForm) { ScrolledForm form = UI.formHeader(managedForm, M.ImpactAnalysis); toolkit = managedForm.getToolkit(); Composite body = UI.formBody(form, toolkit); Section section = UI.section(body, toolkit, M.ImpactAnalysis); UI.gridData(section, true, true); Composite client = toolkit.createComposite(section); section.setClient(client); UI.gridLayout(client, 1); createOptions(client); createTree(client); spinner.register(viewer); form.reflow(true); } private void createOptions(Composite parent) { Composite container = UI.formComposite(parent, toolkit); UI.gridLayout(container, 3); Button button = UI.formCheckBox(container, toolkit, M.SubgroupByProcesses); button.setSelection(true); Controls.onSelect(button, (e) -> { subgroupByProcesses = button.getSelection(); setInput(); }); spinner = ContributionCutoff.create(container, toolkit); } private void setInput() { List<Item> impacts = new ArrayList<>(); for (ImpactCategoryDescriptor impact : result.getImpactDescriptors()) { impacts.add(new Item(impact)); } viewer.setInput(impacts); } private void createTree(Composite comp) { String[] columns = { M.Name, M.Category, M.InventoryResult, M.ImpactFactor, M.ImpactResult, M.Unit }; if (DQUI.displayExchangeQuality(dqResult)) { columns = DQUI.appendTableHeaders(columns, dqResult.setup.exchangeDqSystem); } LabelProvider labelProvider = new LabelProvider(); viewer = Trees.createViewer(comp, columns, labelProvider); viewer.setContentProvider(new ContentProvider()); toolkit.adapt(viewer.getTree(), false, false); toolkit.paintBordersFor(viewer.getTree()); Actions.bind(viewer, TreeClipboard.onCopy(viewer, new ClipboardLabel())); createColumnSorters(labelProvider); double[] widths = { .35, .2, .10, .10, .15, .05 }; if (DQUI.displayExchangeQuality(dqResult)) { widths = DQUI.adjustTableWidths(widths, dqResult.setup.exchangeDqSystem); } viewer.getTree().getColumns()[2].setAlignment(SWT.RIGHT); viewer.getTree().getColumns()[3].setAlignment(SWT.RIGHT); viewer.getTree().getColumns()[4].setAlignment(SWT.RIGHT); Trees.bindColumnWidths(viewer.getTree(), widths); setInput(); } private void createColumnSorters(LabelProvider p) { Viewers.sortByLabels(viewer, p, 0, 1, 5); Viewers.sortByDouble(viewer, (item) -> ((Item) item).flowAmount(), 2); Viewers.sortByDouble(viewer, (item) -> ((Item) item).impactFactor(), 3); Viewers.sortByDouble(viewer, (item) -> ((Item) item).result(), 4); if (!DQUI.displayExchangeQuality(dqResult)) return; for (int i = 0; i < dqResult.setup.exchangeDqSystem.indicators.size(); i++) { Viewers.sortByDouble(viewer, p, i + 5); } } private class ClipboardLabel implements ClipboardLabelProvider { private LabelProvider label = new LabelProvider(); private String[] columns = { M.Name, M.Category, M.InventoryResult, M.Unit, M.ImpactFactor, M.Unit, M.ImpactResult, M.Unit }; @Override public int columns() { return columns.length; } @Override public String getHeader(int col) { return columns[col]; } @Override public String getLabel(TreeItem treeItem, int col) { Item item = (Item) treeItem.getData(); switch (col) { case 0: return label.getText(item, 0); case 1: return label.getText(item, 1); case 2: return Numbers.format(item.flowAmount()); case 3: return item.flowAmountUnit(); case 4: return Numbers.format(item.impactFactor()); case 5: return item.impactFactorUnit(); case 6: return label.getText(item, 4); case 7: return label.getText(item, 5); } return null; } } private class LabelProvider extends DQLabelProvider { private ContributionImage img = new ContributionImage(Display.getCurrent()); LabelProvider() { super(dqResult, dqResult != null ? dqResult.setup.exchangeDqSystem : null, 6); } @Override public void dispose() { img.dispose(); super.dispose(); } @Override public Image getImage(Object obj, int col) { if (!(obj instanceof Item)) return null; Item item = (Item) obj; if (col == 0) return Images.get(item.type()); if (col == 4 && item.type() != ModelType.IMPACT_CATEGORY) return img.getForTable(item.contribution()); return null; } @Override public String getText(Object obj, int col) { if (!(obj instanceof Item)) return null; Item item = (Item) obj; switch (col) { case 0: return item.name(); case 1: return item.category(); case 2: return item.flowAmountString(); case 3: return item.impactFactorString(); case 4: return Numbers.format(item.result()); case 5: return item.unit(); default: return null; } } @Override protected double[] getQuality(Object obj) { if (dqResult == null) return null; Item item = (Item) obj; switch (item.type()) { case IMPACT_CATEGORY: return dqResult.get(item.impact); case PROCESS: return dqResult.get(item.process, item.impact); case FLOW: if (item.process != null) return dqResult.get(item.process, item.flow); else return dqResult.get(item.flow, item.impact); default: return null; } } } private class ContentProvider extends ArrayContentProvider implements ITreeContentProvider, CutoffContentProvider { private double cutoff; @Override public Object[] getChildren(Object obj) { if (!(obj instanceof Item)) return null; Item parent = (Item) obj; List<Item> children = new ArrayList<>(); if (parent.type() == ModelType.IMPACT_CATEGORY && subgroupByProcesses) { double cutoffValue = parent.result() * cutoff; for (ProcessDescriptor process : result.getProcessDescriptors()) { Item child = new Item(parent.impact, process); double result = child.result(); if (result != 0 && (this.cutoff == 0d || Math.abs(result) >= cutoffValue)) { children.add(child); } } } else { double cutoffValue = parent.result() * cutoff; for (FlowDescriptor flow : result.getFlowDescriptors()) { // process will be null in case of subgroupByProcesses=false Item child = new Item(parent.impact, parent.process, flow); double result = child.result(); if (result != 0 && (this.cutoff == 0d || Math.abs(result) >= cutoffValue)) { children.add(child); } } } children.sort((i1, i2) -> -Double.compare(i1.result(), i2.result())); return children.toArray(); } @Override public Object getParent(Object element) { return null; } @Override public boolean hasChildren(Object element) { if (!(element instanceof Item)) return false; Item item = (Item) element; if (item.type() == ModelType.FLOW) return false; return true; } @Override public void setCutoff(double cutoff) { this.cutoff = cutoff; } } public interface ImpactFactorProvider { double get(ImpactCategoryDescriptor impact, ProcessDescriptor process, FlowDescriptor flow); } private class Item { final ImpactCategoryDescriptor impact; final ProcessDescriptor process; final FlowDescriptor flow; Item(ImpactCategoryDescriptor impact) { this(impact, null, null); } Item(ImpactCategoryDescriptor impact, ProcessDescriptor process) { this(impact, process, null); } Item(ImpactCategoryDescriptor impact, ProcessDescriptor process, FlowDescriptor flow) { this.impact = impact; this.process = process; this.flow = flow; } /** The type of contribution shown by the item. */ ModelType type() { if (flow != null) return ModelType.FLOW; if (process != null) return ModelType.PROCESS; return ModelType.IMPACT_CATEGORY; } double impactFactor() { if (impact == null || process == null || flow == null) return 0; return impactFactors.get(impact, process, flow); } String impactFactorUnit() { String unit = impact.getReferenceUnit(); if (unit == null) unit = "1"; return unit + "/" + Labels.getRefUnit(flow, result.cache); } String impactFactorString() { if (type() != ModelType.FLOW) return null; String f = Numbers.format(impactFactor()); String unit = impactFactorUnit(); return f + " " + unit; } double flowAmount() { if (flow == null) return 0; if (process == null) return result.getTotalFlowResult(flow).value; return result.getSingleFlowResult(process, flow).value; } String flowAmountUnit() { return Labels.getRefUnit(flow, result.cache); } String flowAmountString() { if (type() != ModelType.FLOW) return null; String amount = Numbers.format(flowAmount()); String unit = flowAmountUnit(); return amount + " " + unit; } double result() { switch (type()) { case IMPACT_CATEGORY: return result.getTotalImpactResult(impact).value; case PROCESS: return result.getSingleImpactResult(process, impact).value; case FLOW: return impactFactor() * flowAmount(); default: return 0; } } String unit() { if (impact.getReferenceUnit() == null) return null; return impact.getReferenceUnit(); } String name() { switch (type()) { case IMPACT_CATEGORY: return impact.getName(); case FLOW: return flow.getName(); case PROCESS: return Labels.getDisplayName(process); default: return null; } } String category() { switch (type()) { case FLOW: return Labels.getShortCategory(flow, result.cache); case PROCESS: return Labels.getShortCategory(process, result.cache); default: return null; } } double contribution() { double total = Math.abs(result.getTotalImpactResult(impact).value); double r = result(); if (r == 0) return 0; if (total == 0) return r > 0 ? 1 : -1; return r / total; } } }