From 845189c63835b086a0f599902aa27b1f711b9455 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 30 Mar 2021 11:58:31 +0200 Subject: [PATCH] experimental smooth I/O charts --- .../cryptomator/ui/stats/SmoothAreaChart.java | 78 +++++++++++++++++++ main/ui/src/main/resources/fxml/stats.fxml | 5 +- 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/stats/SmoothAreaChart.java diff --git a/main/ui/src/main/java/org/cryptomator/ui/stats/SmoothAreaChart.java b/main/ui/src/main/java/org/cryptomator/ui/stats/SmoothAreaChart.java new file mode 100644 index 000000000..ed94f4520 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/stats/SmoothAreaChart.java @@ -0,0 +1,78 @@ +package org.cryptomator.ui.stats; + +import javafx.beans.NamedArg; +import javafx.collections.ObservableList; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.chart.AreaChart; +import javafx.scene.chart.Axis; +import javafx.scene.shape.ClosePath; +import javafx.scene.shape.CubicCurveTo; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; + +// inspired by http://fxexperience.com/2012/01/curve-fitting-and-styling-areachart/ +public class SmoothAreaChart extends AreaChart { + + public SmoothAreaChart(@NamedArg("xAxis") Axis xAxis, @NamedArg("yAxis") Axis yAxis) { + super(xAxis, yAxis); + } + + @Override + protected void layoutPlotChildren() { + super.layoutPlotChildren(); + var iter = getDisplayedSeriesIterator(); + while (iter.hasNext()) { + var series = iter.next(); + final Path seriesLine = (Path)((Group)series.getNode()).getChildren().get(1); + final Path fillPath = (Path)((Group)series.getNode()).getChildren().get(0); + var dx = getXAxis().getWidth() / series.getData().size(); + smooth(seriesLine.getElements(), fillPath.getElements(), dx); + } + } + + private static void smooth(ObservableList strokeElements, ObservableList fillElements, double dx) { + // as we do not have direct access to the data, first recreate the list of all the data points we have + final Point2D[] dataPoints = new Point2D[strokeElements.size()]; + for (int i = 0; i < strokeElements.size(); i++) { + final PathElement element = strokeElements.get(i); + if (element instanceof MoveTo) { + final MoveTo move = (MoveTo)element; + dataPoints[i] = new Point2D(move.getX(), move.getY()); + } else if (element instanceof LineTo) { + final LineTo line = (LineTo)element; + final double x = line.getX(), y = line.getY(); + dataPoints[i] = new Point2D(x, y); + } + } + // next we need to know the zero Y value + final double zeroY = ((MoveTo) fillElements.get(0)).getY(); + final double dx2 = dx / 2.0; + + // now clear and rebuild elements + strokeElements.clear(); + fillElements.clear(); + // start both paths + strokeElements.add(new MoveTo(dataPoints[0].getX(),dataPoints[0].getY())); + fillElements.add(new MoveTo(dataPoints[0].getX(),zeroY)); + fillElements.add(new LineTo(dataPoints[0].getX(),dataPoints[0].getY())); + // add curves + for (int i = 1; i < dataPoints.length; i++) { + final int ci = i-1; + strokeElements.add(new CubicCurveTo( + dataPoints[ci].getX() + dx2, dataPoints[ci].getY(), + dataPoints[i].getX() - dx2, dataPoints[i].getY(), + dataPoints[i].getX(), dataPoints[i].getY())); + fillElements.add(new CubicCurveTo( + dataPoints[ci].getX() + dx2, dataPoints[ci].getY(), + dataPoints[i].getX() - dx2, dataPoints[i].getY(), + dataPoints[i].getX(),dataPoints[i].getY())); + } + // end the paths + fillElements.add(new LineTo(dataPoints[dataPoints.length-1].getX(),zeroY)); + fillElements.add(new ClosePath()); + } + +} diff --git a/main/ui/src/main/resources/fxml/stats.fxml b/main/ui/src/main/resources/fxml/stats.fxml index a2ef920b9..929301fa7 100644 --- a/main/ui/src/main/resources/fxml/stats.fxml +++ b/main/ui/src/main/resources/fxml/stats.fxml @@ -13,6 +13,7 @@ + - + @@ -48,7 +49,7 @@ - +