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

Layout and notification system #594

Merged
merged 81 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
879f9aa
Revert "reverted userTickUnit split"
wirew0rm Aug 8, 2023
ce63897
created outline for bit based event system
ennerf Jul 21, 2023
bbd6e38
initial refactoring to migrate axis drawing to the bit flag event system
ennerf Jul 21, 2023
cc91218
changed axis properties to be for display only - computations are don…
ennerf Jul 21, 2023
abee1d5
added debug printer
ennerf Jul 22, 2023
3d87918
created dedicated property util class
ennerf Jul 22, 2023
e0fb8f8
fixed clean check
ennerf Jul 22, 2023
1fde071
improved readability
ennerf Jul 22, 2023
17241b1
updated unit tests
ennerf Jul 22, 2023
5383ce9
improved readability
ennerf Jul 22, 2023
c28f576
changed set range behavior to match current implementation
ennerf Jul 22, 2023
ae92115
fixed a missing cache update
ennerf Jul 22, 2023
5822f05
reverted userTickUnit split
ennerf Jul 22, 2023
a583988
fixed rendering of scaled axes
ennerf Jul 22, 2023
aa1aa3a
added event debugging info
ennerf Jul 22, 2023
b332ed0
cleaned up property overview
ennerf Jul 22, 2023
8fe9cce
fixed cached return value
ennerf Jul 23, 2023
f70f900
cleanup and updated unit tests
ennerf Jul 23, 2023
5b4a1ef
moved constructor up to improve readability
ennerf Jul 23, 2023
e164ed5
removed labels when the axis is nan
ennerf Jul 23, 2023
4df61d9
exposed more debug flags
ennerf Jul 23, 2023
993b00c
fixed scale for inf range
ennerf Jul 23, 2023
639f278
fixed wrong conditions
ennerf Jul 24, 2023
3c64dcb
fixed oscilloscope axis
ennerf Jul 24, 2023
9f998d9
fixed histogram example
ennerf Jul 24, 2023
3a77694
created a thread safe implementation of BitState
ennerf Jul 24, 2023
cd7c64c
setup outline for chart pre/post layout phases
ennerf Jul 25, 2023
b5f66e2
first massive commit for removing the old event system
ennerf Jul 25, 2023
3c995f1
fixed waterfall and mountain range examples
ennerf Jul 26, 2023
621cc04
fixed an initialization issue where postLayout would run by itself
ennerf Jul 26, 2023
2dba3d4
ported more examples
ennerf Jul 26, 2023
f620aea
updated data viewer sample
ennerf Jul 26, 2023
470b1f5
changed data set measurements to compile (broken for now)
ennerf Jul 26, 2023
5e5dd32
removed some outdated unit test code
ennerf Jul 26, 2023
62b3c32
added a default locale for decimal formatter tests
ennerf Jul 27, 2023
28279e4
cleaned up listener code
ennerf Jul 27, 2023
b431993
added global dataset locking between pre and post layout phase
ennerf Jul 27, 2023
c3ff7b3
removed unnecessary locks from renderers
ennerf Jul 27, 2023
f3e90df
removed some unnecessary redraws
ennerf Jul 27, 2023
5f1722f
moved showing property to a utility method
ennerf Jul 27, 2023
418a85b
fixed category axis sample
ennerf Jul 27, 2023
125263b
changed renderer-axis assignment to happen before pre layout
ennerf Jul 27, 2023
26037cd
added more layout hook checks and allow multiple occurences of a sing…
ennerf Jul 27, 2023
09875b8
added architecture documentation
ennerf Jul 28, 2023
2bc955c
removed some unnecessary atomic / concurrent objects
ennerf Jul 28, 2023
c9ac1d3
changed contour renderer to add the extra color bar before the layout
ennerf Jul 29, 2023
b53658f
split data updates into added/removed again
ennerf Jul 31, 2023
e6684c4
migrated math data set and tests
ennerf Jul 31, 2023
ed2ccd3
fixed axis scale
ennerf Aug 1, 2023
9576d23
fixed some unit tests
ennerf Aug 1, 2023
913d6a6
removed explicit layout requests from histogram renderer
ennerf Aug 1, 2023
070d738
bugfix and added compatibility API
ennerf Aug 1, 2023
da8f71a
added even offset and changed to use the same style for all tick marks
ennerf Aug 1, 2023
4afc0fc
fixed a bug where the layout could be called before the JavaFX would …
ennerf Aug 1, 2023
3fa339a
fixed aliasing issue in center axes
ennerf Aug 1, 2023
7bd741d
moved padding and min/pref size to css
ennerf Aug 1, 2023
8c77d79
removed unnecessary parent
ennerf Aug 1, 2023
4100fea
bugfix when disabling a category range
ennerf Aug 1, 2023
f17c15b
fixed an issue when unregistering a scene
ennerf Aug 2, 2023
f7fa0ed
removed pref size from css
ennerf Aug 2, 2023
0b1dd2b
added support for renderer axes that are not part of the chart
ennerf Aug 2, 2023
8c80597
removed a now unnecessary requirement for minimum tick marks
ennerf Aug 2, 2023
9dae77d
changed tick values from boxed lists to primitive lists
ennerf Aug 2, 2023
75e7bf1
added support for stroked text
ennerf Aug 2, 2023
a3e4903
updated tests to use primitive tickmarks
ennerf Aug 2, 2023
bb03ef3
fixed dataset tests by making limit computation explicit
ennerf Aug 2, 2023
638ad88
@Alex: disabled some tests that will need to be updated
ennerf Aug 2, 2023
9631ca5
minor change to keep sonarcloud happy
ennerf Aug 2, 2023
38e925b
added deregistration of legend item listener to avoid potential memor…
ennerf Aug 2, 2023
5daad7b
fixed an issue where switching datasets with the same name were not u…
ennerf Aug 2, 2023
86dc664
fixed an issue where the layout hooks would not be added on a manual …
ennerf Aug 2, 2023
3ea64ba
added a length update in axis layout
ennerf Aug 3, 2023
37d760f
fixed an issue that caused unnecessary redraws on hidden menu animations
ennerf Aug 3, 2023
32d8d0e
fixed grid renderer CSS styling
ennerf Aug 3, 2023
bc20491
added a pref size to keep JavaFX panes from flickering
ennerf Aug 3, 2023
5d15994
added legacy layout request call to fix some old examples
ennerf Aug 4, 2023
283bc50
mapped explicit chart::requestLayout calls to a dedicated method to a…
ennerf Aug 5, 2023
d52e3d0
fixed grow ranging example
ennerf Aug 7, 2023
db257c3
fixed axis range sample
ennerf Aug 7, 2023
9d94974
TableViewer: fix column update and clipbaoard export
wirew0rm Aug 7, 2023
7ed1331
YWatchValueIndicatorTest: update check to value determined by axis fo…
wirew0rm Aug 8, 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
505 changes: 193 additions & 312 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java

Large diffs are not rendered by default.

113 changes: 47 additions & 66 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
package io.fair_acc.chartfx;

import java.security.InvalidParameterException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import io.fair_acc.chartfx.axes.spi.AxisRange;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import io.fair_acc.chartfx.utils.PropUtil;
import io.fair_acc.dataset.events.ChartBits;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.canvas.GraphicsContext;
import javafx.util.Duration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -57,9 +49,6 @@ public class XYChart extends Chart {
protected final BooleanProperty polarPlot = new SimpleBooleanProperty(this, "polarPlot", false);
private final ObjectProperty<PolarTickStep> polarStepSize = new SimpleObjectProperty<>(PolarTickStep.THIRTY);
private final GridRenderer gridRenderer = new GridRenderer();
protected final ChangeListener<? super Boolean> gridLineVisibilitychange = (ob, o, n) -> requestLayout();
private long lastCanvasUpdate;
private boolean callCanvasUpdateLater;

/**
* Construct a new XYChart with the given axes.
Expand Down Expand Up @@ -97,11 +86,18 @@ public XYChart(final Axis... axes) {
getAxes().add(axis);
}

gridRenderer.horizontalGridLinesVisibleProperty().addListener(gridLineVisibilitychange);
gridRenderer.verticalGridLinesVisibleProperty().addListener(gridLineVisibilitychange);
gridRenderer.getHorizontalMinorGrid().visibleProperty().addListener(gridLineVisibilitychange);
gridRenderer.getVerticalMinorGrid().visibleProperty().addListener(gridLineVisibilitychange);
gridRenderer.drawOnTopProperty().addListener(gridLineVisibilitychange);
getChildren().add(0, gridRenderer);
PropUtil.runOnChange(getBitState().onAction(ChartBits.ChartCanvas),
gridRenderer.horizontalGridLinesVisibleProperty(),
gridRenderer.verticalGridLinesVisibleProperty(),
gridRenderer.getHorizontalMinorGrid().visibleProperty(),
gridRenderer.getVerticalMinorGrid().visibleProperty(),
gridRenderer.drawOnTopProperty(),
gridRenderer.getHorizontalMajorGrid().changeCounterProperty(),
gridRenderer.getVerticalMajorGrid().changeCounterProperty(),
gridRenderer.getHorizontalMinorGrid().changeCounterProperty(),
gridRenderer.getVerticalMinorGrid().changeCounterProperty()
);

this.setAnimated(false);
getRenderers().addListener(this::rendererChanged);
Expand All @@ -120,7 +116,12 @@ public ObservableList<DataSet> getAllDatasets() {

allDataSets.clear();
allDataSets.addAll(getDatasets());
getRenderers().stream().filter(renderer -> !(renderer instanceof LabelledMarkerRenderer)).forEach(renderer -> allDataSets.addAll(renderer.getDatasets()));
for (Renderer renderer : getRenderers()) {
if(renderer instanceof LabelledMarkerRenderer){
continue;
}
allDataSets.addAll(renderer.getDatasets());
}

return allDataSets;
}
Expand Down Expand Up @@ -253,26 +254,17 @@ public void updateAxisRange() {
return;
}

// lock datasets to prevent writes while updating the axes
ObservableList<DataSet> dataSets = this.getAllDatasets();
// check that all registered data sets have proper ranges defined
dataSets.parallelStream()
.forEach(dataset -> dataset.getAxisDescriptions().parallelStream().filter(axisD -> !axisD.isDefined()).forEach(axisDescription -> dataset.lock().writeLockGuard(() -> dataset.recomputeLimits(axisDescription.getDimIndex()))));

final ArrayDeque<DataSet> lockQueue = new ArrayDeque<>(dataSets);
recursiveLockGuard(lockQueue, () -> getAxes().forEach(chartAxis -> {
final List<DataSet> dataSetForAxis = getDataSetForAxis(chartAxis);
updateNumericAxis(chartAxis, dataSetForAxis);
// chartAxis.requestAxisLayout()
}));
}
// 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)
.forEach(dataset -> dataset.getAxisDescriptions().parallelStream()
.filter(axisD -> !axisD.isDefined())
.forEach(axisDescription -> dataset.recomputeLimits(axisDescription.getDimIndex())));

// Update each of the axes
getAxes().forEach(chartAxis -> updateNumericAxis(chartAxis, getDataSetForAxis(chartAxis)));

protected void recursiveLockGuard(final Deque<DataSet> queue, final Runnable runnable) { // NOPMD
if (queue.isEmpty()) {
runnable.run();
} else {
queue.pop().lock().readLockGuard(() -> recursiveLockGuard(queue, runnable));
}
}

/**
Expand Down Expand Up @@ -309,7 +301,7 @@ protected void axesChanged(final ListChangeListener.Change<? extends Axis> chang
});
}

requestLayout();
invalidate();
}

/**
Expand Down Expand Up @@ -348,55 +340,47 @@ protected List<DataSet> getDataSetForAxis(final Axis axis) {
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");
}
setAutoNotification(false);
FXUtils.assertJavaFxThread();
final long now = System.nanoTime();
final double diffMillisSinceLastUpdate = TimeUnit.NANOSECONDS.toMillis(now - lastCanvasUpdate);
if (diffMillisSinceLastUpdate < XYChart.BURST_LIMIT_MS) {
if (!callCanvasUpdateLater) {
callCanvasUpdateLater = true;
// repaint 20 ms later in case this was just a burst operation
final KeyFrame kf1 = new KeyFrame(Duration.millis(20), e -> requestLayout());

final Timeline timeline = new Timeline(kf1);
Platform.runLater(timeline::play);
}

return;
}
if (DEBUG && LOGGER.isDebugEnabled()) {
LOGGER.debug(" xychart redrawCanvas() - executing");
LOGGER.debug(" xychart redrawCanvas() - canvas size = {}", String.format("%fx%f", canvas.getWidth(), canvas.getHeight()));
}

lastCanvasUpdate = now;
callCanvasUpdateLater = false;

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

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

// Data
int dataSetOffset = 0;
for (final Renderer renderer : getRenderers()) {
// check for and add required axes
checkRendererForRequiredAxes(renderer);

final List<DataSet> drawnDataSets = renderer.render(gc, this, dataSetOffset, getDatasets());
dataSetOffset += drawnDataSets == null ? 0 : drawnDataSets.size();
}

// Top grid
if (gridRenderer.isDrawOnTop()) {
gridRenderer.render(gc, this, 0, null);
}
setAutoNotification(true);

if (DEBUG && LOGGER.isDebugEnabled()) {
LOGGER.debug(" xychart redrawCanvas() - done");
}
Expand All @@ -407,14 +391,13 @@ protected static void updateNumericAxis(final Axis axis, final List<DataSet> dat
return;
}

final boolean oldAutoState = axis.autoNotification().getAndSet(false);
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 -> dataset.lock().readLockGuard(() -> {
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);
Expand All @@ -429,11 +412,11 @@ protected static void updateNumericAxis(final Axis axis, final List<DataSet> dat
dsRange.add(dataset.getAxisDescription(nDim).getMin());
dsRange.add(dataset.getAxisDescription(nDim).getMax());
}
}));
});

// Update the auto range
final boolean changed;
if (axis.isAutoGrowRanging()) {
if (axis.isAutoGrowRanging() && axis.getAutoRange().isDefined()) {
changed = axis.getAutoRange().add(dsRange);
} else {
changed = axis.getAutoRange().set(dsRange);
Expand All @@ -442,14 +425,12 @@ protected static void updateNumericAxis(final Axis axis, final List<DataSet> dat
// Trigger a redraw
if (changed && (axis.isAutoRanging() || axis.isAutoGrowRanging())) {
axis.invalidateRange();
axis.requestAxisLayout();
}

// TODO: is this used for anything? can it be removed?
double axisLength = axis.getLength() == 0 ? 1 : axis.getLength();
axis.getAutoRange().setAxisLength(axisLength, side);
axis.getUserRange().setAxisLength(axisLength, side);

axis.autoNotification().set(oldAutoState);
}
}
13 changes: 1 addition & 12 deletions chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;

import io.fair_acc.dataset.events.BitState;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
Expand Down Expand Up @@ -202,18 +203,6 @@ public interface Axis extends AxisDescription {
*/
BooleanProperty invertAxisProperty();

/**
* invoke object within update listener list
*
* @param updateEvent the event the listeners are notified with
* @param executeParallel {@code true} execute event listener via parallel executor service
*/
@Override
default void invokeListener(final UpdateEvent updateEvent, final boolean executeParallel) {
// implemented for forwarding purposes
AxisDescription.super.invokeListener(updateEvent, executeParallel);
}

/**
* This is true when the axis determines its range from the data automatically and grows it if necessary
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package io.fair_acc.chartfx.axes;

import java.util.List;

import io.fair_acc.chartfx.axes.spi.format.DefaultTickUnitSupplier;
import io.fair_acc.dataset.spi.fastutil.DoubleArrayList;
import javafx.beans.property.ObjectProperty;

/**
Expand Down Expand Up @@ -61,6 +60,6 @@ public interface AxisLabelFormatter {
* @param newMajorTickMarks for which the labels should be computed
* @param unitScaling scaling applied to the raw data set units
*/
void updateFormatter(List<Double> newMajorTickMarks, double unitScaling);
void updateFormatter(DoubleArrayList newMajorTickMarks, double unitScaling);

}
Loading
Loading