Compare commits

...

1 Commits

Author SHA1 Message Date
Sebastian Stenzel
845189c638 experimental smooth I/O charts 2021-03-30 11:58:31 +02:00
2 changed files with 81 additions and 2 deletions

View File

@@ -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<Number, Number> {
public SmoothAreaChart(@NamedArg("xAxis") Axis<Number> xAxis, @NamedArg("yAxis") Axis<Number> 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<PathElement> strokeElements, ObservableList<PathElement> 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());
}
}

View File

@@ -13,6 +13,7 @@
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?>
<?import org.cryptomator.ui.stats.SmoothAreaChart?>
<HBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.stats.VaultStatisticsController"
@@ -38,7 +39,7 @@
<!-- Read -->
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
<ThrougputLabel styleClass="label-large" idleFormat="%stats.read.throughput.idle" kibsFormat="%stats.read.throughput.kibs" mibsFormat="%stats.read.throughput.mibs" bytesPerSecond="${controller.bpsRead}"/>
<AreaChart fx:id="readChart" styleClass="io-stats" createSymbols="false" animated="false">
<SmoothAreaChart fx:id="readChart" styleClass="io-stats" createSymbols="false" animated="false">
<xAxis>
<NumberAxis fx:id="readChartXAxis" styleClass="io-stats" autoRanging="false" forceZeroInRange="false" side="BOTTOM"/>
</xAxis>
@@ -48,7 +49,7 @@
<cursor>
<Cursor fx:constant="DEFAULT"/>
</cursor>
</AreaChart>
</SmoothAreaChart>
<DataLabel byteFormat="%stats.read.total.data.none" kibFormat="%stats.read.total.data.kib" mibFormat="%stats.read.total.data.mib" gibFormat="%stats.read.total.data.gib" dataInBytes="${controller.totalBytesRead}"/>
<DataLabel byteFormat="%stats.decr.total.data.none" kibFormat="%stats.decr.total.data.kib" mibFormat="%stats.decr.total.data.mib" gibFormat="%stats.decr.total.data.gib" dataInBytes="${controller.totalBytesDecrypted}"/>
<FormattedLabel format="%stats.read.accessCount" arg1="${controller.filesRead}"/>