blob: 93b8182d9a75e3fec530b339ffe95e56162b1e9c [file] [log] [blame]
/*********************************************************************************
* Copyright (c) 2020-2021 Robert Bosch GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.amalthea.visualizations.standard;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.app4mc.amalthea.model.ContinuousValueBetaDistribution;
import org.eclipse.app4mc.amalthea.model.ContinuousValueConstant;
import org.eclipse.app4mc.amalthea.model.ContinuousValueGaussDistribution;
import org.eclipse.app4mc.amalthea.model.ContinuousValueHistogram;
import org.eclipse.app4mc.amalthea.model.ContinuousValueHistogramEntry;
import org.eclipse.app4mc.amalthea.model.ContinuousValueInterval;
import org.eclipse.app4mc.amalthea.model.ContinuousValueStatistics;
import org.eclipse.app4mc.amalthea.model.ContinuousValueWeibullEstimatorsDistribution;
import org.eclipse.app4mc.amalthea.model.DiscreteValueBetaDistribution;
import org.eclipse.app4mc.amalthea.model.DiscreteValueConstant;
import org.eclipse.app4mc.amalthea.model.DiscreteValueGaussDistribution;
import org.eclipse.app4mc.amalthea.model.DiscreteValueHistogram;
import org.eclipse.app4mc.amalthea.model.DiscreteValueHistogramEntry;
import org.eclipse.app4mc.amalthea.model.DiscreteValueInterval;
import org.eclipse.app4mc.amalthea.model.DiscreteValueStatistics;
import org.eclipse.app4mc.amalthea.model.DiscreteValueWeibullEstimatorsDistribution;
import org.eclipse.app4mc.amalthea.model.IContinuousValueDeviation;
import org.eclipse.app4mc.amalthea.model.IDiscreteValueDeviation;
import org.eclipse.app4mc.amalthea.model.ITimeDeviation;
import org.eclipse.app4mc.amalthea.model.Time;
import org.eclipse.app4mc.amalthea.model.TimeBetaDistribution;
import org.eclipse.app4mc.amalthea.model.TimeConstant;
import org.eclipse.app4mc.amalthea.model.TimeGaussDistribution;
import org.eclipse.app4mc.amalthea.model.TimeHistogram;
import org.eclipse.app4mc.amalthea.model.TimeHistogramEntry;
import org.eclipse.app4mc.amalthea.model.TimeInterval;
import org.eclipse.app4mc.amalthea.model.TimeStatistics;
import org.eclipse.app4mc.amalthea.model.TimeUnit;
import org.eclipse.app4mc.amalthea.model.TimeWeibullEstimatorsDistribution;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import javafx.geometry.Pos;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
public abstract class AbstractDeviationChart {
private static final String CHART_SERIES_AREA_LINE = ".chart-series-area-line";
private static final String CHART_SERIES_AREA_FILL = ".chart-series-area-fill";
protected static final List<String> DEVIATION_LIST = Collections.unmodifiableList(Arrays.asList(
"Constant", "Histogram", "Boundaries", "Statistic", "Uniform", "Beta", "Gauss", "Weibull"));
protected static final String ARROW = "\u2192";
protected AreaChart<Number, Number> addNewChart(BorderPane pane, EObject deviation, String unit) {
NumberAxis xAxis = new NumberAxis();
String part1 = getXAxisType(deviation, "values");
String part2 = (unit == null || unit.isEmpty()) ? "" : " [" + unit + "]";
xAxis.setLabel(part1 + part2);
xAxis.setForceZeroInRange(false);
xAxis.setMinorTickVisible(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("density");
yAxis.setForceZeroInRange(false);
yAxis.setTickMarkVisible(false);
yAxis.setTickLabelsVisible(false);
yAxis.setMinorTickVisible(false);
AreaChart<Number, Number> chart = new AreaChart<>(xAxis, yAxis);
chart.setTitle(getChartTitle(deviation));
chart.setAnimated(false);
chart.setVerticalGridLinesVisible(false);
chart.setHorizontalGridLinesVisible(false);
chart.setLegendVisible(false);
chart.lookup(".chart-vertical-zero-line").setStyle("-fx-stroke: rgba(0, 0, 0, 0.0);");
pane.setCenter(chart);
return chart;
}
private String getChartTitle(EObject deviation) {
if (deviation != null) {
String className = deviation.eClass().getName();
for (String genericName : DEVIATION_LIST) {
if (className.contains(genericName)) return genericName;
}
}
return null;
}
private String getXAxisType(EObject deviation, String defaultText) {
if (deviation != null && deviation.eContainer()!= null && deviation.eContainingFeature() != null) {
String containerClassName = deviation.eContainer().eClass().getName();
String featureName = deviation.eContainingFeature().getName();
if (containerClassName.contains("Ticks")) {
return "ticks";
}
if (deviation.eClass().getName().contains("Time")) {
return "time";
}
if (featureName.contains("Latency")) {
return "cycles";
}
if (featureName.contains("occurrences")) {
return "occurrences";
}
}
return defaultText;
}
protected Label addNewStatus(BorderPane pane, String status) {
// add status line for warnings
if (status != null) {
Label statusLabel = new Label(status);
statusLabel.setMaxWidth(Double.MAX_VALUE);
statusLabel.setAlignment(Pos.CENTER);
statusLabel.setTextFill(Color.RED);
pane.setBottom(statusLabel);
return statusLabel;
}
return null;
}
protected void setChartXBounds(AreaChart<Number, Number> chart, double lowerBound, double upperBound) {
if (lowerBound >= upperBound) return;
if (chart.getXAxis() instanceof NumberAxis) {
NumberAxis axis = (NumberAxis) chart.getXAxis();
// ValueAxis<Number> allows to set the bounds
axis.setAutoRanging(false);
axis.setLowerBound(lowerBound);
axis.setUpperBound(upperBound);
axis.setMinorTickCount(0);
axis.setTickUnit((upperBound - lowerBound) / 5);
}
}
protected void setChartYBounds(AreaChart<Number, Number> chart, double upperBound) {
if (chart.getYAxis() instanceof NumberAxis) {
NumberAxis axis = (NumberAxis) chart.getYAxis();
// ValueAxis<Number> allows to set the bounds
axis.setAutoRanging(false);
axis.setLowerBound(0);
axis.setUpperBound(upperBound);
}
}
protected void addSeriesStandard(AreaChart<Number, Number> chart, final Series<Number, Number> series) {
if (series.getData().isEmpty()) return;
chart.getData().add(series);
// set standard color for line and fill
series.getNode().lookup(CHART_SERIES_AREA_LINE).setStyle("-fx-stroke: rgba(0, 128, 0, 1.0);");
series.getNode().lookup(CHART_SERIES_AREA_FILL).setStyle("-fx-fill: rgba(0, 128, 0, 0.15);");
// hide line markers
for (Data<Number, Number> data : series.getData()) {
data.getNode().setVisible(false);
}
}
protected void addSeriesOffLimit(AreaChart<Number, Number> chart, final Series<Number, Number> series) {
if (series.getData().isEmpty()) return;
chart.getData().add(series);
// set color and dash style for line; no fill
series.getNode().lookup(CHART_SERIES_AREA_LINE).setStyle("-fx-stroke: rgba(128, 128, 128, 1.0); -fx-stroke-dash-array: 2;");
series.getNode().lookup(CHART_SERIES_AREA_FILL).setStyle("-fx-fill: rgba(0, 0, 0, 0.0);");
// hide line markers
for (Data<Number, Number> data : series.getData()) {
data.getNode().setVisible(false);
}
}
protected void addSeriesGradient(AreaChart<Number, Number> chart, final Series<Number, Number> series) {
if (series.getData().isEmpty()) return;
chart.getData().add(series);
// set color and dash style for line; no fill
series.getNode().lookup(CHART_SERIES_AREA_LINE).setStyle("-fx-stroke: rgba(0, 0, 0, 0.0);");
series.getNode().lookup(CHART_SERIES_AREA_FILL).setStyle("-fx-fill: linear-gradient(to top, rgba(0, 128, 0, 0.3), rgba(0, 128, 0, 0.0));");
// hide line markers
for (Data<Number, Number> data : series.getData()) {
data.getNode().setVisible(false);
}
}
protected void addSinglePeek(AreaChart<Number, Number> chart, double y, double x) {
if (chart.getYAxis() instanceof NumberAxis) {
final Series<Number, Number> series = new XYChart.Series<>();
chart.getData().add(series);
series.getData().add(new XYChart.Data<>(x, 0.0));
series.getData().add(new XYChart.Data<>(x, y));
series.getNode().lookup(CHART_SERIES_AREA_LINE).setStyle("-fx-stroke: rgba(0, 128, 0, 1.0);");
series.getData().get(1).getNode().setVisible(false);
}
}
protected void addMarkers(AreaChart<Number, Number> chart, double y, Double min, Double avg, Double max) {
if (chart.getYAxis() instanceof NumberAxis) {
// add lower bounds marker
if (min != null) {
final Series<Number, Number> series = new XYChart.Series<>();
chart.getData().add(series);
series.setName("min");
series.getData().add(new XYChart.Data<>(min, 0.0));
series.getData().add(new XYChart.Data<>(min, y));
series.getNode().lookup(CHART_SERIES_AREA_LINE).setStyle("-fx-stroke: rgba(0, 0, 0, 1.0);");
series.getData().get(1).getNode().setVisible(false);
}
// add upper bounds marker
if (max != null) {
final Series<Number, Number> series = new XYChart.Series<>();
chart.getData().add(series);
series.setName("max");
series.getData().add(new XYChart.Data<>(max, 0.0));
series.getData().add(new XYChart.Data<>(max, y));
series.getNode().lookup(CHART_SERIES_AREA_LINE).setStyle("-fx-stroke: rgba(0, 0, 0, 1.0);");
series.getData().get(1).getNode().setVisible(false);
}
// add average marker if computed average is in correct bounds (skip otherwise)
if (avg != null && (min == null || min <= avg) && (max == null || max >= avg)) {
final Series<Number, Number> series = new XYChart.Series<>();
chart.getData().add(series);
series.setName("avg");
series.getData().add(new XYChart.Data<>(avg, 0.0));
series.getData().add(new XYChart.Data<>(avg, y));
series.getNode().lookup(CHART_SERIES_AREA_LINE).setStyle("-fx-stroke: rgba(150, 5, 5, 1.0); -fx-stroke-dash-array: 3;");
series.getData().get(1).getNode().setVisible(false);
}
}
}
// ***** Some checks *****
public boolean isValid(IDiscreteValueDeviation dev) {
// check constant value
if (dev instanceof DiscreteValueConstant) {
return true;
}
// check histogram entries
if (dev instanceof DiscreteValueHistogram) {
final EList<DiscreteValueHistogramEntry> entries = ((DiscreteValueHistogram) dev).getEntries();
if (entries.isEmpty()) return false;
for (DiscreteValueHistogramEntry entry : entries) {
final Long lowerBound = entry.getLowerBound();
final Long upperBound = entry.getUpperBound();
if (lowerBound == null) return false;
if (upperBound == null) return false;
if (lowerBound.compareTo(upperBound) > 0) return false;
if (entry.getOccurrences() < 0) return false;
}
return true;
}
// check attributes of (abstract) interval; check average if available
if (dev instanceof DiscreteValueInterval) {
final DiscreteValueInterval interval = (DiscreteValueInterval) dev;
final Long lowerBound = interval.getLowerBound();
final Long upperBound = interval.getUpperBound();
if (lowerBound == null) return false;
if (upperBound == null) return false;
if (!isValidMinAvgMax(lowerBound.doubleValue(), getAverage(interval), upperBound.doubleValue())) return false;
}
// check Weibull parameters
if (dev instanceof DiscreteValueWeibullEstimatorsDistribution) {
final DiscreteValueWeibullEstimatorsDistribution weibullDist = (DiscreteValueWeibullEstimatorsDistribution) dev;
final double promille = weibullDist.getPRemainPromille();
return promille > 0 && promille < 1000;
}
// check Beta parameters
if (dev instanceof DiscreteValueBetaDistribution) {
final DiscreteValueBetaDistribution betaDist = (DiscreteValueBetaDistribution) dev;
return betaDist.getAlpha() > 0 && betaDist.getBeta() > 0;
}
// check Gauss parameters
if (dev instanceof DiscreteValueGaussDistribution) {
final DiscreteValueGaussDistribution gaussDist = (DiscreteValueGaussDistribution) dev;
if (gaussDist.getSd() <= 0) return false;
if (!isValidMinAvgMax(gaussDist.getLowerBound(), null, gaussDist.getUpperBound())) return false;
}
return true;
}
public boolean isValid(IContinuousValueDeviation dev) {
// check constant value
if (dev instanceof ContinuousValueConstant) {
return true;
}
// check histogram entries
if (dev instanceof ContinuousValueHistogram) {
final EList<ContinuousValueHistogramEntry> entries = ((ContinuousValueHistogram) dev).getEntries();
if (entries.isEmpty()) return false;
for (ContinuousValueHistogramEntry entry : entries) {
final Double lowerBound = entry.getLowerBound();
final Double upperBound = entry.getUpperBound();
if (lowerBound == null) return false;
if (upperBound == null) return false;
if (lowerBound.compareTo(upperBound) > 0) return false;
if (entry.getOccurrences() < 0) return false;
}
return true;
}
// check attributes of (abstract) interval; check average if available
if (dev instanceof ContinuousValueInterval) {
final ContinuousValueInterval interval = (ContinuousValueInterval) dev;
final Double lowerBound = interval.getLowerBound();
final Double upperBound = interval.getUpperBound();
if (lowerBound == null) return false;
if (upperBound == null) return false;
if (!isValidMinAvgMax(lowerBound, getAverage(interval), upperBound)) return false;
}
// check Weibull parameters
if (dev instanceof ContinuousValueWeibullEstimatorsDistribution) {
final ContinuousValueWeibullEstimatorsDistribution weibullDist = (ContinuousValueWeibullEstimatorsDistribution) dev;
final double promille = weibullDist.getPRemainPromille();
return promille > 0 && promille < 1000;
}
// check Beta parameters
if (dev instanceof ContinuousValueBetaDistribution) {
final ContinuousValueBetaDistribution betaDist = (ContinuousValueBetaDistribution) dev;
return betaDist.getAlpha() > 0 && betaDist.getBeta() > 0;
}
// check Gauss parameters
if (dev instanceof ContinuousValueGaussDistribution) {
final ContinuousValueGaussDistribution gaussDist = (ContinuousValueGaussDistribution) dev;
if (gaussDist.getSd() <= 0) return false;
if (!isValidMinAvgMax(gaussDist.getLowerBound(), null, gaussDist.getUpperBound())) return false;
}
return true;
}
public boolean isValid(ITimeDeviation dev) {
// check constant value
if (dev instanceof TimeConstant) {
return isValidTime(((TimeConstant) dev).getValue());
}
// check histogram entries
if (dev instanceof TimeHistogram) {
final EList<TimeHistogramEntry> entries = ((TimeHistogram) dev).getEntries();
if (entries.isEmpty()) return false;
for (TimeHistogramEntry entry : entries) {
final Time lowerBound = entry.getLowerBound();
final Time upperBound = entry.getUpperBound();
if (!isValidTime(lowerBound)) return false;
if (!isValidTime(upperBound)) return false;
if (lowerBound.compareTo(upperBound) > 0) return false;
if (entry.getOccurrences() < 0) return false;
}
return true;
}
// check Statistics parameters (part 1)
if (dev instanceof TimeStatistics) {
final TimeStatistics statistics = (TimeStatistics) dev;
if (!isValidTime(statistics.getAverage())) return false;
}
// check Weibull parameters (part 1)
if (dev instanceof TimeWeibullEstimatorsDistribution) {
final TimeWeibullEstimatorsDistribution weibullDist = (TimeWeibullEstimatorsDistribution) dev;
if (!isValidTime(weibullDist.getAverage())) return false;
final double promille = weibullDist.getPRemainPromille();
if (promille <= 0) return false;
if (promille >= 1000) return false;
}
// check attributes of (abstract) interval; check average if available
if (dev instanceof TimeInterval) {
final TimeInterval interval = (TimeInterval) dev;
final Time lowerBound = interval.getLowerBound();
final Time upperBound = interval.getUpperBound();
if (!isValidTime(lowerBound)) return false;
if (!isValidTime(upperBound)) return false;
if (!isValidMinAvgMax(lowerBound, getAverage(interval), upperBound)) return false;
}
// check Beta parameters
if (dev instanceof TimeBetaDistribution) {
final TimeBetaDistribution betaDist = (TimeBetaDistribution) dev;
return betaDist.getAlpha() > 0 && betaDist.getBeta() > 0;
}
// check Gauss parameters
if (dev instanceof TimeGaussDistribution) {
final TimeGaussDistribution gaussDist = (TimeGaussDistribution) dev;
final Time mean = gaussDist.getMean();
final Time sd = gaussDist.getSd();
if (!isValidTime(mean)) return false;
if (!isValidTime(sd)) return false;
if (sd.getValue().signum() < 1) return false;
if (!isValidMinAvgMax(gaussDist.getLowerBound(), null, gaussDist.getUpperBound())) return false;
}
return true;
}
public boolean isValidTime(Time time) {
return (time != null && time.getValue() != null && time.getUnit() != TimeUnit._UNDEFINED_);
}
// ***** Some utility functions *****
public <T extends Comparable<T>> boolean isValidMinAvgMax(T min, T avg, T max) {
return isSinglePeek(min, max) || isValidRange(min, avg, max);
}
public <T extends Comparable<T>> boolean isSinglePeek(T min, T max) {
return min != null && max != null && min.compareTo(max) == 0;
}
public <T extends Comparable<T>> boolean isValidRange(T min, T avg, T max) {
if (min != null && max != null && min.compareTo(max) >= 0) {
return false;
}
if (min != null && avg != null && min.compareTo(avg) >= 0) {
return false;
}
if (avg != null && max != null && avg.compareTo(max) >= 0) {
return false;
}
return true;
}
public Time getAverage(TimeInterval obj) {
Time avg = null;
if (obj instanceof TimeStatistics) {
avg = ((TimeStatistics) obj).getAverage();
}
if (obj instanceof TimeWeibullEstimatorsDistribution) {
avg = ((TimeWeibullEstimatorsDistribution) obj).getAverage();
}
return avg;
}
public Double getAverage(DiscreteValueInterval obj) {
Double avg = null;
if (obj instanceof DiscreteValueStatistics) {
avg = ((DiscreteValueStatistics) obj).getAverage();
}
if (obj instanceof DiscreteValueWeibullEstimatorsDistribution) {
avg = ((DiscreteValueWeibullEstimatorsDistribution) obj).getAverage();
}
return avg;
}
public Double getAverage(ContinuousValueInterval obj) {
Double avg = null;
if (obj instanceof ContinuousValueStatistics) {
avg = ((ContinuousValueStatistics) obj).getAverage();
}
if (obj instanceof ContinuousValueWeibullEstimatorsDistribution) {
avg = ((ContinuousValueWeibullEstimatorsDistribution) obj).getAverage();
}
return avg;
}
}