Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhauled dataset styling #603

Merged
merged 92 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
01ec4b4
fixed comment typo
ennerf Aug 14, 2023
7a5be96
work in progress for stylable datasets that are part of the renderer
ennerf Aug 10, 2023
80a9c9e
removed shared datasets and changed legend item to be drawn at a late…
ennerf Aug 14, 2023
47e07a6
added dedicated dataset node parameters class
ennerf Aug 15, 2023
ac1ae15
changed error data set renderer to use static points rather than the …
ennerf Aug 15, 2023
b155c07
removed unnecessary calls to getArrayExact
ennerf Aug 15, 2023
6e87533
initial replacement of color scheme setters
ennerf Aug 15, 2023
3fe1160
migrated built-in palettes to CSS
ennerf Aug 15, 2023
ddc776d
made palette pseudo class applicable to individual renderers
ennerf Aug 15, 2023
406dfb7
made color palette settable via CSS
ennerf Aug 17, 2023
5ff5785
added buttons for scenic view and CSSFX to sampler welcome page
ennerf Aug 16, 2023
031e6d9
fixed an issue where legend icons would not be cleared before drawing
ennerf Aug 16, 2023
5ed9f4a
added palettes that look up colors from the theme
ennerf Aug 16, 2023
655adfe
removed unnecessary lists from render interface
ennerf Aug 16, 2023
08642fa
changed foreground / background panes to fulll-size panes
ennerf Aug 17, 2023
9b2f3ae
added a Renderer abstraction for XY charts
ennerf Aug 16, 2023
16c201a
migrated contour renderer to setup axes in the pre layout routine
ennerf Aug 16, 2023
a451b42
added a more efficient axis check that does not require adding chart …
ennerf Aug 16, 2023
cffa148
migrated history / mountain / reducing-line renderer to AbstractRende…
ennerf Aug 16, 2023
f7d93be
migrated financial charts to AbstractRendererXY
ennerf Aug 16, 2023
cd7cdd4
added more state to renderer method
ennerf Aug 16, 2023
456270f
made z axis behavior consistent with x and y
ennerf Aug 16, 2023
78f6636
migrated error renderer to CSS
ennerf Aug 17, 2023
66347f8
cosmetic error renderer changes
ennerf Aug 17, 2023
7f2fad9
migrated HistogramRenderer to CSS
ennerf Aug 17, 2023
f061bfe
fixed dataset index computation
ennerf Aug 17, 2023
e0e6bb4
made it more convenient to get to the style nodes
ennerf Aug 17, 2023
8881617
cleaned up custom style parser
ennerf Aug 17, 2023
5e7f024
migrated financial renderers to CSS
ennerf Aug 17, 2023
2ad05b4
migrated financial themes to css
ennerf Aug 18, 2023
92c8016
added hatch shift so multiple errors can be drawn on top of each other
ennerf Aug 18, 2023
54c2838
replaced double array cache in error and history renderers
ennerf Aug 18, 2023
3e6179c
changed empty legend icons to not take any space
ennerf Aug 18, 2023
0dcdf3d
removed coded default color scheme
ennerf Aug 18, 2023
1b71e6e
removed unused triple class
ennerf Aug 18, 2023
0003f17
removed a lot of overhead caused by style checks
ennerf Aug 18, 2023
9b1c774
moved dataset visibility to style node and added css classes to base …
ennerf Aug 18, 2023
417169e
replaced self-made synchronization with API calls
ennerf Aug 18, 2023
981964f
created utility for creating chart css without a JavaFX dependency
ennerf Aug 18, 2023
6d2d6e7
overhauled entire css string styling
ennerf Aug 18, 2023
5f92479
reverted accidental commit
ennerf Aug 18, 2023
a137e83
moved default renderer to the required XYChart
ennerf Aug 18, 2023
a42868e
fixed some broken unit tests
ennerf Aug 18, 2023
620e6db
added support for comma separated double arrays
ennerf Aug 18, 2023
1df6940
simplified sass
ennerf Aug 18, 2023
67cb7e0
bound custom sample properties so cssfx won't interfere
ennerf Aug 18, 2023
66a5f22
renamed setErrorType to setErrorStyle (used a lot)
ennerf Aug 18, 2023
58ee297
added an extra color lookup to simplify overwriting
ennerf Aug 18, 2023
9b68ead
temporarily disabled a test that deadlocks
ennerf Aug 20, 2023
2a30ab5
DataSetMath: catch empty datasets before trying to compute fft
wirew0rm Aug 20, 2023
12dcd68
removed shape/text style properties from default node and simplified…
ennerf Aug 24, 2023
53256a0
updated style parser and builder to support all used properties
ennerf Aug 24, 2023
2b0ed00
added visibility listener
ennerf Aug 24, 2023
499ffe4
removed marker type and size properties that moved to the styleable n…
ennerf Aug 24, 2023
fa47f7b
changed node check from object equality to object identity
ennerf Aug 25, 2023
38f34f6
added styling utility methods
ennerf Aug 29, 2023
313a231
created a profiling framework
ennerf Aug 19, 2023
2684048
added an experimental profiler that shows results live in a chart
ennerf Aug 19, 2023
cf03e14
added profileable interface to Axis and Renderer directly to reduce c…
ennerf Aug 20, 2023
c72cbf3
improved profiling utilities
ennerf Aug 20, 2023
62b95b3
renamed measurement to measure
ennerf Aug 20, 2023
837cad1
moved profiler interface to dataset module
ennerf Aug 20, 2023
5dbe0ac
made locks and datasets profileable
ennerf Aug 20, 2023
aebffc6
added profiling info to xchart
ennerf Aug 20, 2023
a76c573
added better support for recording durations coming from external tim…
ennerf Aug 20, 2023
dd3cd17
adapted some hebi classes to create a hdr-histogram like visualizer
ennerf Aug 20, 2023
46dd766
added a profiling level in addition to tags
ennerf Aug 21, 2023
efc557d
fixed postfix
ennerf Aug 21, 2023
f1072c6
added experimental renderers
ennerf Aug 21, 2023
e744c61
improved renderer w/ polyLine
ennerf Aug 22, 2023
1e3dd65
added a default legend symbol
ennerf Aug 22, 2023
3890874
moved hebi rendererer to spi
ennerf Aug 24, 2023
bfd8128
added experimental cached marker
ennerf Aug 24, 2023
f78b170
disabled nans in profiler
ennerf Aug 24, 2023
da5b517
switched rgb to hex color
ennerf Aug 24, 2023
7ba0838
fixed an issue with the last point being nan
ennerf Aug 28, 2023
084992f
fixed parsing of single numbers for an array
ennerf Aug 28, 2023
5d16b26
fixed comment typo
ennerf Sep 14, 2023
0f9574d
major renaming of benchmark classes
ennerf Sep 14, 2023
a0a82fd
removed swing based renderer
ennerf Sep 14, 2023
142e455
Merge branch 'ennerf/experimental-renderers' into ennerf/profiling
ennerf Sep 14, 2023
bdaf88c
reverted accidental name change
ennerf Sep 15, 2023
b4ade23
created a base class for xyz renderers
ennerf Sep 15, 2023
6481506
moved axis range updates to the renderers
ennerf Sep 15, 2023
09a05d9
fixed intensified history color
ennerf Sep 15, 2023
69cd092
simplfiied history data set renderer
ennerf Sep 15, 2023
0b7e3f1
cosmetic name changes
ennerf Sep 15, 2023
12421b2
removed unnecessary getDataSetsCopy method
ennerf Sep 15, 2023
34f690b
moved renderer axis adding to renderer
ennerf Sep 15, 2023
1a353c5
Merge pull request #604 profiling
ennerf Sep 18, 2023
5307d43
fixed unit test string
ennerf Sep 18, 2023
755626f
removed duplicate color def
ennerf Sep 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions chartfx-chart/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@
<artifactId>pngj</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
<version>2.1.12</version>
</dependency>
</dependencies>

<build>
Expand Down
223 changes: 131 additions & 92 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java

Large diffs are not rendered by default.

228 changes: 100 additions & 128 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.fair_acc.chartfx;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import io.fair_acc.chartfx.axes.spi.AxisRange;
import io.fair_acc.chartfx.plugins.ChartPlugin;
import io.fair_acc.chartfx.renderer.spi.ErrorDataSetRenderer;
import io.fair_acc.chartfx.ui.css.DataSetNode;
import io.fair_acc.chartfx.utils.PropUtil;
import io.fair_acc.dataset.benchmark.MeasurementRecorder;
import io.fair_acc.dataset.events.ChartBits;
import io.fair_acc.dataset.benchmark.DurationMeasure;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
Expand All @@ -18,13 +18,9 @@
import javafx.geometry.Orientation;
import javafx.scene.canvas.GraphicsContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.fair_acc.chartfx.axes.Axis;
import io.fair_acc.chartfx.renderer.PolarTickStep;
import io.fair_acc.chartfx.renderer.Renderer;
import io.fair_acc.chartfx.renderer.spi.ErrorDataSetRenderer;
import io.fair_acc.chartfx.renderer.spi.GridRenderer;
import io.fair_acc.chartfx.renderer.spi.LabelledMarkerRenderer;
import io.fair_acc.chartfx.ui.geometry.Side;
Expand All @@ -44,15 +40,13 @@
* @author rstein
*/
public class XYChart extends Chart {
private static final Logger LOGGER = LoggerFactory.getLogger(XYChart.class);
protected static final int BURST_LIMIT_MS = 15;
protected final BooleanProperty polarPlot = new SimpleBooleanProperty(this, "polarPlot", false);
private final ObjectProperty<PolarTickStep> polarStepSize = new SimpleObjectProperty<>(PolarTickStep.THIRTY);
private final GridRenderer gridRenderer = new GridRenderer();
private final GridRenderer gridRenderer = new GridRenderer(this);

/**
* Construct a new XYChart with the given axes.
*
*/
public XYChart() {
this(new Axis[] {}); // NOPMD NOSONAR
Expand Down Expand Up @@ -94,12 +88,22 @@ public XYChart(final Axis... axes) {
gridRenderer.getVerticalMinorGrid().changeCounterProperty(),
gridRenderer.drawOnTopProperty());

this.setAnimated(false);
getRenderers().addListener(this::rendererChanged);

// TODO: get rid of default instance. It's created if anyone wants to use getDatasets()
getRenderers().add(new ErrorDataSetRenderer());
}

/**
* @return datasets of the first renderer. Creates a renderer if needed.
*/
public ObservableList<DataSet> getDatasets() {
if (getRenderers().isEmpty()) {
getRenderers().add(new ErrorDataSetRenderer());
}
return getRenderers().get(0).getDatasets();
}

/**
* @return datasets attached to the chart and datasets attached to all renderers
*/
Expand All @@ -110,14 +114,12 @@ public ObservableList<DataSet> getAllDatasets() {
}

allDataSets.clear();
allDataSets.addAll(getDatasets());
for (Renderer renderer : getRenderers()) {
if(renderer instanceof LabelledMarkerRenderer){
if (renderer instanceof LabelledMarkerRenderer) {
continue;
}
allDataSets.addAll(renderer.getDatasets());
}

return allDataSets;
}

Expand All @@ -127,7 +129,6 @@ public ObservableList<DataSet> getAllDatasets() {
*/
public ObservableList<DataSet> getAllShownDatasets() {
final ObservableList<DataSet> ret = FXCollections.observableArrayList();
ret.addAll(getDatasets());
getRenderers().stream().filter(Renderer::showInLegend).forEach(renderer -> ret.addAll(renderer.getDatasets()));
return ret;
}
Expand Down Expand Up @@ -200,28 +201,49 @@ public void setPolarStepSize(final PolarTickStep step) {

@Override
public void updateAxisRange() {
if (isDataEmpty()) {
return;
}

// Check that all registered data sets have proper ranges defined. The datasets
// are already locked, so we can use parallel stream without extra synchronization.
getAllDatasets().stream()
.filter(DataSet::isVisible)
.filter(ds -> ds.getBitState().isDirty())
// Update the axis definitions of all datasets. We do it here, so we can make better
// use of multi-threading. The datasets are already locked, so we can use a parallel
// stream without extra synchronization.
getRenderers().stream()
.flatMap(renderer -> renderer.getDatasetNodes().stream())
.filter(DataSetNode::isVisible)
.map(DataSetNode::getDataSet)
.filter(ds -> ds.getBitState().isDirty(ChartBits.DataSetData, ChartBits.DataSetRange))
.distinct()
.forEach(dataset -> dataset.getAxisDescriptions().parallelStream()
.filter(axisD -> !axisD.isDefined() || axisD.getBitState().isDirty())
.forEach(axisDescription -> dataset.recomputeLimits(axisDescription.getDimIndex())));

// Update each of the axes
getAxes().forEach(chartAxis -> updateNumericAxis(chartAxis, getDataSetForAxis(chartAxis)));
// Update each axis
for (Axis axis : getAxes()) {

}
// Determine the current range
axisRange.clear();
for (Renderer renderer : getRenderers()) {
renderer.updateAxisRange(axis, axisRange);
}

// Update the internal auto range
boolean changed = false;
if (axis.isAutoGrowRanging() && axis.getAutoRange().isDefined()) {
if (axisRange.isDefined()) {
changed = axis.getAutoRange().add(axisRange);
}
} else {
changed = axis.getAutoRange().set(axisRange.getMin(), axisRange.getMax());
}

// Trigger a redraw
if (changed && (axis.isAutoRanging() || axis.isAutoGrowRanging())) {
axis.invalidateRange();
}

}

private boolean isDataEmpty() {
return getAllDatasets() == null || getAllDatasets().isEmpty();
}

private final AxisRange axisRange = new AxisRange();

/**
* add XYChart specific axis handling (ie. placement around charts, add new DefaultNumericAxis if one is missing,
* etc.)
Expand All @@ -246,128 +268,78 @@ protected void axesChanged(final ListChangeListener.Change<? extends Axis> chang
invalidate();
}

/**
* checks whether renderer has required x and y axes and adds the first x or y from the chart itself if necessary
* <p>
* additionally moves axis from Renderer with defined Side that are not yet in the Chart also to the chart's list
*
* @param renderer to be checked
*/
protected void checkRendererForRequiredAxes(final Renderer renderer) {
if (renderer.getAxes().size() < 2) {
// not enough axes present in renderer
Optional<Axis> xAxis = renderer.getAxes().stream().filter(a -> a.getSide().isHorizontal()).findFirst();
Optional<Axis> yAxis = renderer.getAxes().stream().filter(a -> a.getSide().isVertical()).findFirst();

// search for horizontal/vertical axes in Chart (which creates one if missing) and add to renderer
if (xAxis.isEmpty()) {
renderer.getAxes().add(getFirstAxis(Orientation.HORIZONTAL));
}
if (yAxis.isEmpty()) {
// search for horizontal axis in Chart (which creates one if missing) and add to renderer
renderer.getAxes().add(getFirstAxis(Orientation.VERTICAL));
}
}
// check if there are assignable axes not yet present in the Chart's list
getAxes().addAll(renderer.getAxes().stream().limit(2).filter(a -> (a.getSide() != null && !getAxes().contains(a))).collect(Collectors.toList()));
}

protected List<DataSet> getDataSetForAxis(final Axis axis) {
final List<DataSet> retVal = new ArrayList<>();
if (axis == null) {
return retVal;
}
retVal.addAll(getDatasets());
getRenderers().forEach(renderer -> renderer.getAxes().stream().filter(axis::equals).forEach(rendererAxis -> retVal.addAll(renderer.getDatasets())));
return retVal;
}

@Override
protected void runPreLayout() {
for (Renderer renderer : getRenderers()) {
// check for and add required axes
checkRendererForRequiredAxes(renderer);
}
super.runPreLayout();
}

@Override
protected void redrawCanvas() {
if (DEBUG && LOGGER.isDebugEnabled()) {
LOGGER.debug(" xychart redrawCanvas() - pre");
}
FXUtils.assertJavaFxThread();
final long now = System.nanoTime();
if (DEBUG && LOGGER.isDebugEnabled()) {
LOGGER.debug(" xychart redrawCanvas() - executing");
LOGGER.debug(" xychart redrawCanvas() - canvas size = {}", String.format("%fx%f", canvas.getWidth(), canvas.getHeight()));
}

final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());

// Bottom grid
if (!gridRenderer.isDrawOnTop()) {
gridRenderer.render(gc, this, 0, null);
benchDrawGrid.start();
gridRenderer.render();
benchDrawGrid.stop();
}

// Data
int dataSetOffset = 0;
benchDrawData.start();
for (final Renderer renderer : getRenderers()) {
final List<DataSet> drawnDataSets = renderer.render(gc, this, dataSetOffset, getDatasets());
dataSetOffset += drawnDataSets == null ? 0 : drawnDataSets.size();
renderer.render();
}
benchDrawData.stop();

// Top grid
if (gridRenderer.isDrawOnTop()) {
gridRenderer.render(gc, this, 0, null);
benchDrawGrid.start();
gridRenderer.render();
benchDrawGrid.stop();
}

if (DEBUG && LOGGER.isDebugEnabled()) {
LOGGER.debug(" xychart redrawCanvas() - done");
}
}

protected static void updateNumericAxis(final Axis axis, final List<DataSet> dataSets) {
if (dataSets == null || dataSets.isEmpty()) {
return;
}

final Side side = axis.getSide();
final boolean isHorizontal = side.isHorizontal();

// Determine the range of all datasets for this axis
final AxisRange dsRange = new AxisRange();
dsRange.clear();
dataSets.stream().filter(DataSet::isVisible).forEach(dataset -> {
if (dataset.getDimension() > 2 && (side == Side.RIGHT || side == Side.TOP)) {
if (!dataset.getAxisDescription(DataSet.DIM_Z).isDefined()) {
dataset.recomputeLimits(DataSet.DIM_Z);
}
dsRange.add(dataset.getAxisDescription(DataSet.DIM_Z).getMin());
dsRange.add(dataset.getAxisDescription(DataSet.DIM_Z).getMax());
/**
* @param recorder recorder for this chart and all nested components
*/
public void setGlobalRecorder(MeasurementRecorder recorder) {
setRecorder(recorder);
int i = 0;
for (Axis axis : getAxes()) {
if (axis == getXAxis()) {
axis.setRecorder(recorder.addPrefix("x"));
} else if (axis == getYAxis()) {
axis.setRecorder(recorder.addPrefix("y"));
} else {
final int nDim = isHorizontal ? DataSet.DIM_X : DataSet.DIM_Y;
if (!dataset.getAxisDescription(nDim).isDefined()) {
dataset.recomputeLimits(nDim);
}
dsRange.add(dataset.getAxisDescription(nDim).getMin());
dsRange.add(dataset.getAxisDescription(nDim).getMax());
axis.setRecorder(recorder.addPrefix("axis" + i++));
}
});

// Update the auto range
final boolean changed;
if (axis.isAutoGrowRanging() && axis.getAutoRange().isDefined()) {
changed = axis.getAutoRange().add(dsRange);
} else {
changed = axis.getAutoRange().set(dsRange.getMin(), dsRange.getMax());
}

// Trigger a redraw
if (changed && (axis.isAutoRanging() || axis.isAutoGrowRanging())) {
axis.invalidateRange();
i = 0;
gridRenderer.setRecorder(recorder);
for (var renderer : getRenderers()) {
var p = recorder.addPrefix("renderer" + i);
renderer.setRecorder(p);
int dsIx = 0;
for (var dataset : renderer.getDatasets()) {
dataset.setRecorder(p.addPrefix("ds" + dsIx));
dataset.lock().setRecorder(p.addPrefix("ds" + dsIx));
dsIx++;
}
i++;
}
i = 0;
for (ChartPlugin plugin : getPlugins()) {
plugin.setRecorder(recorder.addPrefix("plugin" + i++));
}
}

@Override
public void setRecorder(MeasurementRecorder recorder) {
super.setRecorder(recorder);
benchDrawGrid = recorder.newDebugDuration("xychart-drawGrid");
benchDrawData = recorder.newDebugDuration("xychart-drawData");
}

private DurationMeasure benchDrawGrid = DurationMeasure.DISABLED;
private DurationMeasure benchDrawData = DurationMeasure.DISABLED;

}
30 changes: 0 additions & 30 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChartCss.java

This file was deleted.

Loading