package org.openlca.app.results.contributions;
import java.util.Collections;
import java.util.List;
import javafx.embed.swt.FXCanvas;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Tooltip;
import javafx.util.StringConverter;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.openlca.app.util.Numbers;
import org.openlca.app.util.UI;
import org.openlca.core.model.descriptors.BaseDescriptor;
import org.openlca.core.results.ContributionItem;
public class ContributionChart extends BarChart<String, Number> {
private ChartLegend legend;
public static ContributionChart create(Composite parent, FormToolkit toolkit) {
// #see getGap for more info why this size specifically
return create(parent, toolkit, 700, 300);
}
public static ContributionChart create(Composite parent, FormToolkit toolkit, double width, double height) {
Composite container = UI.formComposite(parent, toolkit);
UI.gridLayout(container, 2);
UI.gridData(container, true, true);
ContributionChart chart = new ContributionChart(width, height);
FXCanvas canvas = new FXCanvas(container, SWT.NONE);
Scene scene = new Scene(new Group(chart));
String cssPath = ContributionChart.class.getPackage().getName().replace('.', '/');
scene.getStylesheets().add("/" + cssPath + "/styles.css");
canvas.setScene(scene);
chart.legend = new ChartLegend(container);
return chart;
}
public void setLabel(ILabelProvider label) {
legend.label = label;
}
private ContributionChart(double width, double height) {
super(new CategoryAxis(), new NumberAxis());
setLegendVisible(false);
getXAxis().setTickMarkVisible(false);
setAnimated(false);
setPrefSize(width, height);
}
public void setData(List<ContributionItem<?>> items, String unit) {
getData().clear();
Collections.sort(items, new Comparator(true));
List<ContributionItem<?>> top = getTop(items);
for (ContributionItem<?> item : top) {
addData(getLabel(item), item.amount);
}
double others = 0;
if (items.size() > 6) {
for (int i = 5; i < items.size(); i++) {
others += items.get(i).amount;
}
Data<String, Number> data = addData("Others", others);
data.getNode().getStyleClass().add("others");
}
updateYAxis(top, others);
int bars = items.size() > 5 ? 6 : items.size();
setBarGap(getGap(bars));
legend.setData(top, others, unit);
}
private List<ContributionItem<?>> getTop(List<ContributionItem<?>> items) {
List<ContributionItem<?>> top = items.size() <= 6 ? items : items.subList(0, items.size() == 6 ? 6 : 5);
Collections.sort(top, new Comparator(false));
return top;
}
private double getGap(int bars) {
// TODO maybe there is a better way to calculate this correctly, this
// seems to work for now but not in all chart sizes, so be careful when
// not using the standard size in 2-arg constructor
switch (bars) {
case 1:
return 255;
case 2:
return 150;
case 3:
return 100;
case 4:
return 70;
case 5:
return 55;
default:
return 40;
}
}
private Data<String, Number> addData(String label, double amount) {
Series<String, Number> series = new Series<>();
series.setName(label);
Data<String, Number> data = new Data<>("", amount);
series.getData().add(data);
getData().add(series);
Tooltip tooltip = new Tooltip();
tooltip.setText(label + "\n" + data.getYValue().toString());
Tooltip.install(data.getNode(), tooltip);
return data;
}
private static String getLabel(ContributionItem<?> item) {
if (item.item instanceof BaseDescriptor)
return ((BaseDescriptor) item.item).getName();
return null;
}
private void updateYAxis(List<ContributionItem<?>> top, double others) {
double min = others < 0 ? others : 0;
double max = others > 0 ? others : 0;
for (ContributionItem<?> item : top) {
min = Math.min(min, item.amount);
max = Math.max(max, item.amount);
}
min = Rounding.apply(min);
max = Rounding.apply(max);
double bound = Math.max(-min, max);
NumberAxis yAxis = (NumberAxis) getYAxis();
yAxis.setAutoRanging(false);
yAxis.setLowerBound(min < 0 ? -bound : 0);
yAxis.setUpperBound(bound);
double tick = bound / (min < 0 ? 2 : 4);
yAxis.setTickUnit(tick);
yAxis.setTickLabelFormatter(new StringConverter<Number>() {
@Override
public String toString(Number arg0) {
if (arg0 == null)
return "0";
return Numbers.format(arg0.doubleValue(), 1);
}
@Override
public Number fromString(String arg0) {
return Double.parseDouble(arg0);
}
});
}
private class Comparator implements java.util.Comparator<ContributionItem<?>> {
private final boolean abs;
private Comparator(boolean abs) {
this.abs = abs;
}
@Override
public int compare(ContributionItem<?> o1, ContributionItem<?> o2) {
double a1 = o1.amount;
double a2 = o2.amount;
if (abs) {
a1 = Math.abs(a1);
a2 = Math.abs(a2);
}
if (a1 == a2)
return 0;
if (a1 == 0d)
return 1;
if (a2 == 0d)
return -1;
return -Double.compare(a1, a2);
}
}
}