From 879f9aa8b4459b7f223844f69c136f584a8e258f Mon Sep 17 00:00:00 2001 From: Alexander Krimm Date: Tue, 8 Aug 2023 19:38:24 +0200 Subject: [PATCH 01/81] Revert "reverted userTickUnit split" This reverts commit 5703417953c0c4dca0f0f924018b2340b32a73cc. --- .../java/io/fair_acc/chartfx/axes/Axis.java | 15 +++++----- .../chartfx/axes/spi/AbstractAxis.java | 2 +- .../axes/spi/AbstractAxisParameter.java | 30 ++++++++++++++++--- .../io/fair_acc/chartfx/plugins/EditAxis.java | 6 ++-- .../renderer/spi/MountainRangeRenderer.java | 2 +- .../chartfx/utils/AxisSynchronizer.java | 4 +-- .../utils/MasterSlaveAxisSynchronizer.java | 4 +-- .../sample/chart/TimeAxisRangeSample.java | 3 +- .../sample/chart/legacy/utils/TestChart.java | 3 +- .../sample/math/DataSetAverageSample.java | 2 +- 10 files changed, 48 insertions(+), 23 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java index 1d14afd88..7f9a8dc04 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java @@ -1,11 +1,6 @@ package io.fair_acc.chartfx.axes; -import java.util.List; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.StringProperty; +import javafx.beans.property.*; import javafx.collections.ObservableList; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; @@ -150,6 +145,8 @@ public interface Axis extends AxisDescription { double getTickUnit(); + double getUserTickUnit(); + /** * @return axis primary unit scaling */ @@ -311,7 +308,7 @@ default void invokeListener(final UpdateEvent updateEvent, final boolean execute void setSide(Side newSide); - void setTickUnit(double tickUnit); + void setUserTickUnit(double tickUnit); /** * This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition @@ -337,7 +334,9 @@ default void invokeListener(final UpdateEvent updateEvent, final boolean execute ObjectProperty sideProperty(); - DoubleProperty tickUnitProperty(); + ReadOnlyDoubleProperty tickUnitProperty(); + + DoubleProperty userTickUnitProperty(); /** * This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index f8d3963b3..58b56a5dd 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -309,7 +309,7 @@ protected void updateAxisContents() { } // Tick units - double mTickUnit = getTickUnit(); + double mTickUnit = getUserTickUnit(); if (isAutoRanging() || isAutoGrowRanging() || true /* TODO: zoom never changes scale */){ mTickUnit = computePreferredTickUnit(length); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 259cccbb9..9d9eefd92 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -348,7 +348,10 @@ protected void invalidated() { } }; - protected final transient StyleableDoubleProperty tickUnit = CSS.createDoubleProperty(this, "tickUnit", DEFAULT_TICK_UNIT, () -> { + /** + * user-set tick units when doing auto-ranging + */ + protected final transient StyleableDoubleProperty userTickUnit = CSS.createDoubleProperty(this, "userTickUnit", DEFAULT_TICK_UNIT, () -> { if (isAutoRanging() || isAutoGrowRanging()) { return; } @@ -356,6 +359,11 @@ protected void invalidated() { invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); }); + /** + * system-set tick units for getting the currently displayed units + */ + protected final transient DoubleProperty tickUnit = new SimpleDoubleProperty(Double.NaN); + protected final transient ChangeListener scaleChangeListener = (ch, o, n) -> { final double axisLength = getLength(); // [pixel] final double lowerBound = getMin(); @@ -789,6 +797,11 @@ public double getTickUnit() { return tickUnitProperty().get(); } + @Override + public double getUserTickUnit() { + return userTickUnitProperty().get(); + } + @Override public String getUnit() { return unitProperty().get(); @@ -1185,9 +1198,13 @@ public void setTickMarkVisible(final boolean value) { * * @param unit major tick unit */ + protected void setTickUnit(final double unit) { + tickUnit.set(unit); + } + @Override - public void setTickUnit(final double unit) { - tickUnitProperty().set(unit); + public void setUserTickUnit(final double unit) { + userTickUnit.set(unit); } /** @@ -1270,10 +1287,15 @@ public BooleanProperty tickMarkVisibleProperty() { * @return tickUnit property */ @Override - public DoubleProperty tickUnitProperty() { + public ReadOnlyDoubleProperty tickUnitProperty() { return tickUnit; } + @Override + public DoubleProperty userTickUnitProperty() { + return userTickUnit; + } + /** * This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition * diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java index d7030115d..2ee162c42 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java @@ -310,7 +310,8 @@ private void changeAxisRangeLimit(final Axis axis, final boolean isHorizontal, f if (axis instanceof AbstractAxis) { // ((AbstractAxis) axis).recomputeTickMarks(); - axis.setTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); + ((AbstractAxis) axis).setAutoRanging(false); // TODO: this used to set internal units. Is this ok? + axis.setUserTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); } } @@ -425,7 +426,8 @@ private TextField getBoundField(final Axis axis, final boolean isLowerBound) { if (axis instanceof AbstractAxis) { // ((AbstractAxis) axis).recomputeTickMarks(); - axis.setTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); + ((AbstractAxis) axis).setAutoRanging(false); // TODO: this used to set internal units. Is this ok? + axis.setUserTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); if (LOGGER.isDebugEnabled()) { LOGGER.debug("recompute axis tick unit to {}", ((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java index d10a88394..a0b9708cb 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java @@ -108,7 +108,7 @@ public List render(final GraphicsContext gc, final Chart chart, final i yAxis.setAutoRanging(false); yAxis.setMin(zRangeMin); yAxis.setMax(max); - yAxis.setTickUnit(Math.abs(max - zRangeMin) / 10.0); + yAxis.setUserTickUnit(Math.abs(max - zRangeMin) / 10.0); yAxis.forceRedraw(); } yAxis.setAutoRanging(autoRange); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java index 63054eda2..8e8127a1d 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java @@ -69,7 +69,7 @@ private void lowerBoundChanged(ObservableValue property, Numbe axis.setMin(value); axis.setAutoRanging(false); } - axis.setTickUnit(tickUnit); + axis.setUserTickUnit(tickUnit); } updating = false; } @@ -102,7 +102,7 @@ private void upperBoundChanged(ObservableValue property, Numbe axis.setAutoRanging(false); axis.setMax(value); } - axis.setTickUnit(tickUnit); + axis.setUserTickUnit(tickUnit); } updating = false; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java index 833e4a834..584c798a6 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java @@ -31,7 +31,7 @@ public MasterSlaveAxisSynchronizer(AbstractAxis master) { public void add(AbstractAxis axis) { slaves.add(axis); axis.setAutoRanging(false); - axis.tickUnitProperty().bind(master.tickUnitProperty()); + axis.userTickUnitProperty().bind(master.tickUnitProperty()); } private void lowerBoundChanged(double value) { @@ -46,7 +46,7 @@ private void lowerBoundChanged(double value) { public void remove(AbstractAxis axis) { slaves.remove(axis); - axis.tickUnitProperty().unbind(); + axis.userTickUnitProperty().unbind(); axis.setAutoRanging(true); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java index 5bdbe4661..57ecb8df8 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java @@ -73,7 +73,8 @@ public void run() { xAxisDyn.minProperty().set(now - range); final String text = "actual range [s]: " + String.format("%#.3f", range) + " (" + String.format("%#.1f", range / 3600 / 24) + " days)"; - xAxisDyn.setTickUnit(range / 12); + xAxisDyn.setAutoRanging(false); + xAxisDyn.setUserTickUnit(range / 12); xAxisDyn.forceRedraw(); xAxis9Text.setText(text); }); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java index 7b0599a1f..f95199880 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java @@ -86,7 +86,8 @@ public void setNumberOfSamples(int nSamples) { chart.getDatasets().setAll(testFunction); xAxis.setMax(nSamples - 1.0); xAxis.setMin(0); - xAxis.setTickUnit(nSamples / 20.0); + xAxis.setAutoRanging(false); + xAxis.setUserTickUnit(nSamples / 20.0); updateDataSet(); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java index 631d9b6c2..217b55eb9 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java @@ -37,7 +37,7 @@ public Node getContent() { chart.getYAxis(1).setAutoRanging(false); chart.getYAxis().maxProperty().bindBidirectional(chart.getYAxis(1).maxProperty()); chart.getYAxis().minProperty().bindBidirectional(chart.getYAxis(1).minProperty()); - chart.getYAxis().tickUnitProperty().bindBidirectional(chart.getYAxis(1).tickUnitProperty()); + chart.getYAxis().userTickUnitProperty().bindBidirectional(chart.getYAxis(1).userTickUnitProperty()); // TODO: used tickUnitProperty. Make sure it still works. LimitedQueue lastDataSets = new LimitedQueue<>(N_GRAPHS); for (int i = 0; i < 20 * N_GRAPHS; i++) { From ce6389732aa3336e4f7150beb4ddf1ee30505658 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 21 Jul 2023 08:09:46 +0200 Subject: [PATCH 02/81] created outline for bit based event system --- .../chartfx/axes/spi/AbstractAxis.java | 10 +- .../axes/spi/AbstractAxisParameter.java | 56 ++--- .../io/fair_acc/dataset/events/BitState.java | 205 ++++++++++++++++++ .../io/fair_acc/dataset/events/ChartBits.java | 27 +++ .../dataset/events/StateListener.java | 26 +++ 5 files changed, 298 insertions(+), 26 deletions(-) create mode 100644 chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java create mode 100644 chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java create mode 100644 chartfx-dataset/src/main/java/io/fair_acc/dataset/events/StateListener.java diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 58b56a5dd..eafbe025f 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -6,6 +6,9 @@ import io.fair_acc.chartfx.ui.css.PathStyle; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.utils.FXUtils; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; +import io.fair_acc.dataset.events.StateListener; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -90,6 +93,10 @@ protected AbstractAxis() { } getChildren().add(canvas); + dirty.addChangeListener(BitState.mask(ChartBits.Layout), (source, bits) -> { + super.requestLayout(); + }); + // set default axis title/label alignment updateTickLabelAlignment(); updateAxisLabelAlignment(); @@ -321,6 +328,7 @@ protected void updateAxisContents() { updateMinorTickMarks(); updateTickMarkPositions(getTickMarks()); updateTickMarkPositions(getMinorTickMarks()); + dirty.clear(); valid.set(true); } @@ -346,7 +354,7 @@ public AxisRange getRange() { */ @Override public void requestAxisLayout() { - super.requestLayout(); + dirty.set(ChartBits.Layout); } public void setAxisLabelFormatter(final AxisLabelFormatter value) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 9d9eefd92..ac5f7711c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -10,6 +10,8 @@ import io.fair_acc.chartfx.ui.css.StyleUtil; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.ui.layout.ChartPane; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; import javafx.beans.binding.Bindings; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; @@ -42,6 +44,10 @@ * @author rstein */ public abstract class AbstractAxisParameter extends Pane implements Axis { + protected final BitState dirty = new BitState(this); + protected final ChangeListener changeLayoutListener = dirty.onPropChange(ChartBits.Layout)::set; + protected final Runnable changeLayoutAction = dirty.onAction(ChartBits.Layout); + private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Region.getClassCssMetaData()); protected static final int MAX_TICK_COUNT = 20; @@ -56,7 +62,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); - private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, this::requestAxisLayout); + private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, changeLayoutAction); /** * Nodes used for css-type styling. Not used for actual drawing. Used as a storage container for the settings @@ -72,10 +78,10 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { { StyleUtil.addStyles(this, "axis"); getChildren().addAll(axisLabel, tickLabelStyle, majorTickStyle, minorTickStyle); - majorTickStyle.changeCounterProperty().addListener((obs, old, value) -> requestAxisLayout()); - minorTickStyle.changeCounterProperty().addListener((obs, old, value) -> requestAxisLayout()); - tickLabelStyle.changeCounterProperty().addListener((obs, old, value) -> requestAxisLayout()); - axisLabel.changeCounterProperty().addListener((obs, old, value) -> requestAxisLayout()); + majorTickStyle.changeCounterProperty().addListener(changeLayoutListener); + minorTickStyle.changeCounterProperty().addListener(changeLayoutListener); + tickLabelStyle.changeCounterProperty().addListener(changeLayoutListener); + axisLabel.changeCounterProperty().addListener(changeLayoutListener); } protected final transient BooleanProperty valid = new SimpleBooleanProperty(this, "valid", false); @@ -118,22 +124,22 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The relative alignment (N.B. clamped to [0.0,1.0]) of the axis if drawn on top of the main canvas (N.B. side == CENTER_HOR or CENTER_VER */ - private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), this::requestAxisLayout); + private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), changeLayoutAction); /** * axis label alignment */ - private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), this::requestAxisLayout); + private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), changeLayoutAction); /** * The axis label */ - private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", this::requestAxisLayout); + private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", changeLayoutAction); /** * true if tick marks should be displayed */ - private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, this::requestAxisLayout); + private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, changeLayoutAction); /** * true if tick mark labels should be displayed @@ -147,22 +153,22 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, this::requestAxisLayout); + private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, changeLayoutAction); /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, this::requestAxisLayout); + private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, changeLayoutAction); /** * This is true when the axis determines its range from the data automatically */ - private final transient StyleableBooleanProperty autoRanging = CSS.createBooleanProperty(this, "autoRanging", true, this::requestAxisLayout); + private final transient StyleableBooleanProperty autoRanging = CSS.createBooleanProperty(this, "autoRanging", true, changeLayoutAction); /** * The font for all tick labels */ - private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, this::requestAxisLayout); + private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, changeLayoutAction); /** * The fill for all tick labels @@ -172,32 +178,32 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The gap between tick marks and the canvas area */ - private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0, this::requestAxisLayout); + private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0, changeLayoutAction); /** * The gap between tick labels and the tick mark lines */ - private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, this::requestAxisLayout); + private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, changeLayoutAction); /** * The minimum gap between tick labels */ - private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, this::requestAxisLayout); + private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, changeLayoutAction); /** * The gap between tick labels and the axis label */ - private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, this::requestAxisLayout); + private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, changeLayoutAction); /** * The animation duration in MS */ - private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, this::requestAxisLayout); + private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, changeLayoutAction); /** * The maximum number of ticks */ - private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, this::requestAxisLayout); + private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, changeLayoutAction); /** * When true any changes to the axis and its range will be animated. @@ -207,12 +213,12 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * Rotation in degrees of tick mark labels from their normal horizontal. */ - protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, this::requestAxisLayout); + protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, changeLayoutAction); /** * true if minor tick marks should be displayed */ - private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, this::requestAxisLayout); + private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, changeLayoutAction); /** * The scale factor from data units to visual units @@ -278,16 +284,16 @@ protected void invalidated() { /** * The length of minor tick mark lines. Set to 0 to not display minor tick marks. */ - private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, this::requestAxisLayout); + private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, changeLayoutAction); /** * The number of minor tick divisions to be displayed between each major tick mark. The number of actual minor tick * marks will be one less than this. N.B. number of divisions, minor tick mark is not drawn if minorTickMark == * majorTickMark */ - private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, this::requestAxisLayout); + private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, changeLayoutAction); - private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, this::requestAxisLayout); + private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, changeLayoutAction); protected boolean isInvertedAxis = false; // internal use (for performance reason) private final transient BooleanProperty invertAxis = new SimpleBooleanProperty(this, "invertAxis", false) { @@ -314,7 +320,7 @@ protected void invalidated() { } }; - private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, this::requestAxisLayout); + private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, changeLayoutAction); private final transient DoubleProperty autoRangePadding = new SimpleDoubleProperty(0); diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java new file mode 100644 index 000000000..368e33487 --- /dev/null +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -0,0 +1,205 @@ +package io.fair_acc.dataset.events; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.IntSupplier; + +/** + * @author ennerf + */ +public class BitState implements StateListener { + + public Runnable onAction(IntSupplier bit0, IntSupplier... more) { + return onAction(mask(bit0, more)); + } + + public Runnable onAction(int bits) { + return () -> set(bits); + } + + public OnChangeSetter onPropChange(IntSupplier bit0, IntSupplier... more) { + return onPropChange(mask(bit0, more)); + } + + public OnChangeSetter onPropChange(int bits) { + return (obs, o, v) -> set(bits); + } + + public OnInvalidateSetter onPropInvalidate(IntSupplier bit0, IntSupplier... more) { + return onPropInvalidate(mask(bit0, more)); + } + + public OnInvalidateSetter onPropInvalidate(int bits) { + return obs -> set(bits); + } + + public static int mask(IntSupplier[] bits){ + int mask = 0; + for (IntSupplier bit : bits) { + mask |= bit.getAsInt(); + } + return mask; + } + + public static int mask(IntSupplier bit0, IntSupplier... more) { + int mask = bit0.getAsInt(); + for (var bit : more) { + mask |= bit.getAsInt(); + } + return mask; + } + + public void set(int bits) { + final int filtered = bits & filter; + final int delta = (state ^ filtered) & filtered; + if (delta != 0) { + state |= filtered; + notifyListeners(changeListeners, delta); + } + notifyListeners(invalidateListeners, bits); + } + + @Override + public void accept(BitState source, int bits) { + set(bits); + } + + public void set(IntSupplier bit0, IntSupplier... bits) { + set(mask(bit0, bits)); + } + + public boolean isDirty() { + return state != 0; + } + + public boolean isDirty(int mask) { + return (state & mask) == 0; + } + + public boolean isDirty(IntSupplier bit0, IntSupplier... bits) { + return isDirty(mask(bit0, bits)); + } + + public void clear() { + state = 0; + } + + public int clear(final int mask) { + state &= ~mask; + return state; + } + + public BitState addChangeListener(int filter, StateListener listener) { + return addChangeListener(new FilteredListener(filter, listener)); + } + + public BitState addInvalidateListener(int filter, StateListener listener) { + return addInvalidateListener(new FilteredListener(filter, listener)); + } + + public BitState addChangeListener(StateListener listener) { + if(changeListeners == null) { + changeListeners = new ArrayList<>(); + } + changeListeners.add(listener); + return this; + } + + public BitState addInvalidateListener(StateListener listener) { + if(invalidateListeners == null) { + invalidateListeners = new ArrayList<>(); + } + invalidateListeners.add(listener); + return this; + } + + public boolean removeChangeListener(StateListener listener) { + return removeListener(changeListeners, listener); + } + + public boolean removeInvalidateListener(StateListener listener) { + return removeListener(invalidateListeners, listener); + } + + private static boolean removeListener(List list, StateListener listener) { + if (list == null) { + return false; + } + int removed = 0; + for (int i = list.size() - 1; i >= 0; i--) { + if (isEqual(list.get(i), listener)) { + list.remove(i); + removed++; + } + } + if (removed > 1) { + throw new IllegalStateException("Can't remove targets that have been added more than once"); + } + return removed == 1; + } + + private void notifyListeners(List list, int delta) { + if (list != null) { + for (StateListener onChangeListener : list) { + onChangeListener.accept(this, delta); + } + } + } + + private static boolean isEqual(StateListener internal, StateListener listener) { + if (internal == listener) { + return true; + } + if (internal instanceof FilteredListener) { + return ((FilteredListener) internal).listener == listener; + } + return false; + } + + private static class FilteredListener implements StateListener { + + private FilteredListener(int filter, StateListener listener) { + this.filter = filter; + this.listener = listener; + } + + @Override + public void accept(BitState source, int bits) { + final int filteredBits = filter & bits; + if(filteredBits != 0){ + listener.accept(source, filteredBits); + } + } + + final int filter; + final StateListener listener; + + } + + public Object getSource() { + return source; + } + + public BitState(Object source) { + this(source, NO_FILTER); + } + + public BitState(Object source, int filter) { + this.source = source; + this.filter = filter; + } + + @Override + public String toString() { + return "bits(" + String.valueOf(source) + ")"; + } + + int state; + final Object source; + final int filter; + public static final int NO_FILTER = ~0; + + List changeListeners; + List invalidateListeners = null; + +} diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java new file mode 100644 index 000000000..a8e5586a4 --- /dev/null +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -0,0 +1,27 @@ +package io.fair_acc.dataset.events; + +import java.util.function.IntSupplier; + +/** + * @author ennerf + */ +public enum ChartBits implements IntSupplier { + Layout, + Canvas, + AxisSide, + AxisRangeChanged; + + public int getBit() { + return bit; + } + + @Override + public int getAsInt() { + return getBit(); + } + + final int bit = 1 << ordinal(); + + public static final int ANY = BitState.mask(ChartBits.values()); + +} diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/StateListener.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/StateListener.java new file mode 100644 index 000000000..ddce67f1d --- /dev/null +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/StateListener.java @@ -0,0 +1,26 @@ +package io.fair_acc.dataset.events; + +/** + * @author ennerf + */ +@FunctionalInterface +public interface StateListener { + + /** + * @param source where the update came from + * @param bits changed or set bits + */ + void accept(BitState source, int bits); + + // Compatible with InvalidationListener, but no JavaFX dependency + @FunctionalInterface + public interface OnInvalidateSetter { + public void set(Object observable); + } + + @FunctionalInterface + public interface OnChangeSetter { + public void set(Object observable, Object oldValue, Object newValue); + } + +} From bbd6e386efb1c08b09131ac40fb793987419f91d Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 21 Jul 2023 16:51:57 +0200 Subject: [PATCH 03/81] initial refactoring to migrate axis drawing to the bit flag event system --- .../chartfx/axes/spi/AbstractAxis.java | 144 +++++--- .../axes/spi/AbstractAxisParameter.java | 339 ++++++------------ .../chartfx/axes/spi/DefaultNumericAxis.java | 68 ++-- .../io/fair_acc/chartfx/utils/FXUtils.java | 56 +++ .../io/fair_acc/dataset/events/BitState.java | 75 +++- .../io/fair_acc/dataset/events/ChartBits.java | 18 +- 6 files changed, 359 insertions(+), 341 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index eafbe025f..1d266878e 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -6,9 +6,9 @@ import io.fair_acc.chartfx.ui.css.PathStyle; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.utils.FXUtils; -import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.event.AxisNameChangeEvent; +import io.fair_acc.dataset.event.AxisRangeChangeEvent; import io.fair_acc.dataset.events.ChartBits; -import io.fair_acc.dataset.events.StateListener; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -83,6 +83,11 @@ protected void invalidated() { protected AbstractAxis() { super(); + // Can we remove these? Axes without a chart don't work anymore. + VBox.setVgrow(this, Priority.ALWAYS); + HBox.setHgrow(this, Priority.ALWAYS); + + // Canvas settings setMouseTransparent(false); setPickOnBounds(true); canvas.setMouseTransparent(false); @@ -93,7 +98,23 @@ protected AbstractAxis() { } getChildren().add(canvas); - dirty.addChangeListener(BitState.mask(ChartBits.Layout), (source, bits) -> { + // We can ignore the layout if labels can only move linearly along + // the axis length. This happens e.g. for an X-axis that displays + // moving time with the default policy. + state.addChangeListener(ChartBits.AxisTransform, (source, bits) -> { + if (!isTickMarkVisible() || !isTickLabelsVisible()) { + return; + } + final int rot = Math.abs(((int) getTickLabelRotation()) % 360); + if (getOverlapPolicy() == AxisLabelOverlapPolicy.SHIFT_ALT || getSide() == null + || (getSide().isHorizontal() && !(rot == 0 || rot == 180)) + || (getSide().isVertical() && !(rot == 90 || rot == 270))) { + state.setDirty(ChartBits.AxisLayout); + } + }); + + // Forward axis layouts to the JavaFX layout bit + state.addChangeListener(ChartBits.AxisLayout, (source, bits) -> { super.requestLayout(); }); @@ -104,17 +125,28 @@ protected AbstractAxis() { updateAxisLabelAlignment(); updateTickLabelAlignment(); }); - tickLabelRotationProperty().addListener((ch, o, n) -> updateTickLabelAlignment()); - - // TODO: remove? - invertAxisProperty().addListener((ch, o, n) -> Platform.runLater(this::forceRedraw)); + tickLabelRotationProperty().addListener((ch, o, n) -> { + updateTickLabelAlignment(); + }); - VBox.setVgrow(this, Priority.ALWAYS); - HBox.setHgrow(this, Priority.ALWAYS); - // Disconnect the length from the layout so we can make it user settable + // Disconnect the length from the layout to make it user settable lengthProperty().unbind(); - lengthProperty().addListener((ch, o, n) -> invalidate()); + + // Send out events to be backwards compatible with old event system + final AxisChangeEvent axisTransformEvent = new AxisRangeChangeEvent(this); + final AxisChangeEvent axisLabelEvent = new AxisNameChangeEvent(this); + final AxisChangeEvent otherChangeEvent = new AxisChangeEvent(this); + state.addChangeListener((source, bits) -> { + if (ChartBits.AxisTransform.isSet(bits)) { + invokeListener(axisTransformEvent, false); + } else if (ChartBits.AxisLabelText.isSet(bits)) { + invokeListener(axisLabelEvent, false); + } else { + invokeListener(otherChangeEvent, false); + } + }); + } protected AbstractAxis(final double lowerBound, final double upperBound) { @@ -142,7 +174,8 @@ public void drawAxis(final GraphicsContext gc, final double axisWidth, final dou return; } - updateAxisContents(); + // Always update transform and ticks so they can be used in the grid + updateContent(); // Nothing shown -> no need to draw anything if (!isVisible()) { @@ -199,7 +232,7 @@ public void fireInvalidated() { @Override public void forceRedraw() { - invalidate(); + layoutChangedAction.run(); } public AxisLabelFormatter getAxisLabelFormatter() { @@ -267,20 +300,7 @@ public void invalidateCaches() { */ @Override public void invalidateRange() { - final boolean oldState = autoNotification().getAndSet(false); - final AxisRange autoRange = autoRange(getLength()); // derived axes may potentially pad and round limits - if (set(autoRange.getMin(), autoRange.getMax())) { - getAutoRange().setAxisLength(getLength() == 0 ? 1 : getLength(), getSide()); - //setScale(getAutoRange().getScale()); - setScale(calculateNewScale(getLength(), autoRange.getMin(), autoRange.getMax())); - updateAxisLabelAndUnit(); - // update cache in derived classes - updateCachedVariables(); - invalidate(); - } - - autoNotification().set(oldState); - invokeListener(new AxisChangeEvent(this)); + axisTransformChanged.run(); } public boolean isLabelOverlapping() { @@ -300,36 +320,54 @@ public boolean isValueOnAxis(final double value) { } /** - * Updates the tick marks based on the current axis length - * TODO: based on old recomputeTickMarks(), some of it may not be necessary + * Updates the contents for this axis, e.g., tick labels, spacing + * range, caches, etc. */ - protected void updateAxisContents() { + protected void updateContent() { final double length = getLength(); - if (!Double.isFinite(length) || isValid()) { + if (!Double.isFinite(length) || state.isClean()) { return; } + isUpdatingContent = true; + + if(debug(String.format("min: %f, max: %f", getMin(), getMax()))) { + System.out.println(); + } + + // Update range & scale // TODO: some initialization broke when adding events + AxisRange range = autoRange(getLength()); // derived axes may potentially pad and round limits + range.setAxisLength(length == 0 ? 1 : length, getSide()); + set(range.getMin(), range.getMax()); + setScale(calculateNewScale(getLength(), range.getMin(), range.getMax())); - // Range - var newAxisRange = getRange(); - if (getRange().getMin() != newAxisRange.getMin() || getRange().getMax() != newAxisRange.getMax()) { - set(newAxisRange.getMin(), newAxisRange.getMax()); + // Displayed label & units + if (state.isDirty(ChartBits.AxisLabelText)) { + updateAxisLabelAndUnit(); // can this set dirty on the axis transform? } // Tick units double mTickUnit = getUserTickUnit(); - if (isAutoRanging() || isAutoGrowRanging() || true /* TODO: zoom never changes scale */){ + if (isAutoRanging() || isAutoGrowRanging() || true /* TODO: does not work in zoom */) { mTickUnit = computePreferredTickUnit(length); } - setTickUnit(newAxisRange.tickUnit = mTickUnit); + setTickUnit(range.tickUnit = mTickUnit); + + // Update cache to have axis transforms updateCachedVariables(); // Tick marks - updateMajorTickMarks(newAxisRange); + updateMajorTickMarks(range); updateMinorTickMarks(); updateTickMarkPositions(getTickMarks()); updateTickMarkPositions(getMinorTickMarks()); - dirty.clear(); - valid.set(true); + + postUpdateContent(); + + } + + protected void postUpdateContent() { + state.clear(); + isUpdatingContent = false; } /** @@ -354,7 +392,7 @@ public AxisRange getRange() { */ @Override public void requestAxisLayout() { - dirty.set(ChartBits.Layout); + state.setDirty(ChartBits.AxisLayout); } public void setAxisLabelFormatter(final AxisLabelFormatter value) { @@ -393,9 +431,9 @@ public boolean setMin(final double value) { * @return Range information, this is implementation dependent */ protected AxisRange autoRange(final double length) { - // guess a sensible starting size for label size, that is approx 2 lines vertically or 2 charts horizontally + // guess a sensible starting size for label size, that is approx 2 lines vertically or 2 chars horizontally if (isAutoRanging() || isAutoGrowRanging()) { - // guess a sensible starting size for label size, that is approx 2 lines vertically or 2 charts horizontally + // guess a sensible starting size for label size, that is approx 2 lines vertically or 2 chars horizontally final double labelSize = getTickLabelFont().getSize() * 1.2; // N.B. was '2' in earlier implementations return autoRange(getAutoRange().getMin(), getAutoRange().getMax(), length, labelSize); } @@ -496,7 +534,7 @@ private double computePrefSize(final double axisLength) { // correct, so later changes happen very rarely, e.g., at a point where // y axes labels switch to shifting lines. setLength(axisLength); - updateAxisContents(); + updateContent(); boolean isHorizontal = getSide().isHorizontal(); scaleFont = 1.0; @@ -682,26 +720,28 @@ List computeTickMarks(AxisRange range, boolean major) { } protected void updateMajorTickMarks(AxisRange range) { - // TODO: cache if the range and tick units have not changed? var newTickValues = calculateMajorTickValues(range); var oldTickValues = getTickMarkValues(); - if (newTickValues.equals(oldTickValues) || newTickValues.size() < 2) { + if(newTickValues.size() < 2) { + return; // TODO: why did the previous code only update when there are > 2 ticks? + }else if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickFormatter)) { return; // no need to redo labels, just reposition the ticks } - // TODO: why did the previous code only update when there are > 2 ticks? That would cause a mismatch - oldTickValues.setAll(newTickValues); - // Update labels - var formatter = getAxisLabelFormatter(); - formatter.updateFormatter(newTickValues, getUnitScaling()); + if (isTickLabelsVisible()) { + getAxisLabelFormatter().updateFormatter(newTickValues, getUnitScaling()); + } // Update the existing mark objects List marks = FXUtils.sizedList(getTickMarks(), newTickValues.size(), () -> new TickMark(getTickLabelStyle())); int i = 0; for (var tick : newTickValues) { - marks.get(i++).setValue(tick, getTickMarkLabel(tick)); + String label = isTickLabelsVisible() ? getTickMarkLabel(tick) : ""; + marks.get(i++).setValue(tick, label); } + + oldTickValues.setAll(newTickValues); tickMarksUpdated(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index ac5f7711c..3d4badb69 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -10,6 +10,7 @@ import io.fair_acc.chartfx.ui.css.StyleUtil; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.ui.layout.ChartPane; +import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; import javafx.beans.binding.Bindings; @@ -44,9 +45,12 @@ * @author rstein */ public abstract class AbstractAxisParameter extends Pane implements Axis { - protected final BitState dirty = new BitState(this); - protected final ChangeListener changeLayoutListener = dirty.onPropChange(ChartBits.Layout)::set; - protected final Runnable changeLayoutAction = dirty.onAction(ChartBits.Layout); + protected final BitState state = BitState.initDirty(this); + protected final Runnable layoutChangedAction = state.onAction(ChartBits.AxisLayout); + protected final Runnable axisTransformChanged = state.onAction(ChartBits.AxisTransform); + protected final Runnable axisNameChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisLabelText); + protected final ChangeListener layoutChangedListener = state.onPropChange(ChartBits.AxisLayout)::set; + protected final ChangeListener axisTransformChangedListener = state.onPropChange(ChartBits.AxisTransform)::set; private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Region.getClassCssMetaData()); @@ -62,7 +66,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); - private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, changeLayoutAction); + private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, layoutChangedAction); /** * Nodes used for css-type styling. Not used for actual drawing. Used as a storage container for the settings @@ -78,20 +82,13 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { { StyleUtil.addStyles(this, "axis"); getChildren().addAll(axisLabel, tickLabelStyle, majorTickStyle, minorTickStyle); - majorTickStyle.changeCounterProperty().addListener(changeLayoutListener); - minorTickStyle.changeCounterProperty().addListener(changeLayoutListener); - tickLabelStyle.changeCounterProperty().addListener(changeLayoutListener); - axisLabel.changeCounterProperty().addListener(changeLayoutListener); + majorTickStyle.changeCounterProperty().addListener(layoutChangedListener); + minorTickStyle.changeCounterProperty().addListener(state.onPropChange(ChartBits.AxisCanvas)::set); + tickLabelStyle.changeCounterProperty().addListener(layoutChangedListener); + axisLabel.changeCounterProperty().addListener(layoutChangedListener); } - protected final transient BooleanProperty valid = new SimpleBooleanProperty(this, "valid", false); - { - valid.addListener((observable, oldValue, newValue) -> { - if (!newValue) { - requestLayout(); - } - }); - } + // TODO: replace with primitive DoubleArrayList and specialized TickMarkList protected final transient ObservableList majorTickMarkValues = FXCollections.observableArrayList(new NoDuplicatesList<>()); protected final transient ObservableList minorTickMarkValues = FXCollections.observableArrayList(new NoDuplicatesList<>()); protected final transient ObservableList majorTickMarks = FXCollections.observableArrayList(); @@ -113,62 +110,58 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { */ private final transient StyleableObjectProperty side = CSS.createEnumPropertyWithPseudoclasses(this, "side", Side.BOTTOM, false, Side.class, null, () -> { ChartPane.setSide(this, getSide()); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); + state.setDirty(ChartBits.AxisLayout); }); /** * The side of the plot which this axis is being drawn on */ - private final transient StyleableObjectProperty overlapPolicy = CSS.createObjectProperty(this, "overlapPolicy", AxisLabelOverlapPolicy.SKIP_ALT, StyleConverter.getEnumConverter(AxisLabelOverlapPolicy.class), () -> invokeListener(new AxisChangeEvent(AbstractAxisParameter.this))); + private final transient StyleableObjectProperty overlapPolicy = CSS.createObjectProperty(this, "overlapPolicy", AxisLabelOverlapPolicy.SKIP_ALT, StyleConverter.getEnumConverter(AxisLabelOverlapPolicy.class), layoutChangedAction); /** * The relative alignment (N.B. clamped to [0.0,1.0]) of the axis if drawn on top of the main canvas (N.B. side == CENTER_HOR or CENTER_VER */ - private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), changeLayoutAction); + private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), layoutChangedAction); /** * axis label alignment */ - private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), changeLayoutAction); + private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), layoutChangedAction); /** * The axis label */ - private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", changeLayoutAction); + private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", axisNameChangedAction); /** * true if tick marks should be displayed */ - private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, changeLayoutAction); + private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, layoutChangedAction); /** * true if tick mark labels should be displayed */ - private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true, () -> { - getTickMarks().forEach(tick -> tick.setVisible(AbstractAxisParameter.this.tickLabelsVisible.get())); - invalidate(); - invokeListener(new AxisChangeEvent(this)); - }); + private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true, layoutChangedAction); /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, changeLayoutAction); + private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, layoutChangedAction); /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, changeLayoutAction); + private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, layoutChangedAction); /** * This is true when the axis determines its range from the data automatically */ - private final transient StyleableBooleanProperty autoRanging = CSS.createBooleanProperty(this, "autoRanging", true, changeLayoutAction); + private final transient StyleableBooleanProperty autoRanging = CSS.createBooleanProperty(this, "autoRanging", true, axisTransformChanged); /** * The font for all tick labels */ - private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, changeLayoutAction); + private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, layoutChangedAction); /** * The fill for all tick labels @@ -178,32 +171,32 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The gap between tick marks and the canvas area */ - private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0, changeLayoutAction); + private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0, layoutChangedAction); /** * The gap between tick labels and the tick mark lines */ - private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, changeLayoutAction); + private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, layoutChangedAction); /** * The minimum gap between tick labels */ - private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, changeLayoutAction); + private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, axisTransformChanged); /** * The gap between tick labels and the axis label */ - private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, changeLayoutAction); + private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, layoutChangedAction); /** * The animation duration in MS */ - private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, changeLayoutAction); + private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, layoutChangedAction); /** * The maximum number of ticks */ - private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, changeLayoutAction); + private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, axisTransformChanged); /** * When true any changes to the axis and its range will be animated. @@ -213,146 +206,89 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * Rotation in degrees of tick mark labels from their normal horizontal. */ - protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, changeLayoutAction); + protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, layoutChangedAction); /** * true if minor tick marks should be displayed */ - private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, changeLayoutAction); + private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, axisTransformChanged); /** * The scale factor from data units to visual units */ - private final transient ReadOnlyDoubleWrapper scale = new ReadOnlyDoubleWrapper(this, "scale", 1); + private final transient ReadOnlyDoubleWrapper scale = FXUtils.createReadOnlyDoubleWrapper(this, "scale", 1, axisTransformChanged); /** * The axis length in pixels */ - private final transient DoubleProperty length = new SimpleDoubleProperty(Double.NaN); + private final transient DoubleProperty length = FXUtils.createDoubleProperty(this, "length", Double.NaN, axisTransformChanged) ; /** * The value for the upper bound of this axis, ie max value. This is automatically set if auto ranging is on. */ - protected final transient DoubleProperty maxProp = new SimpleDoubleProperty(this, "upperBound", DEFAULT_MAX_RANGE) { - @Override - public void set(final double newValue) { - final double oldValue = get(); - if (oldValue != newValue) { - super.set(newValue); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); - } - } - }; + protected final transient DoubleProperty maxProp = FXUtils.createDoubleProperty(this, "upperBound", DEFAULT_MAX_RANGE, axisTransformChanged); /** * The value for the lower bound of this axis, ie min value. This is automatically set if auto ranging is on. */ - protected final transient DoubleProperty minProp = new SimpleDoubleProperty(this, "lowerBound", DEFAULT_MIN_RANGE) { - @Override - public void set(final double newValue) { - final double oldValue = get(); - if (oldValue != newValue) { - super.set(newValue); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); - } - } - }; + protected final transient DoubleProperty minProp = FXUtils.createDoubleProperty(this, "lowerBound", DEFAULT_MIN_RANGE, axisTransformChanged); protected double cachedOffset; // for caching /** * StringConverter used to format tick mark labels. If null a default will be used */ - private final transient ObjectProperty> tickLabelFormatter = new ObjectPropertyBase<>(null) { - @Override - public Object getBean() { - return AbstractAxisParameter.this; - } - - @Override - public String getName() { - return "tickLabelFormatter"; - } - - @Override - protected void invalidated() { - invalidate(); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); - } - }; + private final transient ObjectProperty> tickLabelFormatter = FXUtils.createObjectProperty(this, "tickLabelFormatter", null, state.onAction(ChartBits.AxisTransform, ChartBits.AxisTickFormatter)); /** * The length of minor tick mark lines. Set to 0 to not display minor tick marks. */ - private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, changeLayoutAction); + private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, layoutChangedAction); /** * The number of minor tick divisions to be displayed between each major tick mark. The number of actual minor tick * marks will be one less than this. N.B. number of divisions, minor tick mark is not drawn if minorTickMark == * majorTickMark */ - private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, changeLayoutAction); + private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, layoutChangedAction); - private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, changeLayoutAction); + private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, layoutChangedAction); protected boolean isInvertedAxis = false; // internal use (for performance reason) - private final transient BooleanProperty invertAxis = new SimpleBooleanProperty(this, "invertAxis", false) { - @Override - protected void invalidated() { - isInvertedAxis = get(); - invalidate(); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); - } - }; + private final transient BooleanProperty invertAxis = FXUtils.createBooleanProperty(this, "invertAxis", isInvertedAxis, ()->{ + isInvertedAxis = invertAxisProperty().get(); + layoutChangedAction.run(); + }); protected boolean isTimeAxis = false; // internal use (for performance reasons) - private final transient BooleanProperty timeAxis = new SimpleBooleanProperty(this, "timeAxis", false) { - @Override - protected void invalidated() { - isTimeAxis = get(); - if (isTimeAxis) { - setMinorTickCount(0); - } else { - setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); - } - invalidate(); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); + private final transient BooleanProperty timeAxis = FXUtils.createBooleanProperty(this, "timeAxis", isTimeAxis, ()->{ + isTimeAxis = timeAxisProperty().get(); + if (isTimeAxis) { + setMinorTickCount(0); + } else { + setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); } - }; + layoutChangedAction.run(); + }); - private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, changeLayoutAction); + private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, axisTransformChanged); - private final transient DoubleProperty autoRangePadding = new SimpleDoubleProperty(0); + private final transient DoubleProperty autoRangePadding = FXUtils.createDoubleProperty(this, "autoRangePadding", 0, axisTransformChanged); /** * The axis unit label */ - private final transient StyleableStringProperty axisUnit = CSS.createStringProperty(this, "axisUnit", "", () -> { - updateAxisLabelAndUnit(); - requestAxisLayout(); - }); + private final transient StyleableStringProperty axisUnit = CSS.createStringProperty(this, "axisUnit", "", axisNameChangedAction); /** * The axis unit label */ - private final transient BooleanProperty autoUnitScaling = new SimpleBooleanProperty(this, "autoUnitScaling", false) { - @Override - protected void invalidated() { - updateAxisLabelAndUnit(); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); - } - }; + private final transient BooleanProperty autoUnitScaling = FXUtils.createBooleanProperty(this, "autoUnitScaling", false, axisNameChangedAction); /** * The axis unit label */ - private final transient DoubleProperty unitScaling = new SimpleDoubleProperty(this, "unitScaling", 1.0) { - @Override - protected void invalidated() { - updateAxisLabelAndUnit(); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); - } - }; + private final transient DoubleProperty unitScaling = FXUtils.createDoubleProperty(this, "unitScaling", 1.0, axisNameChangedAction); /** * user-set tick units when doing auto-ranging @@ -361,59 +297,47 @@ protected void invalidated() { if (isAutoRanging() || isAutoGrowRanging()) { return; } - invalidate(); - invokeListener(new AxisChangeEvent(AbstractAxisParameter.this)); + axisNameChangedAction.run(); }); /** * system-set tick units for getting the currently displayed units */ - protected final transient DoubleProperty tickUnit = new SimpleDoubleProperty(Double.NaN); + protected final transient DoubleProperty tickUnit = FXUtils.createDoubleProperty(this, "tickUnit", Double.NaN, axisTransformChanged); - protected final transient ChangeListener scaleChangeListener = (ch, o, n) -> { - final double axisLength = getLength(); // [pixel] - final double lowerBound = getMin(); - final double upperBound = getMax(); - if (!Double.isFinite(axisLength) || !Double.isFinite(lowerBound) || !Double.isFinite(upperBound)) { - return; - } - - double newScale; - final double diff = upperBound - lowerBound; - if (getSide().isVertical()) { - newScale = diff == 0 ? -axisLength : -(axisLength / diff); - cachedOffset = axisLength; - } else { // HORIZONTAL - newScale = diff == 0 ? axisLength : axisLength / diff; - cachedOffset = 0; - } - - setScale(newScale == 0 ? -1.0 : newScale); - }; + protected boolean isUpdatingContent = false; /** * Create a auto-ranging AbstractAxisParameter */ public AbstractAxisParameter() { super(); - autoRangingProperty().addListener(ch -> { + autoRangingProperty().addListener((ch, o, enabled) -> { // disable auto grow if auto range is enabled - if (isAutoRanging()) { + if (enabled) { setAutoGrowRanging(false); } }); - autoGrowRangingProperty().addListener(ch -> { + autoGrowRangingProperty().addListener((ch, o, enabled) -> { // disable auto grow if auto range is enabled - if (isAutoGrowRanging()) { + if (enabled) { setAutoRanging(false); } }); - nameProperty().addListener(e -> { - updateAxisLabelAndUnit(); - invokeListener(new AxisChangeEvent(this)); - }); + // bind limits to user-specified axis range + // userRange.set + final ChangeListener userLimitChangeListener = (ch, o, n) -> { + // Disable auto ranging if range was set manually + if (!isUpdatingContent) { + getUserRange().set(getMin(), getMax()); + setAutoRanging(false); + setAutoGrowRanging(false); + } + }; + minProperty().addListener(userLimitChangeListener); + maxProperty().addListener(userLimitChangeListener); // TODO: remove and use styles directly? tickLabelStyle.rotateProperty().bindBidirectional(tickLabelRotation); @@ -421,7 +345,7 @@ public AbstractAxisParameter() { tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill); axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD - // Provide a binding for the axis length + // Provide an initial binding for the axis length var layoutLength = Bindings.createDoubleBinding(() -> { if (getSide() == null) { return Double.NaN; @@ -430,26 +354,6 @@ public AbstractAxisParameter() { }, sideProperty(), widthProperty(), heightProperty()); length.bind(layoutLength); - // bind limits to user-specified axis range - // userRange.set - final ChangeListener userLimitChangeListener = (ch, o, n) -> { - getUserRange().set(getMin(), getMax()); - // axis range has been set manually -> disable auto-ranging - // TODO: enable once the new scheme checks out - // setAutoRanging(false) - // setAutoGrowRanging(false) - - if (!isAutoRanging() && !isAutoGrowRanging()) { - invokeListener(new AxisChangeEvent(this)); - } - invalidate(); - }; - minProperty().addListener(userLimitChangeListener); - maxProperty().addListener(userLimitChangeListener); - - minProperty().addListener(scaleChangeListener); - maxProperty().addListener(scaleChangeListener); - lengthProperty().addListener(scaleChangeListener); } @Override @@ -823,15 +727,6 @@ public AxisRange getUserRange() { return userRange; } - /** - * Mark the current axis invalid, this will cause anything that depends on the axis range or physical size to be - * recalculated on the next - * layout iteration. - */ - public void invalidate() { - validProperty().set(false); - } - /** * This is {@code true} when the axis labels and data point order should be inverted * @@ -949,7 +844,7 @@ public boolean isTimeAxis() { * @return true if current axis range and physical size calculations are valid */ public boolean isValid() { - return validProperty().get(); + return state.isClean(); } public IntegerProperty maxMajorTickLabelCountProperty() { @@ -993,16 +888,14 @@ public ReadOnlyDoubleProperty scaleProperty() { @Override public boolean set(final double min, final double max) { - final double oldMin = minProp.get(); - final double oldMax = maxProp.get(); - final boolean oldState = autoNotification().getAndSet(false); - minProp.set(min); - maxProp.set(max); - autoNotification().set(oldState); - final boolean changed = (oldMin != min) || (oldMax != max); - if (changed) { - invalidate(); - invokeListener(new AxisChangeEvent(this)); + boolean changed = false; + if (min != minProp.get()) { + minProp.set(min); + changed = true; + } + if (max != maxProp.get()) { + maxProp.set(max); + changed = true; } return changed; } @@ -1010,7 +903,6 @@ public boolean set(final double min, final double max) { @Override public boolean set(final String axisName, final String... axisUnit) { boolean changed = false; - final boolean oldState = autoNotification().getAndSet(false); if (!equalString(axisName, getName())) { setName(axisName); changed = true; @@ -1019,23 +911,12 @@ public boolean set(final String axisName, final String... axisUnit) { setUnit(axisUnit[0]); changed = true; } - autoNotification().set(oldState); - if (changed) { - invokeListener(new AxisChangeEvent(this)); - } return changed; } @Override public boolean set(final String axisName, final String axisUnit, final double rangeMin, final double rangeMax) { - final boolean oldState = autoNotification().getAndSet(false); - boolean changed = this.set(axisName, axisUnit); - changed |= this.set(rangeMin, rangeMax); - autoNotification().set(oldState); - if (changed) { - invokeListener(new AxisChangeEvent(this)); - } - return changed; + return set(axisName, axisUnit) | set(rangeMin, rangeMax); // note: single '|' to avoid skipping right part } /** @@ -1062,13 +943,7 @@ public void setAnimationDuration(final int value) { */ @Override public void setAutoGrowRanging(final boolean state) { - // TODO: setter should probably be dumb and state changes should happen in property's invalidate - if (state) { - setAutoRanging(false); - invalidate(); - requestAxisLayout(); - } - autoGrowRangingProperty().set(state); + autoRangingProperty().set(state); } /** @@ -1331,6 +1206,26 @@ protected void setScale(final double scale) { scalePropertyImpl().set(scale); } + // TODO: remove? + protected void updateScale() { + final double length = getLength(); // [pixel] + final double range = getMax() - getMin(); + if (!Double.isFinite(range)) { + return; + } + + double newScale; + if (getSide().isVertical()) { + newScale = range == 0 ? -length : -(length / range); + cachedOffset = length; + } else { // HORIZONTAL + newScale = range == 0 ? length : length / range; + cachedOffset = 0; + } + + setScale(newScale == 0 ? -1.0 : newScale); + } + protected void updateAxisLabelAndUnit() { final String axisPrimaryLabel = getName(); String localAxisUnit = getUnit(); @@ -1349,7 +1244,6 @@ protected void updateAxisLabelAndUnit() { } else { getAxisLabel().setText(axisPrimaryLabel + " [" + axisPrefix + localAxisUnit + "]"); } - invalidate(); // listeners already trigger request a layout } protected void updateScaleAndUnitPrefix() { @@ -1366,17 +1260,6 @@ protected void updateScaleAndUnitPrefix() { setTickUnit(range / getMinorTickCount()); } - /** - * valid flag property. - * This will cause anything that depends on the axis range or physical size to be recalculated on the next layout - * iteration. - * - * @return the validProperty() - */ - protected BooleanProperty validProperty() { - return valid; - } - ReadOnlyDoubleWrapper scalePropertyImpl() { return scale; } @@ -1391,4 +1274,10 @@ ReadOnlyDoubleWrapper scalePropertyImpl() { protected static boolean equalString(final String str1, final String str2) { return Objects.equals(str1, str2); } + + @Deprecated + protected void invalidate() { + requestAxisLayout(); + } + } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index 6410f534b..67c46024c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; +import io.fair_acc.chartfx.utils.FXUtils; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -47,54 +48,36 @@ public class DefaultNumericAxis extends AbstractAxis implements Axis { private transient AxisTransform axisTransform = linearTransform; protected boolean isUpdating; - private final transient BooleanProperty forceZeroInRange = new SimpleBooleanProperty(this, "forceZeroInRange", false) { - @Override - protected void invalidated() { - if (isAutoRanging() || isAutoGrowRanging()) { - invalidate(); - requestAxisLayout(); - } - } - }; + private final transient BooleanProperty forceZeroInRange = FXUtils.createBooleanProperty(this, "forceZeroInRange", false, axisTransformChanged); protected boolean isLogAxis = false; // internal use (for performance reason - private final transient BooleanProperty logAxis = new SimpleBooleanProperty(this, "logAxis", isLogAxis) { - @Override - protected void invalidated() { - isLogAxis = get(); - - if (isLogAxis) { - if (DefaultNumericAxis.this.isTimeAxis()) { - axisTransform = logTimeTransform; - setMinorTickCount(0); - } else { - axisTransform = logTransform; - setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); - } - if (getMin() <= 0) { - isUpdating = true; - setMin(DefaultNumericAxis.DEFAULT_LOG_MIN_VALUE); - isUpdating = false; - } - - invalidate(); - requestLayout(); + private final transient BooleanProperty logAxis = FXUtils.createBooleanProperty(this, "logAxis", isLogAxis, () -> { + isLogAxis = isLogAxis(); + if (isLogAxis) { + if (DefaultNumericAxis.this.isTimeAxis()) { + axisTransform = logTimeTransform; + setMinorTickCount(0); } else { - axisTransform = linearTransform; - if (DefaultNumericAxis.this.isTimeAxis()) { - setMinorTickCount(0); - } else { - setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); - } + axisTransform = logTransform; + setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); } - - if (isAutoRanging() || isAutoGrowRanging()) { - invalidate(); + if (getMin() <= 0) { + isUpdating = true; + setMin(DefaultNumericAxis.DEFAULT_LOG_MIN_VALUE); + isUpdating = false; + } + } else { + axisTransform = linearTransform; + if (DefaultNumericAxis.this.isTimeAxis()) { + setMinorTickCount(0); + } else { + setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); } - requestAxisLayout(); } - }; + + axisTransformChanged.run(); + }); /** * Creates an {@link #autoRangingProperty() auto-ranging} Axis. @@ -342,8 +325,7 @@ public void setForceZeroInRange(final boolean value) { */ public void setLogarithmBase(final double value) { logarithmBaseProperty().set(value); - invalidate(); - requestAxisLayout(); + axisTransformChanged.run(); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java index 3bcaaa39a..41b3471c1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java @@ -12,7 +12,10 @@ import java.util.function.Function; import java.util.function.Supplier; +import io.fair_acc.chartfx.axes.spi.AbstractAxisParameter; +import io.fair_acc.dataset.events.ChartBits; import javafx.application.Platform; +import javafx.beans.property.*; import javafx.scene.Node; import javafx.scene.Scene; @@ -282,4 +285,57 @@ public static List sizedList(List list, int desiredSize return list; } + public static BooleanProperty createBooleanProperty(Object bean, String name, boolean initial, Runnable onChange) { + return new SimpleBooleanProperty(bean, name, initial) { + @Override + public void set(final boolean newValue) { + final boolean oldValue = get(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } + + public static DoubleProperty createDoubleProperty(Object bean, String name, double initial, Runnable onChange) { + return new SimpleDoubleProperty(bean, name, initial) { + @Override + public void set(final double newValue) { + final double oldValue = get(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } + + public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial, Runnable onChange) { + return new ReadOnlyDoubleWrapper(bean, name, initial) { + @Override + public void set(final double newValue) { + final double oldValue = get(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } + + public static ObjectProperty createObjectProperty(Object bean, String name, T initial, Runnable onChange) { + return new SimpleObjectProperty(bean, name, initial) { + @Override + public void set(final T newValue) { + final T oldValue = getValue(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } + + } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 368e33487..4d3a9c4c7 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -14,7 +14,7 @@ public Runnable onAction(IntSupplier bit0, IntSupplier... more) { } public Runnable onAction(int bits) { - return () -> set(bits); + return () -> setDirty(bits); } public OnChangeSetter onPropChange(IntSupplier bit0, IntSupplier... more) { @@ -22,7 +22,7 @@ public OnChangeSetter onPropChange(IntSupplier bit0, IntSupplier... more) { } public OnChangeSetter onPropChange(int bits) { - return (obs, o, v) -> set(bits); + return (obs, o, v) -> setDirty(bits); } public OnInvalidateSetter onPropInvalidate(IntSupplier bit0, IntSupplier... more) { @@ -30,7 +30,7 @@ public OnInvalidateSetter onPropInvalidate(IntSupplier bit0, IntSupplier... more } public OnInvalidateSetter onPropInvalidate(int bits) { - return obs -> set(bits); + return obs -> setDirty(bits); } public static int mask(IntSupplier[] bits){ @@ -49,7 +49,7 @@ public static int mask(IntSupplier bit0, IntSupplier... more) { return mask; } - public void set(int bits) { + public void setDirty(int bits) { final int filtered = bits & filter; final int delta = (state ^ filtered) & filtered; if (delta != 0) { @@ -61,11 +61,11 @@ public void set(int bits) { @Override public void accept(BitState source, int bits) { - set(bits); + setDirty(bits); } - public void set(IntSupplier bit0, IntSupplier... bits) { - set(mask(bit0, bits)); + public void setDirty(IntSupplier bit0, IntSupplier... bits) { + setDirty(mask(bit0, bits)); } public boolean isDirty() { @@ -73,11 +73,43 @@ public boolean isDirty() { } public boolean isDirty(int mask) { - return (state & mask) == 0; + return (state & mask) != 0; } - public boolean isDirty(IntSupplier bit0, IntSupplier... bits) { - return isDirty(mask(bit0, bits)); + public boolean isDirty(IntSupplier bit0) { + return isDirty(bit0.getAsInt()); + } + + public boolean isDirty(IntSupplier bit0, IntSupplier bit1) { + return isDirty(bit0.getAsInt() | bit1.getAsInt()); + } + + public boolean isDirty(IntSupplier bit0, IntSupplier bit1, IntSupplier... bits) { + return isDirty(bit0.getAsInt() | bit1.getAsInt() | mask(bits)); + } + + public boolean isClean() { + return state == 0; + } + + public boolean isClean(int mask) { + return (state & mask) != 0; + } + + public boolean isClean(IntSupplier bit0) { + return isClean(bit0.getAsInt()); + } + + public boolean isClean(IntSupplier bit0, IntSupplier bit1) { + return isClean(bit0.getAsInt() | bit1.getAsInt()); + } + + public boolean isClean(IntSupplier bit0, IntSupplier bit1, IntSupplier... bits) { + return isClean(bit0.getAsInt() | bit1.getAsInt() | mask(bits)); + } + + public int getBits() { + return state; } public void clear() { @@ -89,6 +121,10 @@ public int clear(final int mask) { return state; } + public BitState addChangeListener(IntSupplier bit, StateListener listener) { + return addChangeListener(bit.getAsInt(), listener); + } + public BitState addChangeListener(int filter, StateListener listener) { return addChangeListener(new FilteredListener(filter, listener)); } @@ -180,13 +216,26 @@ public Object getSource() { return source; } - public BitState(Object source) { - this(source, NO_FILTER); + public static BitState initClean(Object source) { + return initClean(source, NO_FILTER); + } + + public static BitState initDirty(Object source) { + return initDirty(source, NO_FILTER); + } + + public static BitState initClean(Object source, int filter) { + return new BitState(source, filter, 0); + } + + public static BitState initDirty(Object source, int filter) { + return new BitState(source, filter, filter); } - public BitState(Object source, int filter) { + protected BitState(Object source, int filter, int initial) { this.source = source; this.filter = filter; + this.state = initial; } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index a8e5586a4..a919fa9dd 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -6,18 +6,20 @@ * @author ennerf */ public enum ChartBits implements IntSupplier { - Layout, - Canvas, - AxisSide, - AxisRangeChanged; + AxisCanvas, + AxisLayout, + AxisTransform, + AxisTickLocation, + AxisTickFormatter, + AxisLabelText; - public int getBit() { + @Override + public int getAsInt() { return bit; } - @Override - public int getAsInt() { - return getBit(); + public boolean isSet(int mask) { + return (bit & mask) != 0; } final int bit = 1 << ordinal(); From cc91218140d15741608608c96dfa8c53997a2dcd Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 21 Jul 2023 21:49:56 +0200 Subject: [PATCH 04/81] changed axis properties to be for display only - computations are done on raw values --- .../chartfx/axes/spi/AbstractAxis.java | 91 +++---- .../axes/spi/AbstractAxisParameter.java | 245 +++++++++--------- .../chartfx/axes/spi/DefaultNumericAxis.java | 12 + .../io/fair_acc/chartfx/utils/FXUtils.java | 4 + .../io/fair_acc/dataset/events/BitState.java | 16 ++ 5 files changed, 200 insertions(+), 168 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 1d266878e..9eb97fa61 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -129,10 +129,6 @@ protected AbstractAxis() { updateTickLabelAlignment(); }); - - // Disconnect the length from the layout to make it user settable - lengthProperty().unbind(); - // Send out events to be backwards compatible with old event system final AxisChangeEvent axisTransformEvent = new AxisRangeChangeEvent(this); final AxisChangeEvent axisLabelEvent = new AxisNameChangeEvent(this); @@ -168,23 +164,20 @@ public ObjectProperty axisLabelFormatterProperty() { */ public abstract double computePreferredTickUnit(final double axisLength); + public double computePreferredTickUnit(AxisRange range) { + // TODO: remove the older general one + return computePreferredTickUnit(range.axisLength); + } + @Override public void drawAxis(final GraphicsContext gc, final double axisWidth, final double axisHeight) { if ((gc == null) || (getSide() == null)) { return; } - // Always update transform and ticks so they can be used in the grid - updateContent(); - - // Nothing shown -> no need to draw anything - if (!isVisible()) { - return; - } - drawAxisPre(); - final double axisLength = getSide().isHorizontal() ? axisWidth : axisHeight; + final double axisLength = getLength(); if (isTickMarkVisible()) { final var majorTicks = getTickMarks(); final var minorTicks = getMinorTickMarks(); @@ -210,6 +203,7 @@ public void drawAxis(final GraphicsContext gc, final double axisWidth, final dou drawAxisLabel(gc, axisWidth, axisHeight, getAxisLabel(), getTickLength()); drawAxisLine(gc, axisLength, axisWidth, axisHeight); drawAxisPost(); + } /** @@ -324,35 +318,34 @@ public boolean isValueOnAxis(final double value) { * range, caches, etc. */ protected void updateContent() { - final double length = getLength(); + final double length = Math.max(1, getLength()); if (!Double.isFinite(length) || state.isClean()) { return; } - isUpdatingContent = true; - if(debug(String.format("min: %f, max: %f", getMin(), getMax()))) { - System.out.println(); + // Update range & scale + final AxisRange range; + if (isAutoGrowRanging() || isAutoRanging()) { + range = autoRange(length); // derived axes may potentially pad and round limits + } else { + range = getUserRange(); } - // Update range & scale // TODO: some initialization broke when adding events - AxisRange range = autoRange(getLength()); // derived axes may potentially pad and round limits - range.setAxisLength(length == 0 ? 1 : length, getSide()); - set(range.getMin(), range.getMax()); - setScale(calculateNewScale(getLength(), range.getMin(), range.getMax())); + range.axisLength = length; + range.scale = calculateNewScale(length, range.getMin(), range.getMax()); + range.tickUnit =/* (!isAutoRanging() && !isAutoRanging()) ? getUserTickUnit() :*/ computePreferredTickUnit(range); // TODO: does not work in zoom + + // Scale the units // TODO: actually scale + double unitScale = computeUnitScale(range); + setUnitScaling(unitScale); // Displayed label & units if (state.isDirty(ChartBits.AxisLabelText)) { - updateAxisLabelAndUnit(); // can this set dirty on the axis transform? - } - - // Tick units - double mTickUnit = getUserTickUnit(); - if (isAutoRanging() || isAutoGrowRanging() || true /* TODO: does not work in zoom */) { - mTickUnit = computePreferredTickUnit(length); + updateAxisLabel(); } - setTickUnit(range.tickUnit = mTickUnit); // Update cache to have axis transforms + setDisplayedRange(range); updateCachedVariables(); // Tick marks @@ -361,13 +354,6 @@ protected void updateContent() { updateTickMarkPositions(getTickMarks()); updateTickMarkPositions(getMinorTickMarks()); - postUpdateContent(); - - } - - protected void postUpdateContent() { - state.clear(); - isUpdatingContent = false; } /** @@ -467,25 +453,18 @@ protected AxisRange autoRange(final double length) { * @return new scale to fit the range from lower bound to upper bound in the given display length */ protected double calculateNewScale(final double length, final double lowerBound, final double upperBound) { - double newScale; - final var side = getSide(); - final double diff = upperBound - lowerBound; - if (side.isVertical()) { - newScale = diff == 0 ? -length : -(length / diff); - } else { // HORIZONTAL - newScale = (upperBound - lowerBound) == 0 ? length : length / diff; + final double range = upperBound - lowerBound; + if(range == 0 || length == 0) { + return -1.0; } - return newScale == 0 ? -1.0 : newScale; + final double scale = length / range; + return isInvertedAxis ? -scale : scale; } protected void clearAxisCanvas(final GraphicsContext gc, final double width, final double height) { gc.clearRect(0, 0, width, height); } - private void setLength(double length) { - lengthProperty().set(length); - } - /** * Computes the preferred height of this axis for the given width. If axis orientation is horizontal, it takes into * account the tick mark length, tick label gap and label height. @@ -963,13 +942,20 @@ protected void layoutChildren() { // to guarantee ordering (e.g. ticks are available before the grid) canvas.resizeRelocate(-canvasPadX, -canvasPadY, getWidth() + 2 * canvasPadX, getHeight() + 2 * canvasPadY); - // Layout only gets called on actual size changes. Most of the time the length set - // during the prefSize phase is already correct, so this rarely triggers a recompute. - setLength(super.getLength()); + // Layout only gets called on actual size changes. The length already gets set during + // the prefSize phase, so this is more of a sanity check. + setLength(getSide().isHorizontal() ? getWidth() : getHeight()); } @Override public void drawAxis() { + if (state.isClean()) { + return; + } + + // update labels, tick marks etc. + updateContent(); + // clear outdated canvas content final var gc = canvas.getGraphicsContext2D(); clearAxisCanvas(gc, canvas.getWidth(), canvas.getHeight()); @@ -981,6 +967,7 @@ public void drawAxis() { } finally { gc.translate(-canvasPadX, -canvasPadY); } + state.clear(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 3d4badb69..0442e2bbc 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -13,7 +13,6 @@ import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; -import javafx.beans.binding.Bindings; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; @@ -156,7 +155,19 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * This is true when the axis determines its range from the data automatically */ - private final transient StyleableBooleanProperty autoRanging = CSS.createBooleanProperty(this, "autoRanging", true, axisTransformChanged); + private final transient StyleableBooleanProperty autoRanging = CSS.createBooleanProperty(this, "autoRanging", true, () -> { + if (isAutoRanging()) { + setAutoGrowRanging(false); + } + axisTransformChanged.run(); + }); + + private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, () -> { + if (isAutoGrowRanging()) { + setAutoRanging(false); + } + axisTransformChanged.run(); + }); /** * The font for all tick labels @@ -216,22 +227,42 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The scale factor from data units to visual units */ - private final transient ReadOnlyDoubleWrapper scale = FXUtils.createReadOnlyDoubleWrapper(this, "scale", 1, axisTransformChanged); + private final transient ReadOnlyDoubleWrapper scale = FXUtils.createReadOnlyDoubleWrapper(this, "scale", 1); /** * The axis length in pixels */ - private final transient DoubleProperty length = FXUtils.createDoubleProperty(this, "length", Double.NaN, axisTransformChanged) ; + private final transient ReadOnlyDoubleWrapper length = FXUtils.createReadOnlyDoubleWrapper(this, "length", Double.NaN, axisTransformChanged) ; + + private boolean settingDisplayRange = false; /** * The value for the upper bound of this axis, ie max value. This is automatically set if auto ranging is on. */ - protected final transient DoubleProperty maxProp = FXUtils.createDoubleProperty(this, "upperBound", DEFAULT_MAX_RANGE, axisTransformChanged); + protected final transient ReadOnlyDoubleWrapper maxProp = new ReadOnlyDoubleWrapper(this, "upperBound", DEFAULT_MAX_RANGE) { + @Override + public void set(final double newValue) { + if (settingDisplayRange) { + super.set(newValue); + } else { + setMax(newValue); + } + } + }; /** * The value for the lower bound of this axis, ie min value. This is automatically set if auto ranging is on. */ - protected final transient DoubleProperty minProp = FXUtils.createDoubleProperty(this, "lowerBound", DEFAULT_MIN_RANGE, axisTransformChanged); + protected final transient ReadOnlyDoubleWrapper minProp = new ReadOnlyDoubleWrapper(this, "lowerBound", DEFAULT_MIN_RANGE) { + @Override + public void set(final double newValue) { + if (settingDisplayRange) { + super.set(newValue); + } else { + setMin(newValue); + } + } + }; protected double cachedOffset; // for caching @@ -252,8 +283,6 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { */ private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, layoutChangedAction); - private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, layoutChangedAction); - protected boolean isInvertedAxis = false; // internal use (for performance reason) private final transient BooleanProperty invertAxis = FXUtils.createBooleanProperty(this, "invertAxis", isInvertedAxis, ()->{ isInvertedAxis = invertAxisProperty().get(); @@ -303,57 +332,18 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * system-set tick units for getting the currently displayed units */ - protected final transient DoubleProperty tickUnit = FXUtils.createDoubleProperty(this, "tickUnit", Double.NaN, axisTransformChanged); - - protected boolean isUpdatingContent = false; + protected final transient ReadOnlyDoubleWrapper tickUnit = FXUtils.createReadOnlyDoubleWrapper(this, "tickUnit", Double.NaN); /** * Create a auto-ranging AbstractAxisParameter */ public AbstractAxisParameter() { super(); - autoRangingProperty().addListener((ch, o, enabled) -> { - // disable auto grow if auto range is enabled - if (enabled) { - setAutoGrowRanging(false); - } - }); - - autoGrowRangingProperty().addListener((ch, o, enabled) -> { - // disable auto grow if auto range is enabled - if (enabled) { - setAutoRanging(false); - } - }); - - // bind limits to user-specified axis range - // userRange.set - final ChangeListener userLimitChangeListener = (ch, o, n) -> { - // Disable auto ranging if range was set manually - if (!isUpdatingContent) { - getUserRange().set(getMin(), getMax()); - setAutoRanging(false); - setAutoGrowRanging(false); - } - }; - minProperty().addListener(userLimitChangeListener); - maxProperty().addListener(userLimitChangeListener); - // TODO: remove and use styles directly? tickLabelStyle.rotateProperty().bindBidirectional(tickLabelRotation); tickLabelStyle.fontProperty().bindBidirectional(tickLabelFont); tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill); axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD - - // Provide an initial binding for the axis length - var layoutLength = Bindings.createDoubleBinding(() -> { - if (getSide() == null) { - return Double.NaN; - } - return getSide().isHorizontal() ? getWidth() : getHeight(); - }, sideProperty(), widthProperty(), heightProperty()); - length.bind(layoutLength); - } @Override @@ -561,10 +551,6 @@ public double getLength() { return lengthProperty().get(); } - protected DoubleProperty lengthProperty() { - return length; - } - // JavaFx Properties /** @@ -851,11 +837,6 @@ public IntegerProperty maxMajorTickLabelCountProperty() { return maxMajorTickLabelCount; } - @Override - public DoubleProperty maxProperty() { - return maxProp; - } - public IntegerProperty minorTickCountProperty() { return minorTickCount; } @@ -868,11 +849,34 @@ public BooleanProperty minorTickVisibleProperty() { return minorTickVisible; } + /** + * The value between each major tick mark in data units. This is automatically set if we are auto-ranging. + * + * @return tickUnit property + */ + @Override + public ReadOnlyDoubleProperty tickUnitProperty() { + return tickUnit.getReadOnlyProperty(); + } + @Override public DoubleProperty minProperty() { return minProp; } + @Override + public DoubleProperty maxProperty() { + return maxProp; + } + + public ReadOnlyDoubleProperty scaleProperty() { + return scale.getReadOnlyProperty(); + } + + public ReadOnlyDoubleProperty lengthProperty() { + return length.getReadOnlyProperty(); + } + @Override public StringProperty nameProperty() { return axisName; @@ -882,10 +886,6 @@ public ObjectProperty overlapPolicyProperty() { return overlapPolicy; } - public ReadOnlyDoubleProperty scaleProperty() { - return scale.getReadOnlyProperty(); - } - @Override public boolean set(final double min, final double max) { boolean changed = false; @@ -990,22 +990,61 @@ public void setAxisPadding(final double value) { axisPaddingProperty().set(value); } + /** + * Sets the value of the {@link #tickUnitProperty()}. + * + * @param unit major tick unit + */ + protected void setTickUnit(final double unit) { + if(getUserTickUnit() != unit) { + setUserTickUnit(unit); + } + setAutoRanging(false); + setAutoGrowRanging(false); + } + @Override - public boolean setMax(final double value) { - final double oldvalue = maxProperty().get(); - maxProperty().set(value); - return oldvalue != value; + public void setUserTickUnit(final double unit) { + userTickUnit.set(unit); } - public void setMaxMajorTickLabelCount(final int value) { - this.maxMajorTickLabelCountProperty().set(value); + @Override + public boolean setMax(final double value) { + boolean changed = getUserRange().setMax(value); + if (changed) { + axisTransformChanged.run(); + } + setAutoRanging(false); + setAutoGrowRanging(false); + return changed; } @Override public boolean setMin(final double value) { - final double oldvalue = minProperty().get(); - minProperty().set(value); - return oldvalue != value; + boolean changed = getUserRange().setMin(value); + if (changed) { + axisTransformChanged.run(); + } + setAutoRanging(false); + setAutoGrowRanging(false); + return changed; + } + + protected void setLength(final double axisLength) { + this.length.set(axisLength); + } + + protected void setDisplayedRange(AxisRange range) { + settingDisplayRange = true; + minProp.set(range.getMin()); + maxProp.set(range.getMax()); + tickUnit.set(range.getTickUnit()); + scale.set(range.getScale()); + settingDisplayRange = false; + } + + public void setMaxMajorTickLabelCount(final int value) { + this.maxMajorTickLabelCountProperty().set(value); } public void setMinorTickCount(final int value) { @@ -1074,20 +1113,6 @@ public void setTickMarkVisible(final boolean value) { tickMarkVisibleProperty().set(value); } - /** - * Sets the value of the {@link #tickUnitProperty()}. - * - * @param unit major tick unit - */ - protected void setTickUnit(final double unit) { - tickUnit.set(unit); - } - - @Override - public void setUserTickUnit(final double unit) { - userTickUnit.set(unit); - } - /** * This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition * @@ -1108,7 +1133,6 @@ public void setUnitScaling(final double value) { if (!Double.isFinite(value) || (value == 0)) { throw new IllegalArgumentException("provided number is not finite and/or zero: " + value); } - setTickUnit(value); unitScalingProperty().set(value); } @@ -1162,16 +1186,6 @@ public BooleanProperty tickMarkVisibleProperty() { return tickMarkVisible; } - /** - * The value between each major tick mark in data units. This is automatically set if we are auto-ranging. - * - * @return tickUnit property - */ - @Override - public ReadOnlyDoubleProperty tickUnitProperty() { - return tickUnit; - } - @Override public DoubleProperty userTickUnitProperty() { return userTickUnit; @@ -1226,38 +1240,37 @@ protected void updateScale() { setScale(newScale == 0 ? -1.0 : newScale); } - protected void updateAxisLabelAndUnit() { + protected void updateAxisLabel() { final String axisPrimaryLabel = getName(); - String localAxisUnit = getUnit(); - final boolean isAutoScaling = isAutoUnitScaling(); - if (isAutoScaling) { - updateScaleAndUnitPrefix(); - } + String unitString = getUnit(); - final String axisPrefix = MetricPrefix.getShortPrefix(getUnitScaling()); - if ((localAxisUnit == null || localAxisUnit.isBlank()) && !axisPrefix.isBlank()) { - localAxisUnit = ""; + // Is it correct that we scale even if there is no unit? + final String scalePrefix = MetricPrefix.getShortPrefix(getUnitScaling()); + if ((unitString == null || unitString.isBlank()) && !scalePrefix.isBlank()) { + unitString = ""; } - if (localAxisUnit == null) { + if (unitString == null) { getAxisLabel().setText(axisPrimaryLabel); } else { - getAxisLabel().setText(axisPrimaryLabel + " [" + axisPrefix + localAxisUnit + "]"); + getAxisLabel().setText(axisPrimaryLabel + " [" + scalePrefix + unitString + "]"); } } - protected void updateScaleAndUnitPrefix() { - final double range = Math.abs(getMax() - getMin()); - final double logRange = Math.log10(range); - final double power3Upper = 3.0 * Math.ceil(logRange / 3.0); - final double power3Lower = 3.0 * Math.floor(logRange / 3.0); - final double a = Math.min(power3Upper, power3Lower); - final double power = Math.pow(10, a); + protected double computeUnitScale(AxisRange axisRange) { final double oldPower = getUnitScaling(); - if ((power != oldPower) && (power != 0) && (Double.isFinite(power))) { - this.setUnitScaling(power); + if (isAutoUnitScaling()) { + final double range = Math.abs(axisRange.getLength()); + final double logRange = Math.log10(range); + final double power3Upper = 3.0 * Math.ceil(logRange / 3.0); + final double power3Lower = 3.0 * Math.floor(logRange / 3.0); + final double a = Math.min(power3Upper, power3Lower); + final double power = Math.pow(10, a); + if ((power != oldPower) && (power != 0) && (Double.isFinite(power))) { + return power; + } } - setTickUnit(range / getMinorTickCount()); + return oldPower; } ReadOnlyDoubleWrapper scalePropertyImpl() { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index 67c46024c..2c804596d 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -159,6 +159,18 @@ public double computePreferredTickUnit(final double axisLength) { return computeTickUnit(rawTickUnit); } + @Override + public double computePreferredTickUnit(AxisRange range) { + final double labelSize = getTickLabelFont().getSize() * 2; + final int numOfFittingLabels = (int) Math.floor(range.axisLength / labelSize); + final int numOfTickMarks = Math.max(Math.min(numOfFittingLabels, getMaxMajorTickLabelCount()), 2); + double rawTickUnit = (range.getMax() - range.getMin()) / numOfTickMarks; + if (rawTickUnit == 0 || Double.isNaN(rawTickUnit)) { + rawTickUnit = 1e-3; // TODO: remove this hack (eventually) ;-) + } + return computeTickUnit(rawTickUnit); + } + /** * When {@code true} zero is always included in the visible range. This only has effect if * {@link #autoRangingProperty() auto-ranging} is on. diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java index 41b3471c1..dd91ecac1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java @@ -324,6 +324,10 @@ public void set(final double newValue) { }; } + public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial) { + return new ReadOnlyDoubleWrapper(bean, name, initial); + } + public static ObjectProperty createObjectProperty(Object bean, String name, T initial, Runnable onChange) { return new SimpleObjectProperty(bean, name, initial) { @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 4d3a9c4c7..18c621b3f 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -121,6 +121,18 @@ public int clear(final int mask) { return state; } + public int clear(IntSupplier bit0) { + return clear(bit0.getAsInt()); + } + + public int clear(IntSupplier bit0, IntSupplier bit1) { + return clear(bit0.getAsInt() | bit1.getAsInt()); + } + + public int clear(IntSupplier bit0, IntSupplier bit1, IntSupplier... bits) { + return clear(bit0.getAsInt() | bit1.getAsInt() | mask(bits)); + } + public BitState addChangeListener(IntSupplier bit, StateListener listener) { return addChangeListener(bit.getAsInt(), listener); } @@ -129,6 +141,10 @@ public BitState addChangeListener(int filter, StateListener listener) { return addChangeListener(new FilteredListener(filter, listener)); } + public BitState addInvalidateListener(IntSupplier bit, StateListener listener) { + return addInvalidateListener(bit.getAsInt(), listener); + } + public BitState addInvalidateListener(int filter, StateListener listener) { return addInvalidateListener(new FilteredListener(filter, listener)); } From abee1d5f64c9e9947ed248e19f810e6dfbacf934 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 08:04:20 +0200 Subject: [PATCH 05/81] added debug printer --- .../io/fair_acc/dataset/events/BitState.java | 4 +++ .../io/fair_acc/dataset/events/ChartBits.java | 31 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 18c621b3f..b8cad07a6 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -112,6 +112,10 @@ public int getBits() { return state; } + public void getBits(StateListener action) { + action.accept(this, state); + } + public void clear() { state = 0; } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index a919fa9dd..cdbc7b39f 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -22,8 +22,37 @@ public boolean isSet(int mask) { return (bit & mask) != 0; } + private int clear(int mask) { + return mask & ~bit; + } + + public static StateListener printer() { + return (source, bits) -> System.out.println(toString(source, bits)); + } + + public static String toString(BitState bitState, int bits) { + StringBuilder builder = new StringBuilder(); + builder.append(bitState.getSource()).append(" "); + if(bits == 0) { + return builder.append("clean").toString(); + } + builder.append("dirty["); + for (ChartBits bit : knownBits) { + if(bit.isSet(bits)) { + builder.append(bit.name()).append(", "); + bits = bit.clear(bits); + } + } + if (bits != 0) { + builder.append("UNKNOWN, "); + } + builder.setLength(builder.length() - 2); + return builder.append("]").toString(); + } + final int bit = 1 << ordinal(); - public static final int ANY = BitState.mask(ChartBits.values()); + private static final ChartBits[] knownBits = ChartBits.values(); + public static final int ANY = BitState.mask(knownBits); } From 3d879182d5488ff093b6c697b99dcc2c885986df Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 08:24:07 +0200 Subject: [PATCH 06/81] created dedicated property util class --- .../axes/spi/AbstractAxisParameter.java | 52 +++++------- .../chartfx/axes/spi/DefaultNumericAxis.java | 7 +- .../io/fair_acc/chartfx/utils/FXUtils.java | 60 ------------- .../io/fair_acc/chartfx/utils/PropUtil.java | 85 +++++++++++++++++++ 4 files changed, 108 insertions(+), 96 deletions(-) create mode 100644 chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 0442e2bbc..2f8e3fad0 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -10,7 +10,7 @@ import io.fair_acc.chartfx.ui.css.StyleUtil; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.ui.layout.ChartPane; -import io.fair_acc.chartfx.utils.FXUtils; +import io.fair_acc.chartfx.utils.PropUtil; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; import javafx.beans.property.*; @@ -227,12 +227,12 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The scale factor from data units to visual units */ - private final transient ReadOnlyDoubleWrapper scale = FXUtils.createReadOnlyDoubleWrapper(this, "scale", 1); + private final transient ReadOnlyDoubleWrapper scale = PropUtil.createReadOnlyDoubleWrapper(this, "scale", 1); /** * The axis length in pixels */ - private final transient ReadOnlyDoubleWrapper length = FXUtils.createReadOnlyDoubleWrapper(this, "length", Double.NaN, axisTransformChanged) ; + private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, state.onAction(ChartBits.AxisTransform, ChartBits.AxisCanvas)) ; private boolean settingDisplayRange = false; @@ -269,7 +269,7 @@ public void set(final double newValue) { /** * StringConverter used to format tick mark labels. If null a default will be used */ - private final transient ObjectProperty> tickLabelFormatter = FXUtils.createObjectProperty(this, "tickLabelFormatter", null, state.onAction(ChartBits.AxisTransform, ChartBits.AxisTickFormatter)); + private final transient ObjectProperty> tickLabelFormatter = PropUtil.createObjectProperty(this, "tickLabelFormatter", null, state.onAction(ChartBits.AxisTransform, ChartBits.AxisTickFormatter)); /** * The length of minor tick mark lines. Set to 0 to not display minor tick marks. @@ -284,13 +284,13 @@ public void set(final double newValue) { private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, layoutChangedAction); protected boolean isInvertedAxis = false; // internal use (for performance reason) - private final transient BooleanProperty invertAxis = FXUtils.createBooleanProperty(this, "invertAxis", isInvertedAxis, ()->{ + private final transient BooleanProperty invertAxis = PropUtil.createBooleanProperty(this, "invertAxis", isInvertedAxis, ()->{ isInvertedAxis = invertAxisProperty().get(); layoutChangedAction.run(); }); protected boolean isTimeAxis = false; // internal use (for performance reasons) - private final transient BooleanProperty timeAxis = FXUtils.createBooleanProperty(this, "timeAxis", isTimeAxis, ()->{ + private final transient BooleanProperty timeAxis = PropUtil.createBooleanProperty(this, "timeAxis", isTimeAxis, ()->{ isTimeAxis = timeAxisProperty().get(); if (isTimeAxis) { setMinorTickCount(0); @@ -302,7 +302,7 @@ public void set(final double newValue) { private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, axisTransformChanged); - private final transient DoubleProperty autoRangePadding = FXUtils.createDoubleProperty(this, "autoRangePadding", 0, axisTransformChanged); + private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0, axisTransformChanged); /** * The axis unit label @@ -312,12 +312,12 @@ public void set(final double newValue) { /** * The axis unit label */ - private final transient BooleanProperty autoUnitScaling = FXUtils.createBooleanProperty(this, "autoUnitScaling", false, axisNameChangedAction); + private final transient BooleanProperty autoUnitScaling = PropUtil.createBooleanProperty(this, "autoUnitScaling", false, axisNameChangedAction); /** * The axis unit label */ - private final transient DoubleProperty unitScaling = FXUtils.createDoubleProperty(this, "unitScaling", 1.0, axisNameChangedAction); + private final transient DoubleProperty unitScaling = PropUtil.createDoubleProperty(this, "unitScaling", 1.0, axisNameChangedAction); /** * user-set tick units when doing auto-ranging @@ -332,7 +332,7 @@ public void set(final double newValue) { /** * system-set tick units for getting the currently displayed units */ - protected final transient ReadOnlyDoubleWrapper tickUnit = FXUtils.createReadOnlyDoubleWrapper(this, "tickUnit", Double.NaN); + protected final transient ReadOnlyDoubleWrapper tickUnit = PropUtil.createReadOnlyDoubleWrapper(this, "tickUnit", Double.NaN); /** * Create a auto-ranging AbstractAxisParameter @@ -888,28 +888,14 @@ public ObjectProperty overlapPolicyProperty() { @Override public boolean set(final double min, final double max) { - boolean changed = false; - if (min != minProp.get()) { - minProp.set(min); - changed = true; - } - if (max != maxProp.get()) { - maxProp.set(max); - changed = true; - } - return changed; + return PropUtil.set(minProp, min) | PropUtil.set(maxProp, max); } @Override public boolean set(final String axisName, final String... axisUnit) { - boolean changed = false; - if (!equalString(axisName, getName())) { - setName(axisName); - changed = true; - } - if ((axisUnit != null) && (axisUnit.length > 0) && !equalString(axisUnit[0], getUnit())) { - setUnit(axisUnit[0]); - changed = true; + boolean changed = PropUtil.set(nameProperty(), axisName); + if (axisUnit.length > 0) { + changed |= PropUtil.set(unitProperty(), axisUnit[0]); } return changed; } @@ -1036,10 +1022,12 @@ protected void setLength(final double axisLength) { protected void setDisplayedRange(AxisRange range) { settingDisplayRange = true; - minProp.set(range.getMin()); - maxProp.set(range.getMax()); - tickUnit.set(range.getTickUnit()); - scale.set(range.getScale()); + if (PropUtil.set(minProp, range.getMin()) + | PropUtil.set(maxProp, range.getMax()) + | PropUtil.set(tickUnit, range.getTickUnit()) + | PropUtil.set(scale, range.getScale())) { + state.setDirty(ChartBits.AxisCanvas); + } settingDisplayRange = false; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index 2c804596d..a64159417 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -5,10 +5,9 @@ import java.util.Collections; import java.util.List; -import io.fair_acc.chartfx.utils.FXUtils; +import io.fair_acc.chartfx.utils.PropUtil; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.chart.NumberAxis; import org.slf4j.Logger; @@ -48,11 +47,11 @@ public class DefaultNumericAxis extends AbstractAxis implements Axis { private transient AxisTransform axisTransform = linearTransform; protected boolean isUpdating; - private final transient BooleanProperty forceZeroInRange = FXUtils.createBooleanProperty(this, "forceZeroInRange", false, axisTransformChanged); + private final transient BooleanProperty forceZeroInRange = PropUtil.createBooleanProperty(this, "forceZeroInRange", false, axisTransformChanged); protected boolean isLogAxis = false; // internal use (for performance reason - private final transient BooleanProperty logAxis = FXUtils.createBooleanProperty(this, "logAxis", isLogAxis, () -> { + private final transient BooleanProperty logAxis = PropUtil.createBooleanProperty(this, "logAxis", isLogAxis, () -> { isLogAxis = isLogAxis(); if (isLogAxis) { if (DefaultNumericAxis.this.isTimeAxis()) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java index dd91ecac1..3bcaaa39a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java @@ -12,10 +12,7 @@ import java.util.function.Function; import java.util.function.Supplier; -import io.fair_acc.chartfx.axes.spi.AbstractAxisParameter; -import io.fair_acc.dataset.events.ChartBits; import javafx.application.Platform; -import javafx.beans.property.*; import javafx.scene.Node; import javafx.scene.Scene; @@ -285,61 +282,4 @@ public static List sizedList(List list, int desiredSize return list; } - public static BooleanProperty createBooleanProperty(Object bean, String name, boolean initial, Runnable onChange) { - return new SimpleBooleanProperty(bean, name, initial) { - @Override - public void set(final boolean newValue) { - final boolean oldValue = get(); - if (oldValue != newValue) { - super.set(newValue); - onChange.run(); - } - } - }; - } - - public static DoubleProperty createDoubleProperty(Object bean, String name, double initial, Runnable onChange) { - return new SimpleDoubleProperty(bean, name, initial) { - @Override - public void set(final double newValue) { - final double oldValue = get(); - if (oldValue != newValue) { - super.set(newValue); - onChange.run(); - } - } - }; - } - - public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial, Runnable onChange) { - return new ReadOnlyDoubleWrapper(bean, name, initial) { - @Override - public void set(final double newValue) { - final double oldValue = get(); - if (oldValue != newValue) { - super.set(newValue); - onChange.run(); - } - } - }; - } - - public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial) { - return new ReadOnlyDoubleWrapper(bean, name, initial); - } - - public static ObjectProperty createObjectProperty(Object bean, String name, T initial, Runnable onChange) { - return new SimpleObjectProperty(bean, name, initial) { - @Override - public void set(final T newValue) { - final T oldValue = getValue(); - if (oldValue != newValue) { - super.set(newValue); - onChange.run(); - } - } - }; - } - - } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java new file mode 100644 index 000000000..f0bb988da --- /dev/null +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java @@ -0,0 +1,85 @@ +package io.fair_acc.chartfx.utils; + +import javafx.beans.property.*; + +import java.util.Objects; + +/** + * Utility class for working with JavaFX properties + * + * @author ennerf + */ +public class PropUtil { + + public static boolean set(DoubleProperty prop, double value) { + if (prop.get() == value) { + return false; + } + prop.set(value); + return true; + } + + public static boolean set(StringProperty prop, String value){ + if (Objects.equals(prop.get(), value)) { + return false; + } + prop.set(value); + return true; + } + + public static BooleanProperty createBooleanProperty(Object bean, String name, boolean initial, Runnable onChange) { + return new SimpleBooleanProperty(bean, name, initial) { + @Override + public void set(final boolean newValue) { + final boolean oldValue = get(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } + + public static DoubleProperty createDoubleProperty(Object bean, String name, double initial, Runnable onChange) { + return new SimpleDoubleProperty(bean, name, initial) { + @Override + public void set(final double newValue) { + final double oldValue = get(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } + + public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial, Runnable onChange) { + return new ReadOnlyDoubleWrapper(bean, name, initial) { + @Override + public void set(final double newValue) { + final double oldValue = get(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } + + public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial) { + return new ReadOnlyDoubleWrapper(bean, name, initial); + } + + public static ObjectProperty createObjectProperty(Object bean, String name, T initial, Runnable onChange) { + return new SimpleObjectProperty(bean, name, initial) { + @Override + public void set(final T newValue) { + final T oldValue = getValue(); + if (oldValue != newValue) { + super.set(newValue); + onChange.run(); + } + } + }; + } +} From e0fb8f8c3e774317bc74f4f467f1989a891d6381 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 08:35:49 +0200 Subject: [PATCH 07/81] fixed clean check --- .../chartfx/axes/spi/AbstractAxis.java | 28 +++++++++++-------- .../axes/spi/AbstractAxisParameter.java | 4 +-- .../io/fair_acc/dataset/events/BitState.java | 2 +- .../io/fair_acc/dataset/events/ChartBits.java | 1 - 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 9eb97fa61..9c2dd1246 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -318,8 +318,8 @@ public boolean isValueOnAxis(final double value) { * range, caches, etc. */ protected void updateContent() { - final double length = Math.max(1, getLength()); - if (!Double.isFinite(length) || state.isClean()) { + final double length = getLength(); + if (!Double.isFinite(length) || length <= 0 || state.isClean(ChartBits.AxisTransform, ChartBits.AxisTickFormatter)) { return; } @@ -945,6 +945,7 @@ protected void layoutChildren() { // Layout only gets called on actual size changes. The length already gets set during // the prefSize phase, so this is more of a sanity check. setLength(getSide().isHorizontal() ? getWidth() : getHeight()); + state.setDirty(ChartBits.AxisCanvas); } @Override @@ -956,17 +957,20 @@ public void drawAxis() { // update labels, tick marks etc. updateContent(); - // clear outdated canvas content - final var gc = canvas.getGraphicsContext2D(); - clearAxisCanvas(gc, canvas.getWidth(), canvas.getHeight()); - - // the canvas has extra padding, so move in a bit - try { - gc.translate(canvasPadX, canvasPadY); - drawAxis(gc, getWidth(), getHeight()); - } finally { - gc.translate(-canvasPadX, -canvasPadY); + // redraw outdated canvas + if (state.isDirty(ChartBits.AxisCanvas)) { + final var gc = canvas.getGraphicsContext2D(); + clearAxisCanvas(gc, canvas.getWidth(), canvas.getHeight()); + try { + // the canvas has extra padding, so move in a bit + gc.translate(canvasPadX, canvasPadY); + drawAxis(gc, getWidth(), getHeight()); + } finally { + gc.translate(-canvasPadX, -canvasPadY); + } } + + // everything is updated state.clear(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 2f8e3fad0..d0138620c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -45,9 +45,9 @@ */ public abstract class AbstractAxisParameter extends Pane implements Axis { protected final BitState state = BitState.initDirty(this); - protected final Runnable layoutChangedAction = state.onAction(ChartBits.AxisLayout); + protected final Runnable layoutChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas); protected final Runnable axisTransformChanged = state.onAction(ChartBits.AxisTransform); - protected final Runnable axisNameChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisLabelText); + protected final Runnable axisNameChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas, ChartBits.AxisLabelText); protected final ChangeListener layoutChangedListener = state.onPropChange(ChartBits.AxisLayout)::set; protected final ChangeListener axisTransformChangedListener = state.onPropChange(ChartBits.AxisTransform)::set; diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index b8cad07a6..d87652c12 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -93,7 +93,7 @@ public boolean isClean() { } public boolean isClean(int mask) { - return (state & mask) != 0; + return (state & mask) == 0; } public boolean isClean(IntSupplier bit0) { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index cdbc7b39f..e7328962b 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -9,7 +9,6 @@ public enum ChartBits implements IntSupplier { AxisCanvas, AxisLayout, AxisTransform, - AxisTickLocation, AxisTickFormatter, AxisLabelText; From 1fde0712ba1993f737eb04863a74f17ea0f52e84 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 09:00:28 +0200 Subject: [PATCH 08/81] improved readability --- .../chartfx/axes/spi/AbstractAxis.java | 30 +++++++++++-------- .../axes/spi/AbstractAxisParameter.java | 28 ++++++++--------- .../chartfx/axes/spi/DefaultNumericAxis.java | 6 ++-- .../io/fair_acc/dataset/events/ChartBits.java | 17 ++++++----- 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 9c2dd1246..5b5195a52 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -101,7 +101,7 @@ protected AbstractAxis() { // We can ignore the layout if labels can only move linearly along // the axis length. This happens e.g. for an X-axis that displays // moving time with the default policy. - state.addChangeListener(ChartBits.AxisTransform, (source, bits) -> { + state.addChangeListener(ChartBits.AxisRange, (source, bits) -> { if (!isTickMarkVisible() || !isTickLabelsVisible()) { return; } @@ -134,7 +134,7 @@ protected AbstractAxis() { final AxisChangeEvent axisLabelEvent = new AxisNameChangeEvent(this); final AxisChangeEvent otherChangeEvent = new AxisChangeEvent(this); state.addChangeListener((source, bits) -> { - if (ChartBits.AxisTransform.isSet(bits)) { + if (ChartBits.AxisRange.isSet(bits)) { invokeListener(axisTransformEvent, false); } else if (ChartBits.AxisLabelText.isSet(bits)) { invokeListener(axisLabelEvent, false); @@ -294,7 +294,7 @@ public void invalidateCaches() { */ @Override public void invalidateRange() { - axisTransformChanged.run(); + axisRangeChanged.run(); } public boolean isLabelOverlapping() { @@ -317,9 +317,9 @@ public boolean isValueOnAxis(final double value) { * Updates the contents for this axis, e.g., tick labels, spacing * range, caches, etc. */ - protected void updateContent() { + protected void updateAxisRange() { final double length = getLength(); - if (!Double.isFinite(length) || length <= 0 || state.isClean(ChartBits.AxisTransform, ChartBits.AxisTickFormatter)) { + if (!Double.isFinite(length) || length <= 0 || state.isClean(ChartBits.AxisRange)) { return; } @@ -508,14 +508,20 @@ private double computePrefSize(final double axisLength) { return computeMinSize(); } - // Set the axis length, so we can compute ticks with correctly placed - // labels to determine the overlap. The initial estimate is usually - // correct, so later changes happen very rarely, e.g., at a point where - // y axes labels switch to shifting lines. + // Set the axis length to determine the actual ticks. We can + // cache the existing layout if nothing has changed. + final boolean isHorizontal = getSide().isHorizontal(); setLength(axisLength); - updateContent(); + if (state.isClean(ChartBits.AxisLayout)) { + return isHorizontal ? getWidth() : getHeight(); + } + + // Compute the ticks with correctly placed labels to determine the + // overlap. The initial estimate is usually correct, so later changes + // happen very rarely, e.g., at a point where y axes labels switch to + // shifting lines. + updateAxisRange(); - boolean isHorizontal = getSide().isHorizontal(); scaleFont = 1.0; maxLabelHeight = 0; maxLabelWidth = 0; @@ -955,7 +961,7 @@ public void drawAxis() { } // update labels, tick marks etc. - updateContent(); + updateAxisRange(); // redraw outdated canvas if (state.isDirty(ChartBits.AxisCanvas)) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index d0138620c..5efaa56d2 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -44,12 +44,12 @@ * @author rstein */ public abstract class AbstractAxisParameter extends Pane implements Axis { - protected final BitState state = BitState.initDirty(this); + protected final BitState state = BitState.initDirty(this, ChartBits.AxisMask); protected final Runnable layoutChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas); - protected final Runnable axisTransformChanged = state.onAction(ChartBits.AxisTransform); + protected final Runnable axisRangeChanged = state.onAction(ChartBits.AxisRange); protected final Runnable axisNameChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas, ChartBits.AxisLabelText); protected final ChangeListener layoutChangedListener = state.onPropChange(ChartBits.AxisLayout)::set; - protected final ChangeListener axisTransformChangedListener = state.onPropChange(ChartBits.AxisTransform)::set; + protected final ChangeListener axisTransformChangedListener = state.onPropChange(ChartBits.AxisRange)::set; private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Region.getClassCssMetaData()); @@ -159,14 +159,14 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { if (isAutoRanging()) { setAutoGrowRanging(false); } - axisTransformChanged.run(); + axisRangeChanged.run(); }); private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, () -> { if (isAutoGrowRanging()) { setAutoRanging(false); } - axisTransformChanged.run(); + axisRangeChanged.run(); }); /** @@ -192,7 +192,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The minimum gap between tick labels */ - private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, axisTransformChanged); + private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, axisRangeChanged); /** * The gap between tick labels and the axis label @@ -207,7 +207,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The maximum number of ticks */ - private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, axisTransformChanged); + private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, axisRangeChanged); /** * When true any changes to the axis and its range will be animated. @@ -222,7 +222,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * true if minor tick marks should be displayed */ - private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, axisTransformChanged); + private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, axisRangeChanged); /** * The scale factor from data units to visual units @@ -232,7 +232,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The axis length in pixels */ - private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, state.onAction(ChartBits.AxisTransform, ChartBits.AxisCanvas)) ; + private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, state.onAction(ChartBits.AxisRange, ChartBits.AxisCanvas)) ; private boolean settingDisplayRange = false; @@ -269,7 +269,7 @@ public void set(final double newValue) { /** * StringConverter used to format tick mark labels. If null a default will be used */ - private final transient ObjectProperty> tickLabelFormatter = PropUtil.createObjectProperty(this, "tickLabelFormatter", null, state.onAction(ChartBits.AxisTransform, ChartBits.AxisTickFormatter)); + private final transient ObjectProperty> tickLabelFormatter = PropUtil.createObjectProperty(this, "tickLabelFormatter", null, state.onAction(ChartBits.AxisRange, ChartBits.AxisTickFormatter)); /** * The length of minor tick mark lines. Set to 0 to not display minor tick marks. @@ -300,9 +300,9 @@ public void set(final double newValue) { layoutChangedAction.run(); }); - private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, axisTransformChanged); + private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, axisRangeChanged); - private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0, axisTransformChanged); + private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0, axisRangeChanged); /** * The axis unit label @@ -998,7 +998,7 @@ public void setUserTickUnit(final double unit) { public boolean setMax(final double value) { boolean changed = getUserRange().setMax(value); if (changed) { - axisTransformChanged.run(); + axisRangeChanged.run(); } setAutoRanging(false); setAutoGrowRanging(false); @@ -1009,7 +1009,7 @@ public boolean setMax(final double value) { public boolean setMin(final double value) { boolean changed = getUserRange().setMin(value); if (changed) { - axisTransformChanged.run(); + axisRangeChanged.run(); } setAutoRanging(false); setAutoGrowRanging(false); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index a64159417..45f7aa4b5 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -47,7 +47,7 @@ public class DefaultNumericAxis extends AbstractAxis implements Axis { private transient AxisTransform axisTransform = linearTransform; protected boolean isUpdating; - private final transient BooleanProperty forceZeroInRange = PropUtil.createBooleanProperty(this, "forceZeroInRange", false, axisTransformChanged); + private final transient BooleanProperty forceZeroInRange = PropUtil.createBooleanProperty(this, "forceZeroInRange", false, axisRangeChanged); protected boolean isLogAxis = false; // internal use (for performance reason @@ -75,7 +75,7 @@ public class DefaultNumericAxis extends AbstractAxis implements Axis { } } - axisTransformChanged.run(); + axisRangeChanged.run(); }); /** @@ -336,7 +336,7 @@ public void setForceZeroInRange(final boolean value) { */ public void setLogarithmBase(final double value) { logarithmBaseProperty().set(value); - axisTransformChanged.run(); + axisRangeChanged.run(); } /** diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index e7328962b..fd2b01288 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -6,11 +6,15 @@ * @author ennerf */ public enum ChartBits implements IntSupplier { - AxisCanvas, - AxisLayout, - AxisTransform, - AxisTickFormatter, - AxisLabelText; + AxisLayout, // size needs to be evaluated (e.g. labels may be larger) + AxisCanvas, // needs to be drawn + AxisRange, // anything related to min/max, tick marks, etc. + AxisTickFormatter, // tick label formatting + AxisLabelText; // display name or units + + private static final ChartBits[] knownBits = ChartBits.values(); + public static final int KnownMask = BitState.mask(knownBits); + public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickFormatter, AxisLabelText); @Override public int getAsInt() { @@ -51,7 +55,4 @@ public static String toString(BitState bitState, int bits) { final int bit = 1 << ordinal(); - private static final ChartBits[] knownBits = ChartBits.values(); - public static final int ANY = BitState.mask(knownBits); - } From 17241b1948230ccad68f03dd6b6143baf3fb442c Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 10:45:13 +0200 Subject: [PATCH 09/81] updated unit tests --- .../axes/spi/AbstractAxisParameter.java | 45 +++++++++++++------ .../axes/spi/AbstractAxisParameterTests.java | 33 +++++++++----- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 5efaa56d2..a8c863c12 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -232,9 +232,9 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The axis length in pixels */ - private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, state.onAction(ChartBits.AxisRange, ChartBits.AxisCanvas)) ; + private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, axisRangeChanged) ; - private boolean settingDisplayRange = false; + boolean updateDisplayRange = false; /** * The value for the upper bound of this axis, ie max value. This is automatically set if auto ranging is on. @@ -242,7 +242,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { protected final transient ReadOnlyDoubleWrapper maxProp = new ReadOnlyDoubleWrapper(this, "upperBound", DEFAULT_MAX_RANGE) { @Override public void set(final double newValue) { - if (settingDisplayRange) { + if (updateDisplayRange) { super.set(newValue); } else { setMax(newValue); @@ -256,7 +256,7 @@ public void set(final double newValue) { protected final transient ReadOnlyDoubleWrapper minProp = new ReadOnlyDoubleWrapper(this, "lowerBound", DEFAULT_MIN_RANGE) { @Override public void set(final double newValue) { - if (settingDisplayRange) { + if (updateDisplayRange) { super.set(newValue); } else { setMin(newValue); @@ -888,7 +888,7 @@ public ObjectProperty overlapPolicyProperty() { @Override public boolean set(final double min, final double max) { - return PropUtil.set(minProp, min) | PropUtil.set(maxProp, max); + return setMin(min) | setMax(max); } @Override @@ -982,7 +982,11 @@ public void setAxisPadding(final double value) { * @param unit major tick unit */ protected void setTickUnit(final double unit) { - if(getUserTickUnit() != unit) { + if (updateDisplayRange) { + PropUtil.set(tickUnit, unit); + return; + } + if (getUserTickUnit() != unit) { setUserTickUnit(unit); } setAutoRanging(false); @@ -996,6 +1000,9 @@ public void setUserTickUnit(final double unit) { @Override public boolean setMax(final double value) { + if (updateDisplayRange) { + return PropUtil.set(maxProp, value); + } boolean changed = getUserRange().setMax(value); if (changed) { axisRangeChanged.run(); @@ -1007,6 +1014,9 @@ public boolean setMax(final double value) { @Override public boolean setMin(final double value) { + if (updateDisplayRange) { + return PropUtil.set(minProp, value); + } boolean changed = getUserRange().setMin(value); if (changed) { axisRangeChanged.run(); @@ -1021,14 +1031,19 @@ protected void setLength(final double axisLength) { } protected void setDisplayedRange(AxisRange range) { - settingDisplayRange = true; - if (PropUtil.set(minProp, range.getMin()) - | PropUtil.set(maxProp, range.getMax()) - | PropUtil.set(tickUnit, range.getTickUnit()) - | PropUtil.set(scale, range.getScale())) { + setDisplayedRange(range.getMin(), range.getMax(), range.getAxisLength(), range.getScale(), range.getTickUnit()); + } + + protected void setDisplayedRange(double min, double max, double axisLength, double scale, double unit) { + updateDisplayRange = true; + if (PropUtil.set(this.minProp, min) + | PropUtil.set(this.maxProp, max) + | PropUtil.set(this.length, axisLength) + | PropUtil.set(this.scale, scale) + | PropUtil.set(this.tickUnit, unit)) { state.setDirty(ChartBits.AxisCanvas); } - settingDisplayRange = false; + updateDisplayRange = false; } public void setMaxMajorTickLabelCount(final int value) { @@ -1245,6 +1260,10 @@ protected void updateAxisLabel() { } } + public BitState getBitState() { + return state; + } + protected double computeUnitScale(AxisRange axisRange) { final double oldPower = getUnitScaling(); if (isAutoUnitScaling()) { @@ -1278,7 +1297,7 @@ protected static boolean equalString(final String str1, final String str2) { @Deprecated protected void invalidate() { - requestAxisLayout(); + state.setDirty(ChartBits.AxisLayout); } } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index 2346caf0b..70412002b 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -4,8 +4,7 @@ import static io.fair_acc.dataset.DataSet.DIM_X; -import java.util.List; - +import io.fair_acc.dataset.events.ChartBits; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; @@ -68,13 +67,26 @@ void testAutoGetterSetters() { @Test void testBasicGetterSetters() { AbstractAxisParameter axis = new EmptyAbstractAxisParameter(); - axis.set(0.0, 10.0); - assertFalse(axis.isValid()); - axis.validProperty().set(true); - assertTrue(axis.isValid()); - axis.invalidate(); - assertFalse(axis.isValid()); + // Match previous unit test behavior by immediately updating the label and + // applying changes to the range rather than user range. + var state = axis.getBitState(); + state.addChangeListener(ChartBits.AxisLabelText, (src, bits) -> { + axis.updateScale(); + axis.updateAxisLabel(); + state.clear(ChartBits.AxisLabelText); + }); + axis.updateDisplayRange = true; + axis.minProp.addListener(state.onPropChange(ChartBits.AxisCanvas)::set); + axis.maxProp.addListener(state.onPropChange(ChartBits.AxisCanvas)::set); + + assertTrue(state.isDirty()); + state.clear(); + assertTrue(state.isClean()); + + axis.set(0.0, 10.0); + assertTrue(state.isDirty()); + state.clear(); axis.set(Double.NaN, Double.NaN); assertFalse(axis.isDefined()); @@ -146,7 +158,8 @@ void testBasicGetterSetters() { axis.setScale(2.0); assertEquals(2.0, axis.getScale()); - assertEquals(Side.BOTTOM, axis.getSide()); + // TODO: behavior changed. Do we still need these tests? + /*assertEquals(Side.BOTTOM, axis.getSide()); for (Side side : Side.values()) { axis.setSide(side); assertEquals(side, axis.getSide()); @@ -158,7 +171,7 @@ void testBasicGetterSetters() { } axis.setSide(null); assertEquals(Double.NaN, axis.getLength()); - axis.setSide(Side.LEFT); + axis.setSide(Side.LEFT);*/ assertFalse(axis.isTimeAxis()); axis.setTimeAxis(true); From 5383ce9b10b6ebf30a7e31f6fe9d8a53f36f5b2e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 17:10:36 +0200 Subject: [PATCH 10/81] improved readability --- .../chartfx/axes/spi/AbstractAxis.java | 71 +++++------ .../axes/spi/AbstractAxisParameter.java | 115 +++++++++--------- .../chartfx/axes/spi/DefaultNumericAxis.java | 6 +- .../io/fair_acc/chartfx/utils/PropUtil.java | 36 ++++++ .../axes/spi/AbstractAxisParameterTests.java | 2 - 5 files changed, 132 insertions(+), 98 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 5b5195a52..375d12bf1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -224,11 +224,6 @@ public void fireInvalidated() { } } - @Override - public void forceRedraw() { - layoutChangedAction.run(); - } - public AxisLabelFormatter getAxisLabelFormatter() { return axisFormatter.get(); } @@ -280,23 +275,6 @@ public double getZeroPosition() { return getDisplayPosition(0.0); } - public void invalidateCaches() { - getTickMarkValues().clear(); - getMinorTickMarkValues().clear(); - getTickMarks().clear(); - getMinorTickMarks().clear(); - } - - /** - * Called when data has changed and the range may not be valid anymore. This is only called by the chart if - * isAutoRanging() returns true. If we are auto ranging it will cause layout to be requested and auto ranging to - * happen on next layout pass. - */ - @Override - public void invalidateRange() { - axisRangeChanged.run(); - } - public boolean isLabelOverlapping() { // needed for diagnostics purposes return labelOverlap; @@ -370,17 +348,6 @@ public AxisRange getRange() { return getUserRange(); } - /** - * Request that the axis is laid out in the next layout pass. This replaces requestLayout() as it has been - * overridden to do nothing so that changes to children's bounds etc do not cause a layout. This was done as a - * optimisation as the Axis knows the exact minimal set of changes that really need layout to be updated. So we only - * want to request layout then, not on any child change. - */ - @Override - public void requestAxisLayout() { - state.setDirty(ChartBits.AxisLayout); - } - public void setAxisLabelFormatter(final AxisLabelFormatter value) { axisFormatter.set(value); } @@ -951,7 +918,7 @@ protected void layoutChildren() { // Layout only gets called on actual size changes. The length already gets set during // the prefSize phase, so this is more of a sanity check. setLength(getSide().isHorizontal() ? getWidth() : getHeight()); - state.setDirty(ChartBits.AxisCanvas); + invalidateCanvas.run(); } @Override @@ -1197,4 +1164,40 @@ protected static double snap(final double coordinate) { return Math.round(coordinate) + 0.5; // center of a pixel, so 1px lines render exact. TODO: depend on line width? } + /* + * ****************************************************** + * Update methods for backwards compatibility + * ****************************************************** + */ + + /** + * Request that the axis is laid out in the next layout pass. This replaces requestLayout() as it has been + * overridden to do nothing so that changes to children's bounds etc do not cause a layout. This was done as a + * optimisation as the Axis knows the exact minimal set of changes that really need layout to be updated. So we only + * want to request layout then, not on any child change. + */ + @Override + public void requestAxisLayout() { + invalidateLayout.run(); + } + + protected void invalidate() { + invalidateLayout.run(); + } + + @Override + public void forceRedraw() { + invalidateLayout.run(); + } + + /** + * Called when data has changed and the range may not be valid anymore. This is only called by the chart if + * isAutoRanging() returns true. If we are auto ranging it will cause layout to be requested and auto ranging to + * happen on next layout pass. + */ + @Override + public void invalidateRange() { + invalidateAxisRange.run(); + } + } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index a8c863c12..9cde671d0 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -14,7 +14,6 @@ import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; import javafx.beans.property.*; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.css.*; @@ -45,11 +44,11 @@ */ public abstract class AbstractAxisParameter extends Pane implements Axis { protected final BitState state = BitState.initDirty(this, ChartBits.AxisMask); - protected final Runnable layoutChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas); - protected final Runnable axisRangeChanged = state.onAction(ChartBits.AxisRange); - protected final Runnable axisNameChangedAction = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas, ChartBits.AxisLabelText); - protected final ChangeListener layoutChangedListener = state.onPropChange(ChartBits.AxisLayout)::set; - protected final ChangeListener axisTransformChangedListener = state.onPropChange(ChartBits.AxisRange)::set; + protected final Runnable invalidateLayout = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas); + protected final Runnable invalidateAxisRange = state.onAction(ChartBits.AxisRange); + protected final Runnable invalidateTickFormatter = state.onAction(ChartBits.AxisRange, ChartBits.AxisTickFormatter); + protected final Runnable invalidateCanvas = state.onAction(ChartBits.AxisCanvas); + protected final Runnable invalidateAxisName = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas, ChartBits.AxisLabelText); private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Region.getClassCssMetaData()); @@ -65,7 +64,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); - private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, layoutChangedAction); + private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, invalidateLayout); /** * Nodes used for css-type styling. Not used for actual drawing. Used as a storage container for the settings @@ -81,10 +80,12 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { { StyleUtil.addStyles(this, "axis"); getChildren().addAll(axisLabel, tickLabelStyle, majorTickStyle, minorTickStyle); - majorTickStyle.changeCounterProperty().addListener(layoutChangedListener); - minorTickStyle.changeCounterProperty().addListener(state.onPropChange(ChartBits.AxisCanvas)::set); - tickLabelStyle.changeCounterProperty().addListener(layoutChangedListener); - axisLabel.changeCounterProperty().addListener(layoutChangedListener); + PropUtil.addChangeListener(invalidateLayout, + majorTickStyle.changeCounterProperty(), + tickLabelStyle.changeCounterProperty(), + axisLabel.changeCounterProperty()); + PropUtil.addChangeListener(invalidateCanvas, + minorTickStyle.changeCounterProperty()); // not used for layout calculation } // TODO: replace with primitive DoubleArrayList and specialized TickMarkList @@ -109,48 +110,48 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { */ private final transient StyleableObjectProperty side = CSS.createEnumPropertyWithPseudoclasses(this, "side", Side.BOTTOM, false, Side.class, null, () -> { ChartPane.setSide(this, getSide()); - state.setDirty(ChartBits.AxisLayout); + invalidateLayout.run(); }); /** * The side of the plot which this axis is being drawn on */ - private final transient StyleableObjectProperty overlapPolicy = CSS.createObjectProperty(this, "overlapPolicy", AxisLabelOverlapPolicy.SKIP_ALT, StyleConverter.getEnumConverter(AxisLabelOverlapPolicy.class), layoutChangedAction); + private final transient StyleableObjectProperty overlapPolicy = CSS.createObjectProperty(this, "overlapPolicy", AxisLabelOverlapPolicy.SKIP_ALT, StyleConverter.getEnumConverter(AxisLabelOverlapPolicy.class), invalidateLayout); /** * The relative alignment (N.B. clamped to [0.0,1.0]) of the axis if drawn on top of the main canvas (N.B. side == CENTER_HOR or CENTER_VER */ - private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), layoutChangedAction); + private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), invalidateLayout); /** * axis label alignment */ - private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), layoutChangedAction); + private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), invalidateLayout); /** * The axis label */ - private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", axisNameChangedAction); + private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", invalidateAxisName); /** * true if tick marks should be displayed */ - private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, layoutChangedAction); + private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, invalidateLayout); /** * true if tick mark labels should be displayed */ - private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true, layoutChangedAction); + private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true, invalidateLayout); /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, layoutChangedAction); + private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, invalidateLayout); /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, layoutChangedAction); + private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, invalidateLayout); /** * This is true when the axis determines its range from the data automatically @@ -159,20 +160,20 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { if (isAutoRanging()) { setAutoGrowRanging(false); } - axisRangeChanged.run(); + invalidateAxisRange.run(); }); private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, () -> { if (isAutoGrowRanging()) { setAutoRanging(false); } - axisRangeChanged.run(); + invalidateAxisRange.run(); }); /** * The font for all tick labels */ - private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, layoutChangedAction); + private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, invalidateLayout); /** * The fill for all tick labels @@ -182,32 +183,32 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The gap between tick marks and the canvas area */ - private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0, layoutChangedAction); + private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0, invalidateLayout); /** * The gap between tick labels and the tick mark lines */ - private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, layoutChangedAction); + private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, invalidateLayout); /** * The minimum gap between tick labels */ - private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, axisRangeChanged); + private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, invalidateAxisRange); /** * The gap between tick labels and the axis label */ - private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, layoutChangedAction); + private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, invalidateLayout); /** * The animation duration in MS */ - private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, layoutChangedAction); + private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, invalidateLayout); /** * The maximum number of ticks */ - private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, axisRangeChanged); + private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, invalidateAxisRange); /** * When true any changes to the axis and its range will be animated. @@ -217,12 +218,12 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * Rotation in degrees of tick mark labels from their normal horizontal. */ - protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, layoutChangedAction); + protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, invalidateLayout); /** * true if minor tick marks should be displayed */ - private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, axisRangeChanged); + private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, invalidateAxisRange); /** * The scale factor from data units to visual units @@ -232,7 +233,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The axis length in pixels */ - private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, axisRangeChanged) ; + private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, invalidateAxisRange) ; boolean updateDisplayRange = false; @@ -269,55 +270,55 @@ public void set(final double newValue) { /** * StringConverter used to format tick mark labels. If null a default will be used */ - private final transient ObjectProperty> tickLabelFormatter = PropUtil.createObjectProperty(this, "tickLabelFormatter", null, state.onAction(ChartBits.AxisRange, ChartBits.AxisTickFormatter)); + private final transient ObjectProperty> tickLabelFormatter = PropUtil.createObjectProperty(this, "tickLabelFormatter", null, invalidateTickFormatter); /** * The length of minor tick mark lines. Set to 0 to not display minor tick marks. */ - private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, layoutChangedAction); + private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, invalidateLayout); /** * The number of minor tick divisions to be displayed between each major tick mark. The number of actual minor tick * marks will be one less than this. N.B. number of divisions, minor tick mark is not drawn if minorTickMark == * majorTickMark */ - private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, layoutChangedAction); + private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, invalidateLayout); protected boolean isInvertedAxis = false; // internal use (for performance reason) - private final transient BooleanProperty invertAxis = PropUtil.createBooleanProperty(this, "invertAxis", isInvertedAxis, ()->{ + private final transient BooleanProperty invertAxis = PropUtil.createBooleanProperty(this, "invertAxis", isInvertedAxis, () -> { isInvertedAxis = invertAxisProperty().get(); - layoutChangedAction.run(); + invalidateLayout.run(); }); protected boolean isTimeAxis = false; // internal use (for performance reasons) - private final transient BooleanProperty timeAxis = PropUtil.createBooleanProperty(this, "timeAxis", isTimeAxis, ()->{ + private final transient BooleanProperty timeAxis = PropUtil.createBooleanProperty(this, "timeAxis", isTimeAxis, () -> { isTimeAxis = timeAxisProperty().get(); if (isTimeAxis) { setMinorTickCount(0); } else { setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); } - layoutChangedAction.run(); + invalidateLayout.run(); }); - private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, axisRangeChanged); + private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, invalidateAxisRange); - private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0, axisRangeChanged); + private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0, invalidateAxisRange); /** * The axis unit label */ - private final transient StyleableStringProperty axisUnit = CSS.createStringProperty(this, "axisUnit", "", axisNameChangedAction); + private final transient StyleableStringProperty axisUnit = CSS.createStringProperty(this, "axisUnit", "", invalidateAxisName); /** * The axis unit label */ - private final transient BooleanProperty autoUnitScaling = PropUtil.createBooleanProperty(this, "autoUnitScaling", false, axisNameChangedAction); + private final transient BooleanProperty autoUnitScaling = PropUtil.createBooleanProperty(this, "autoUnitScaling", false, invalidateAxisName); /** * The axis unit label */ - private final transient DoubleProperty unitScaling = PropUtil.createDoubleProperty(this, "unitScaling", 1.0, axisNameChangedAction); + private final transient DoubleProperty unitScaling = PropUtil.createDoubleProperty(this, "unitScaling", 1.0, invalidateAxisName); /** * user-set tick units when doing auto-ranging @@ -326,7 +327,7 @@ public void set(final double newValue) { if (isAutoRanging() || isAutoGrowRanging()) { return; } - axisNameChangedAction.run(); + invalidateAxisName.run(); }); /** @@ -344,6 +345,9 @@ public AbstractAxisParameter() { tickLabelStyle.fontProperty().bindBidirectional(tickLabelFont); tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill); axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD + + // Anything that changes the visible range also changes the Canvas + PropUtil.addChangeListener(invalidateCanvas, minProp, maxProp, length, scale, tickUnit); } @Override @@ -1005,7 +1009,7 @@ public boolean setMax(final double value) { } boolean changed = getUserRange().setMax(value); if (changed) { - axisRangeChanged.run(); + invalidateAxisRange.run(); } setAutoRanging(false); setAutoGrowRanging(false); @@ -1019,7 +1023,7 @@ public boolean setMin(final double value) { } boolean changed = getUserRange().setMin(value); if (changed) { - axisRangeChanged.run(); + invalidateAxisRange.run(); } setAutoRanging(false); setAutoGrowRanging(false); @@ -1036,13 +1040,11 @@ protected void setDisplayedRange(AxisRange range) { protected void setDisplayedRange(double min, double max, double axisLength, double scale, double unit) { updateDisplayRange = true; - if (PropUtil.set(this.minProp, min) - | PropUtil.set(this.maxProp, max) - | PropUtil.set(this.length, axisLength) - | PropUtil.set(this.scale, scale) - | PropUtil.set(this.tickUnit, unit)) { - state.setDirty(ChartBits.AxisCanvas); - } + PropUtil.set(this.minProp, min); + PropUtil.set(this.maxProp, max); + PropUtil.set(this.length, axisLength); + PropUtil.set(this.scale, scale); + PropUtil.set(this.tickUnit, unit); updateDisplayRange = false; } @@ -1295,9 +1297,4 @@ protected static boolean equalString(final String str1, final String str2) { return Objects.equals(str1, str2); } - @Deprecated - protected void invalidate() { - state.setDirty(ChartBits.AxisLayout); - } - } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index 45f7aa4b5..b62640a73 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -47,7 +47,7 @@ public class DefaultNumericAxis extends AbstractAxis implements Axis { private transient AxisTransform axisTransform = linearTransform; protected boolean isUpdating; - private final transient BooleanProperty forceZeroInRange = PropUtil.createBooleanProperty(this, "forceZeroInRange", false, axisRangeChanged); + private final transient BooleanProperty forceZeroInRange = PropUtil.createBooleanProperty(this, "forceZeroInRange", false, invalidateAxisRange); protected boolean isLogAxis = false; // internal use (for performance reason @@ -75,7 +75,7 @@ public class DefaultNumericAxis extends AbstractAxis implements Axis { } } - axisRangeChanged.run(); + invalidateAxisRange.run(); }); /** @@ -336,7 +336,7 @@ public void setForceZeroInRange(final boolean value) { */ public void setLogarithmBase(final double value) { logarithmBaseProperty().set(value); - axisRangeChanged.run(); + invalidateAxisRange.run(); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java index f0bb988da..8001e0955 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java @@ -1,5 +1,7 @@ package io.fair_acc.chartfx.utils; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.beans.property.*; import java.util.Objects; @@ -82,4 +84,38 @@ public void set(final T newValue) { } }; } + + /** + * subscribes to property changes without boxing values + */ + public static void addChangeListener(Runnable action, ReadOnlyDoubleProperty... conditions){ + for (var condition : conditions) { + condition.addListener(new InvalidationListener() { + double prev = condition.get(); + @Override + public void invalidated(Observable observable) { + if(prev != condition.get()) { + prev = condition.get(); + action.run(); + } + } + }); + } + } + + public static void addChangeListener(Runnable action, ReadOnlyLongProperty... conditions){ + for (var condition : conditions) { + condition.addListener(new InvalidationListener() { + long prev = condition.get(); + @Override + public void invalidated(Observable observable) { + if(prev != condition.get()) { + prev = condition.get(); + action.run(); + } + } + }); + } + } + } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index 70412002b..d47f28cf4 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -77,8 +77,6 @@ void testBasicGetterSetters() { state.clear(ChartBits.AxisLabelText); }); axis.updateDisplayRange = true; - axis.minProp.addListener(state.onPropChange(ChartBits.AxisCanvas)::set); - axis.maxProp.addListener(state.onPropChange(ChartBits.AxisCanvas)::set); assertTrue(state.isDirty()); state.clear(); From c28f576b79cbf9591f8e297ee140469fa154db25 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 19:11:55 +0200 Subject: [PATCH 11/81] changed set range behavior to match current implementation --- .../axes/spi/AbstractAxisParameter.java | 85 +++++-------------- .../axes/spi/AbstractAxisParameterTests.java | 14 ++- 2 files changed, 34 insertions(+), 65 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 9cde671d0..417329d6b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -228,42 +228,22 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The scale factor from data units to visual units */ - private final transient ReadOnlyDoubleWrapper scale = PropUtil.createReadOnlyDoubleWrapper(this, "scale", 1); + private final transient ReadOnlyDoubleWrapper scale = PropUtil.createReadOnlyDoubleWrapper(this, "scale", 1, invalidateCanvas); /** * The axis length in pixels */ private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, invalidateAxisRange) ; - boolean updateDisplayRange = false; - /** * The value for the upper bound of this axis, ie max value. This is automatically set if auto ranging is on. */ - protected final transient ReadOnlyDoubleWrapper maxProp = new ReadOnlyDoubleWrapper(this, "upperBound", DEFAULT_MAX_RANGE) { - @Override - public void set(final double newValue) { - if (updateDisplayRange) { - super.set(newValue); - } else { - setMax(newValue); - } - } - }; + protected final transient ReadOnlyDoubleWrapper maxProp = PropUtil.createReadOnlyDoubleWrapper(this, "upperBound", DEFAULT_MAX_RANGE, invalidateCanvas); /** * The value for the lower bound of this axis, ie min value. This is automatically set if auto ranging is on. */ - protected final transient ReadOnlyDoubleWrapper minProp = new ReadOnlyDoubleWrapper(this, "lowerBound", DEFAULT_MIN_RANGE) { - @Override - public void set(final double newValue) { - if (updateDisplayRange) { - super.set(newValue); - } else { - setMin(newValue); - } - } - }; + protected final transient ReadOnlyDoubleWrapper minProp = PropUtil.createReadOnlyDoubleWrapper(this, "lowerBound", DEFAULT_MIN_RANGE, invalidateCanvas); protected double cachedOffset; // for caching @@ -333,7 +313,7 @@ public void set(final double newValue) { /** * system-set tick units for getting the currently displayed units */ - protected final transient ReadOnlyDoubleWrapper tickUnit = PropUtil.createReadOnlyDoubleWrapper(this, "tickUnit", Double.NaN); + protected final transient ReadOnlyDoubleWrapper tickUnit = PropUtil.createReadOnlyDoubleWrapper(this, "tickUnit", Double.NaN, invalidateCanvas); /** * Create a auto-ranging AbstractAxisParameter @@ -346,8 +326,15 @@ public AbstractAxisParameter() { tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill); axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD - // Anything that changes the visible range also changes the Canvas - PropUtil.addChangeListener(invalidateCanvas, minProp, maxProp, length, scale, tickUnit); + // We need to keep the user range in sync with what is being displayed, and also + // react in case users set the range via set(min, max). Note that this will not + // trigger if the properties are set during layout because the value would either + // be auto-ranging or be set to the same value as the user range. + PropUtil.addChangeListener(() -> { + if (getUserRange().set(getMin(), getMax()) && !isAutoGrowRanging() && !isAutoRanging()) { + invalidateAxisRange.run(); + } + }, minProp, maxProp); } @Override @@ -933,7 +920,7 @@ public void setAnimationDuration(final int value) { */ @Override public void setAutoGrowRanging(final boolean state) { - autoRangingProperty().set(state); + autoGrowRangingProperty().set(state); } /** @@ -986,15 +973,7 @@ public void setAxisPadding(final double value) { * @param unit major tick unit */ protected void setTickUnit(final double unit) { - if (updateDisplayRange) { - PropUtil.set(tickUnit, unit); - return; - } - if (getUserTickUnit() != unit) { - setUserTickUnit(unit); - } - setAutoRanging(false); - setAutoGrowRanging(false); + tickUnit.set(unit); } @Override @@ -1004,30 +983,12 @@ public void setUserTickUnit(final double unit) { @Override public boolean setMax(final double value) { - if (updateDisplayRange) { - return PropUtil.set(maxProp, value); - } - boolean changed = getUserRange().setMax(value); - if (changed) { - invalidateAxisRange.run(); - } - setAutoRanging(false); - setAutoGrowRanging(false); - return changed; + return PropUtil.set(maxProp, value); } @Override public boolean setMin(final double value) { - if (updateDisplayRange) { - return PropUtil.set(minProp, value); - } - boolean changed = getUserRange().setMin(value); - if (changed) { - invalidateAxisRange.run(); - } - setAutoRanging(false); - setAutoGrowRanging(false); - return changed; + return PropUtil.set(minProp, value); } protected void setLength(final double axisLength) { @@ -1039,13 +1000,11 @@ protected void setDisplayedRange(AxisRange range) { } protected void setDisplayedRange(double min, double max, double axisLength, double scale, double unit) { - updateDisplayRange = true; - PropUtil.set(this.minProp, min); - PropUtil.set(this.maxProp, max); - PropUtil.set(this.length, axisLength); - PropUtil.set(this.scale, scale); - PropUtil.set(this.tickUnit, unit); - updateDisplayRange = false; + setMin(min); + setMax(max); + setLength(axisLength); + setScale(scale); + setTickUnit(unit); } public void setMaxMajorTickLabelCount(final int value) { diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index d47f28cf4..c1ed22f63 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -28,7 +28,18 @@ class AbstractAxisParameterTests { @Test void testAutoGetterSetters() { AbstractAxisParameter axis = new EmptyAbstractAxisParameter(); - axis.set(0.0, 10.0); + + assertEquals(-1, axis.getMin()); + assertEquals(+1, axis.getMax()); + assertEquals(Double.NaN, axis.getUserRange().getMin()); + assertEquals(Double.NaN, axis.getUserRange().getMax()); + + axis.set(0.1, 10.0); + + assertEquals(0.1, axis.getMin()); + assertEquals(10, axis.getMax()); + assertEquals(0.1, axis.getUserRange().getMin()); + assertEquals(10, axis.getUserRange().getMax()); assertNull(axis.getRange()); @@ -76,7 +87,6 @@ void testBasicGetterSetters() { axis.updateAxisLabel(); state.clear(ChartBits.AxisLabelText); }); - axis.updateDisplayRange = true; assertTrue(state.isDirty()); state.clear(); From ae92115833491d62b011477a2f2c2568e2d88232 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 19:13:31 +0200 Subject: [PATCH 12/81] fixed a missing cache update --- .../io/fair_acc/chartfx/axes/spi/AbstractAxis.java | 10 ++++++---- .../chartfx/axes/spi/AbstractAxisParameter.java | 1 + .../fair_acc/chartfx/axes/spi/DefaultNumericAxis.java | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 375d12bf1..ce82705e2 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -420,12 +420,14 @@ protected AxisRange autoRange(final double length) { * @return new scale to fit the range from lower bound to upper bound in the given display length */ protected double calculateNewScale(final double length, final double lowerBound, final double upperBound) { + if (length == 0) return -1.0; final double range = upperBound - lowerBound; - if(range == 0 || length == 0) { - return -1.0; + final double scale = (range == 0) ? length : length / range; + if (getSide().isVertical()) { + return isInvertedAxis ? scale : -scale; + } else { + return isInvertedAxis ? -scale : scale; } - final double scale = length / range; - return isInvertedAxis ? -scale : scale; } protected void clearAxisCanvas(final GraphicsContext gc, final double width, final double height) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 417329d6b..5eab0bdf1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -347,6 +347,7 @@ public String getUserAgentStylesheet() { */ protected void updateCachedVariables() { // NOPMD by rstein function can but does not have to be overwritten // called once new axis parameters have been established + cachedOffset = getSide().isHorizontal() ? 0 : getLength(); } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index b62640a73..8e5b27d0c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -550,6 +550,7 @@ protected double computeTickUnit(final double rawTickUnit) { @Override protected void updateCachedVariables() { + super.updateCachedVariables(); if (cache == null) { // lgtm [java/useless-null-check] NOPMD NOSONAR -- called from static initializer return; } From 5822f052eaf91e69195864d0a124db979953329d Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 19:22:13 +0200 Subject: [PATCH 13/81] reverted userTickUnit split --- .../java/io/fair_acc/chartfx/axes/Axis.java | 15 ++++---- .../axes/spi/AbstractAxisParameter.java | 35 ++++--------------- .../io/fair_acc/chartfx/plugins/EditAxis.java | 6 ++-- .../renderer/spi/MountainRangeRenderer.java | 2 +- .../chartfx/utils/AxisSynchronizer.java | 4 +-- .../utils/MasterSlaveAxisSynchronizer.java | 4 +-- .../sample/chart/TimeAxisRangeSample.java | 3 +- .../sample/chart/legacy/utils/TestChart.java | 3 +- .../sample/math/DataSetAverageSample.java | 2 +- 9 files changed, 24 insertions(+), 50 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java index 7f9a8dc04..1d14afd88 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java @@ -1,6 +1,11 @@ package io.fair_acc.chartfx.axes; -import javafx.beans.property.*; +import java.util.List; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.StringProperty; import javafx.collections.ObservableList; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; @@ -145,8 +150,6 @@ public interface Axis extends AxisDescription { double getTickUnit(); - double getUserTickUnit(); - /** * @return axis primary unit scaling */ @@ -308,7 +311,7 @@ default void invokeListener(final UpdateEvent updateEvent, final boolean execute void setSide(Side newSide); - void setUserTickUnit(double tickUnit); + void setTickUnit(double tickUnit); /** * This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition @@ -334,9 +337,7 @@ default void invokeListener(final UpdateEvent updateEvent, final boolean execute ObjectProperty sideProperty(); - ReadOnlyDoubleProperty tickUnitProperty(); - - DoubleProperty userTickUnitProperty(); + DoubleProperty tickUnitProperty(); /** * This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 5eab0bdf1..9bfc9db1c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -301,20 +301,12 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { private final transient DoubleProperty unitScaling = PropUtil.createDoubleProperty(this, "unitScaling", 1.0, invalidateAxisName); /** - * user-set tick units when doing auto-ranging + * The tick units, i.e., the spacing between the ticks in real units */ - protected final transient StyleableDoubleProperty userTickUnit = CSS.createDoubleProperty(this, "userTickUnit", DEFAULT_TICK_UNIT, () -> { - if (isAutoRanging() || isAutoGrowRanging()) { - return; - } - invalidateAxisName.run(); + protected final transient StyleableDoubleProperty tickUnit = CSS.createDoubleProperty(this, "tickUnit", DEFAULT_TICK_UNIT, () -> { + ((isAutoRanging() || isAutoGrowRanging()) ? invalidateAxisRange : invalidateCanvas).run(); }); - /** - * system-set tick units for getting the currently displayed units - */ - protected final transient ReadOnlyDoubleWrapper tickUnit = PropUtil.createReadOnlyDoubleWrapper(this, "tickUnit", Double.NaN, invalidateCanvas); - /** * Create a auto-ranging AbstractAxisParameter */ @@ -685,11 +677,6 @@ public double getTickUnit() { return tickUnitProperty().get(); } - @Override - public double getUserTickUnit() { - return userTickUnitProperty().get(); - } - @Override public String getUnit() { return unitProperty().get(); @@ -847,8 +834,8 @@ public BooleanProperty minorTickVisibleProperty() { * @return tickUnit property */ @Override - public ReadOnlyDoubleProperty tickUnitProperty() { - return tickUnit.getReadOnlyProperty(); + public DoubleProperty tickUnitProperty() { + return tickUnit; } @Override @@ -973,15 +960,10 @@ public void setAxisPadding(final double value) { * * @param unit major tick unit */ - protected void setTickUnit(final double unit) { + public void setTickUnit(final double unit) { tickUnit.set(unit); } - @Override - public void setUserTickUnit(final double unit) { - userTickUnit.set(unit); - } - @Override public boolean setMax(final double value) { return PropUtil.set(maxProp, value); @@ -1151,11 +1133,6 @@ public BooleanProperty tickMarkVisibleProperty() { return tickMarkVisible; } - @Override - public DoubleProperty userTickUnitProperty() { - return userTickUnit; - } - /** * This is {@code true} when the axis labels and data point should be plotted according to some time-axis definition * diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java index 2ee162c42..d7030115d 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditAxis.java @@ -310,8 +310,7 @@ private void changeAxisRangeLimit(final Axis axis, final boolean isHorizontal, f if (axis instanceof AbstractAxis) { // ((AbstractAxis) axis).recomputeTickMarks(); - ((AbstractAxis) axis).setAutoRanging(false); // TODO: this used to set internal units. Is this ok? - axis.setUserTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); + axis.setTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); } } @@ -426,8 +425,7 @@ private TextField getBoundField(final Axis axis, final boolean isLowerBound) { if (axis instanceof AbstractAxis) { // ((AbstractAxis) axis).recomputeTickMarks(); - ((AbstractAxis) axis).setAutoRanging(false); // TODO: this used to set internal units. Is this ok? - axis.setUserTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); + axis.setTickUnit(((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); if (LOGGER.isDebugEnabled()) { LOGGER.debug("recompute axis tick unit to {}", ((AbstractAxis) axis).computePreferredTickUnit(axis.getLength())); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java index a0b9708cb..d10a88394 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java @@ -108,7 +108,7 @@ public List render(final GraphicsContext gc, final Chart chart, final i yAxis.setAutoRanging(false); yAxis.setMin(zRangeMin); yAxis.setMax(max); - yAxis.setUserTickUnit(Math.abs(max - zRangeMin) / 10.0); + yAxis.setTickUnit(Math.abs(max - zRangeMin) / 10.0); yAxis.forceRedraw(); } yAxis.setAutoRanging(autoRange); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java index 8e8127a1d..63054eda2 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/AxisSynchronizer.java @@ -69,7 +69,7 @@ private void lowerBoundChanged(ObservableValue property, Numbe axis.setMin(value); axis.setAutoRanging(false); } - axis.setUserTickUnit(tickUnit); + axis.setTickUnit(tickUnit); } updating = false; } @@ -102,7 +102,7 @@ private void upperBoundChanged(ObservableValue property, Numbe axis.setAutoRanging(false); axis.setMax(value); } - axis.setUserTickUnit(tickUnit); + axis.setTickUnit(tickUnit); } updating = false; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java index 584c798a6..833e4a834 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/MasterSlaveAxisSynchronizer.java @@ -31,7 +31,7 @@ public MasterSlaveAxisSynchronizer(AbstractAxis master) { public void add(AbstractAxis axis) { slaves.add(axis); axis.setAutoRanging(false); - axis.userTickUnitProperty().bind(master.tickUnitProperty()); + axis.tickUnitProperty().bind(master.tickUnitProperty()); } private void lowerBoundChanged(double value) { @@ -46,7 +46,7 @@ private void lowerBoundChanged(double value) { public void remove(AbstractAxis axis) { slaves.remove(axis); - axis.userTickUnitProperty().unbind(); + axis.tickUnitProperty().unbind(); axis.setAutoRanging(true); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java index 57ecb8df8..5bdbe4661 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java @@ -73,8 +73,7 @@ public void run() { xAxisDyn.minProperty().set(now - range); final String text = "actual range [s]: " + String.format("%#.3f", range) + " (" + String.format("%#.1f", range / 3600 / 24) + " days)"; - xAxisDyn.setAutoRanging(false); - xAxisDyn.setUserTickUnit(range / 12); + xAxisDyn.setTickUnit(range / 12); xAxisDyn.forceRedraw(); xAxis9Text.setText(text); }); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java index f95199880..7b0599a1f 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/utils/TestChart.java @@ -86,8 +86,7 @@ public void setNumberOfSamples(int nSamples) { chart.getDatasets().setAll(testFunction); xAxis.setMax(nSamples - 1.0); xAxis.setMin(0); - xAxis.setAutoRanging(false); - xAxis.setUserTickUnit(nSamples / 20.0); + xAxis.setTickUnit(nSamples / 20.0); updateDataSet(); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java index 217b55eb9..631d9b6c2 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/math/DataSetAverageSample.java @@ -37,7 +37,7 @@ public Node getContent() { chart.getYAxis(1).setAutoRanging(false); chart.getYAxis().maxProperty().bindBidirectional(chart.getYAxis(1).maxProperty()); chart.getYAxis().minProperty().bindBidirectional(chart.getYAxis(1).minProperty()); - chart.getYAxis().userTickUnitProperty().bindBidirectional(chart.getYAxis(1).userTickUnitProperty()); // TODO: used tickUnitProperty. Make sure it still works. + chart.getYAxis().tickUnitProperty().bindBidirectional(chart.getYAxis(1).tickUnitProperty()); LimitedQueue lastDataSets = new LimitedQueue<>(N_GRAPHS); for (int i = 0; i < 20 * N_GRAPHS; i++) { From a5839887de47f77f1c56a8900b0667f479ef1d97 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 21:20:33 +0200 Subject: [PATCH 14/81] fixed rendering of scaled axes --- .../chartfx/axes/spi/AbstractAxis.java | 32 ++------- .../axes/spi/AbstractAxisParameter.java | 67 +++++++++---------- .../chartfx/axes/spi/DefaultNumericAxis.java | 12 ---- .../io/fair_acc/chartfx/utils/PropUtil.java | 16 +++-- .../axes/spi/AbstractAxisParameterTests.java | 2 +- 5 files changed, 47 insertions(+), 82 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index ce82705e2..6641e08bf 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -164,11 +164,6 @@ public ObjectProperty axisLabelFormatterProperty() { */ public abstract double computePreferredTickUnit(final double axisLength); - public double computePreferredTickUnit(AxisRange range) { - // TODO: remove the older general one - return computePreferredTickUnit(range.axisLength); - } - @Override public void drawAxis(final GraphicsContext gc, final double axisWidth, final double axisHeight) { if ((gc == null) || (getSide() == null)) { @@ -301,29 +296,15 @@ protected void updateAxisRange() { return; } - // Update range & scale - final AxisRange range; - if (isAutoGrowRanging() || isAutoRanging()) { - range = autoRange(length); // derived axes may potentially pad and round limits - } else { - range = getUserRange(); - } - - range.axisLength = length; - range.scale = calculateNewScale(length, range.getMin(), range.getMax()); - range.tickUnit =/* (!isAutoRanging() && !isAutoRanging()) ? getUserTickUnit() :*/ computePreferredTickUnit(range); // TODO: does not work in zoom - - // Scale the units // TODO: actually scale - double unitScale = computeUnitScale(range); - setUnitScaling(unitScale); + // Update range & scale TODO: what is already set? the original implementation updates in many different ways + AxisRange range = getRange(); + set(range.getMin(), range.getMax()); + setScale(range.scale = calculateNewScale(length, getMin(), getMax())); + setTickUnit(range.tickUnit = computePreferredTickUnit(length)); - // Displayed label & units - if (state.isDirty(ChartBits.AxisLabelText)) { - updateAxisLabel(); - } + updateAxisLabelAndUnit(); // Update cache to have axis transforms - setDisplayedRange(range); updateCachedVariables(); // Tick marks @@ -943,6 +924,7 @@ public void drawAxis() { } finally { gc.translate(-canvasPadX, -canvasPadY); } + } // everything is updated diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 9bfc9db1c..89afde138 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -301,7 +301,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { private final transient DoubleProperty unitScaling = PropUtil.createDoubleProperty(this, "unitScaling", 1.0, invalidateAxisName); /** - * The tick units, i.e., the spacing between the ticks in real units + * The tick units (spacing between the ticks in real units) */ protected final transient StyleableDoubleProperty tickUnit = CSS.createDoubleProperty(this, "tickUnit", DEFAULT_TICK_UNIT, () -> { ((isAutoRanging() || isAutoGrowRanging()) ? invalidateAxisRange : invalidateCanvas).run(); @@ -978,18 +978,6 @@ protected void setLength(final double axisLength) { this.length.set(axisLength); } - protected void setDisplayedRange(AxisRange range) { - setDisplayedRange(range.getMin(), range.getMax(), range.getAxisLength(), range.getScale(), range.getTickUnit()); - } - - protected void setDisplayedRange(double min, double max, double axisLength, double scale, double unit) { - setMin(min); - setMax(max); - setLength(axisLength); - setScale(scale); - setTickUnit(unit); - } - public void setMaxMajorTickLabelCount(final int value) { this.maxMajorTickLabelCountProperty().set(value); } @@ -1182,41 +1170,46 @@ protected void updateScale() { setScale(newScale == 0 ? -1.0 : newScale); } - protected void updateAxisLabel() { + public BitState getBitState() { + return state; + } + + protected void updateAxisLabelAndUnit() { final String axisPrimaryLabel = getName(); - String unitString = getUnit(); + String localAxisUnit = getUnit(); + final boolean isAutoScaling = isAutoUnitScaling(); + if (isAutoScaling) { + updateScaleAndUnitPrefix(); + } - // Is it correct that we scale even if there is no unit? - final String scalePrefix = MetricPrefix.getShortPrefix(getUnitScaling()); - if ((unitString == null || unitString.isBlank()) && !scalePrefix.isBlank()) { - unitString = ""; + if (!state.isDirty(ChartBits.AxisLabelText)) { + return; + } + + final String axisPrefix = MetricPrefix.getShortPrefix(getUnitScaling()); + if ((localAxisUnit == null || localAxisUnit.isBlank()) && !axisPrefix.isBlank()) { + localAxisUnit = ""; } - if (unitString == null) { + if (localAxisUnit == null) { getAxisLabel().setText(axisPrimaryLabel); } else { - getAxisLabel().setText(axisPrimaryLabel + " [" + scalePrefix + unitString + "]"); + getAxisLabel().setText(axisPrimaryLabel + " [" + axisPrefix + localAxisUnit + "]"); } } - public BitState getBitState() { - return state; - } - - protected double computeUnitScale(AxisRange axisRange) { + protected void updateScaleAndUnitPrefix() { + final double range = Math.abs(getMax() - getMin()); + final double logRange = Math.log10(range); + final double power3Upper = 3.0 * Math.ceil(logRange / 3.0); + final double power3Lower = 3.0 * Math.floor(logRange / 3.0); + final double a = Math.min(power3Upper, power3Lower); + final double power = Math.pow(10, a); final double oldPower = getUnitScaling(); - if (isAutoUnitScaling()) { - final double range = Math.abs(axisRange.getLength()); - final double logRange = Math.log10(range); - final double power3Upper = 3.0 * Math.ceil(logRange / 3.0); - final double power3Lower = 3.0 * Math.floor(logRange / 3.0); - final double a = Math.min(power3Upper, power3Lower); - final double power = Math.pow(10, a); - if ((power != oldPower) && (power != 0) && (Double.isFinite(power))) { - return power; - } + if ((power != oldPower) && (power != 0) && (Double.isFinite(power))) { + this.setUnitScaling(power); } - return oldPower; + setTickUnit(range / getMinorTickCount()); } ReadOnlyDoubleWrapper scalePropertyImpl() { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index 8e5b27d0c..ff9e4162b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -158,18 +158,6 @@ public double computePreferredTickUnit(final double axisLength) { return computeTickUnit(rawTickUnit); } - @Override - public double computePreferredTickUnit(AxisRange range) { - final double labelSize = getTickLabelFont().getSize() * 2; - final int numOfFittingLabels = (int) Math.floor(range.axisLength / labelSize); - final int numOfTickMarks = Math.max(Math.min(numOfFittingLabels, getMaxMajorTickLabelCount()), 2); - double rawTickUnit = (range.getMax() - range.getMin()) / numOfTickMarks; - if (rawTickUnit == 0 || Double.isNaN(rawTickUnit)) { - rawTickUnit = 1e-3; // TODO: remove this hack (eventually) ;-) - } - return computeTickUnit(rawTickUnit); - } - /** * When {@code true} zero is always included in the visible range. This only has effect if * {@link #autoRangingProperty() auto-ranging} is on. diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java index 8001e0955..a6a14f087 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java @@ -14,7 +14,7 @@ public class PropUtil { public static boolean set(DoubleProperty prop, double value) { - if (prop.get() == value) { + if (isEqual(prop.get(), value)) { return false; } prop.set(value); @@ -46,8 +46,7 @@ public static DoubleProperty createDoubleProperty(Object bean, String name, doub return new SimpleDoubleProperty(bean, name, initial) { @Override public void set(final double newValue) { - final double oldValue = get(); - if (oldValue != newValue) { + if (!isEqual(get(), newValue)) { super.set(newValue); onChange.run(); } @@ -59,8 +58,7 @@ public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, Str return new ReadOnlyDoubleWrapper(bean, name, initial) { @Override public void set(final double newValue) { - final double oldValue = get(); - if (oldValue != newValue) { + if (!isEqual(get(), newValue)) { super.set(newValue); onChange.run(); } @@ -77,7 +75,7 @@ public static ObjectProperty createObjectProperty(Object bean, String nam @Override public void set(final T newValue) { final T oldValue = getValue(); - if (oldValue != newValue) { + if (!Objects.equals(oldValue, newValue)) { super.set(newValue); onChange.run(); } @@ -94,7 +92,7 @@ public static void addChangeListener(Runnable action, ReadOnlyDoubleProperty... double prev = condition.get(); @Override public void invalidated(Observable observable) { - if(prev != condition.get()) { + if (!isEqual(prev, condition.get())) { prev = condition.get(); action.run(); } @@ -118,4 +116,8 @@ public void invalidated(Observable observable) { } } + private static boolean isEqual(double a, double b) { + return Double.doubleToLongBits(a) == Double.doubleToLongBits(b); // supports NaN + } + } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index c1ed22f63..0a8128d9a 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -84,7 +84,7 @@ void testBasicGetterSetters() { var state = axis.getBitState(); state.addChangeListener(ChartBits.AxisLabelText, (src, bits) -> { axis.updateScale(); - axis.updateAxisLabel(); + axis.updateAxisLabelAndUnit(); state.clear(ChartBits.AxisLabelText); }); From aa1aa3af7834d934f9b11193873ada1dd39dd6f6 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 22 Jul 2023 21:54:59 +0200 Subject: [PATCH 15/81] added event debugging info --- .../io/fair_acc/dataset/events/BitState.java | 65 ++++++++++++++++++- .../io/fair_acc/dataset/events/ChartBits.java | 33 +--------- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index d87652c12..c97fabc30 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import java.util.function.IntSupplier; /** @@ -212,6 +213,68 @@ private static boolean isEqual(StateListener internal, StateListener listener) { return false; } + public static boolean isSet(int mask, int bit) { + return (bit & mask) != 0; + } + + public static int clear(int mask, int bit) { + return mask & ~bit; + } + + public static StateListener createDebugPrinter(IntSupplier... bits) { + return createDebugPrinter(false, bits); + } + + public static StateListener createDebugPrinter(boolean showStackTrace, IntSupplier... bits) { + return createDebugPrinter(showStackTrace, System.out::println, bits); + } + + public static StateListener createDebugPrinter(boolean showStackTrace, Consumer log, IntSupplier... bits) { + StringBuilder builder = new StringBuilder(); + return (source, mask) -> { + builder.setLength(0); + builder.append(source.getSource()).append(": "); + appendBitStrings(builder, mask, bits); + if (showStackTrace) { + appendStackTrace(builder, 6, 15); // offset to account for internal methods + } + log.accept(builder.toString()); + }; + } + + public static StringBuilder appendBitStrings(StringBuilder builder, int mask, IntSupplier... bits) { + if (mask == 0) { + return builder.append("clean"); + } + int knownBits = 0; + builder.append("dirty["); + for (IntSupplier name : bits) { + int bit = name.getAsInt(); + if (isSet(mask, bit)) { + builder.append(name).append(", "); + knownBits |= bit; + } + } + if (clear(mask, knownBits) != 0) { + builder.append("UNKNOWN, "); + } + builder.setLength(builder.length() - 2); + return builder.append("]"); + } + + public static StringBuilder appendStackTrace(StringBuilder builder, int from, int to) { + var stack = Thread.currentThread().getStackTrace(); + for (int i = from; i < Math.min(stack.length, to); i++) { + var elem = stack[i]; + builder.append("\n [").append(i).append("] ") + .append(elem.getClassName()).append(".").append(elem.getMethodName()) + .append("(") + .append(elem.getFileName()).append(":").append(elem.getLineNumber()) + .append(")"); + } + return builder; + } + private static class FilteredListener implements StateListener { private FilteredListener(int filter, StateListener listener) { @@ -222,7 +285,7 @@ private FilteredListener(int filter, StateListener listener) { @Override public void accept(BitState source, int bits) { final int filteredBits = filter & bits; - if(filteredBits != 0){ + if (filteredBits != 0) { listener.accept(source, filteredBits); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index fd2b01288..07e3a407e 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -12,8 +12,7 @@ public enum ChartBits implements IntSupplier { AxisTickFormatter, // tick label formatting AxisLabelText; // display name or units - private static final ChartBits[] knownBits = ChartBits.values(); - public static final int KnownMask = BitState.mask(knownBits); + public static final int KnownMask = BitState.mask(ChartBits.values()); public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickFormatter, AxisLabelText); @Override @@ -22,35 +21,7 @@ public int getAsInt() { } public boolean isSet(int mask) { - return (bit & mask) != 0; - } - - private int clear(int mask) { - return mask & ~bit; - } - - public static StateListener printer() { - return (source, bits) -> System.out.println(toString(source, bits)); - } - - public static String toString(BitState bitState, int bits) { - StringBuilder builder = new StringBuilder(); - builder.append(bitState.getSource()).append(" "); - if(bits == 0) { - return builder.append("clean").toString(); - } - builder.append("dirty["); - for (ChartBits bit : knownBits) { - if(bit.isSet(bits)) { - builder.append(bit.name()).append(", "); - bits = bit.clear(bits); - } - } - if (bits != 0) { - builder.append("UNKNOWN, "); - } - builder.setLength(builder.length() - 2); - return builder.append("]").toString(); + return BitState.isSet(mask, bit); } final int bit = 1 << ordinal(); From b332ed0e557eaefd6da1aa2f3595df8ca866fe6b Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 23 Jul 2023 00:34:28 +0200 Subject: [PATCH 16/81] cleaned up property overview --- .../chartfx/axes/spi/AbstractAxis.java | 21 +- .../axes/spi/AbstractAxisParameter.java | 193 +++++++++++++----- .../io/fair_acc/chartfx/utils/PropUtil.java | 84 +++++--- .../io/fair_acc/dataset/events/BitState.java | 4 + .../io/fair_acc/dataset/events/ChartBits.java | 6 + 5 files changed, 211 insertions(+), 97 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 6641e08bf..76e42f2d5 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -113,11 +113,6 @@ protected AbstractAxis() { } }); - // Forward axis layouts to the JavaFX layout bit - state.addChangeListener(ChartBits.AxisLayout, (source, bits) -> { - super.requestLayout(); - }); - // set default axis title/label alignment updateTickLabelAlignment(); updateAxisLabelAlignment(); @@ -290,13 +285,13 @@ public boolean isValueOnAxis(final double value) { * Updates the contents for this axis, e.g., tick labels, spacing * range, caches, etc. */ - protected void updateAxisRange() { - final double length = getLength(); - if (!Double.isFinite(length) || length <= 0 || state.isClean(ChartBits.AxisRange)) { + protected void updateAxisRange(double length) { + if (length == getLength() && state.isClean(ChartBits.AxisRange)) { return; } // Update range & scale TODO: what is already set? the original implementation updates in many different ways + setLength(length); AxisRange range = getRange(); set(range.getMin(), range.getMax()); setScale(range.scale = calculateNewScale(length, getMin(), getMax())); @@ -458,11 +453,9 @@ private double computePrefSize(final double axisLength) { return computeMinSize(); } - // Set the axis length to determine the actual ticks. We can - // cache the existing layout if nothing has changed. + // We can cache the existing layout if nothing has changed. final boolean isHorizontal = getSide().isHorizontal(); - setLength(axisLength); - if (state.isClean(ChartBits.AxisLayout)) { + if (getLength() == axisLength && state.isClean(ChartBits.AxisLayout)) { return isHorizontal ? getWidth() : getHeight(); } @@ -470,7 +463,7 @@ private double computePrefSize(final double axisLength) { // overlap. The initial estimate is usually correct, so later changes // happen very rarely, e.g., at a point where y axes labels switch to // shifting lines. - updateAxisRange(); + updateAxisRange(axisLength); scaleFont = 1.0; maxLabelHeight = 0; @@ -911,7 +904,7 @@ public void drawAxis() { } // update labels, tick marks etc. - updateAxisRange(); + updateAxisRange(getLength()); // redraw outdated canvas if (state.isDirty(ChartBits.AxisCanvas)) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 89afde138..012814a1c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -44,11 +44,11 @@ */ public abstract class AbstractAxisParameter extends Pane implements Axis { protected final BitState state = BitState.initDirty(this, ChartBits.AxisMask); - protected final Runnable invalidateLayout = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas); - protected final Runnable invalidateAxisRange = state.onAction(ChartBits.AxisRange); - protected final Runnable invalidateTickFormatter = state.onAction(ChartBits.AxisRange, ChartBits.AxisTickFormatter); - protected final Runnable invalidateCanvas = state.onAction(ChartBits.AxisCanvas); - protected final Runnable invalidateAxisName = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas, ChartBits.AxisLabelText); + protected final Runnable invalidateLayout; + protected final Runnable invalidateAxisRange; + protected final Runnable invalidateTickFormatter; + protected final Runnable invalidateCanvas; + protected final Runnable invalidateAxisName; private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Region.getClassCssMetaData()); @@ -64,7 +64,7 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); - private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1, invalidateLayout); + private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1); /** * Nodes used for css-type styling. Not used for actual drawing. Used as a storage container for the settings @@ -80,12 +80,6 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { { StyleUtil.addStyles(this, "axis"); getChildren().addAll(axisLabel, tickLabelStyle, majorTickStyle, minorTickStyle); - PropUtil.addChangeListener(invalidateLayout, - majorTickStyle.changeCounterProperty(), - tickLabelStyle.changeCounterProperty(), - axisLabel.changeCounterProperty()); - PropUtil.addChangeListener(invalidateCanvas, - minorTickStyle.changeCounterProperty()); // not used for layout calculation } // TODO: replace with primitive DoubleArrayList and specialized TickMarkList @@ -108,50 +102,48 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { * The side of the plot which this axis is being drawn on default axis orientation is BOTTOM, can be set latter to * another side */ - private final transient StyleableObjectProperty side = CSS.createEnumPropertyWithPseudoclasses(this, "side", Side.BOTTOM, false, Side.class, null, () -> { - ChartPane.setSide(this, getSide()); - invalidateLayout.run(); - }); + private final transient StyleableObjectProperty side = CSS.createEnumPropertyWithPseudoclasses(this, "side", Side.BOTTOM, false, Side.class, null, + () -> ChartPane.setSide(this, getSide())); /** * The side of the plot which this axis is being drawn on */ - private final transient StyleableObjectProperty overlapPolicy = CSS.createObjectProperty(this, "overlapPolicy", AxisLabelOverlapPolicy.SKIP_ALT, StyleConverter.getEnumConverter(AxisLabelOverlapPolicy.class), invalidateLayout); + private final transient StyleableObjectProperty overlapPolicy = CSS.createObjectProperty(this, "overlapPolicy", AxisLabelOverlapPolicy.SKIP_ALT, StyleConverter.getEnumConverter(AxisLabelOverlapPolicy.class)); /** * The relative alignment (N.B. clamped to [0.0,1.0]) of the axis if drawn on top of the main canvas (N.B. side == CENTER_HOR or CENTER_VER */ - private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0)), invalidateLayout); + private final transient StyleableDoubleProperty axisCenterPosition = CSS.createDoubleProperty(this, "axisCenterPosition", 0.5, true, (oldVal, newVal) -> Math.max(0.0, Math.min(newVal, 1.0))); /** * axis label alignment */ - private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class), invalidateLayout); + private final transient StyleableObjectProperty axisLabelTextAlignment = CSS.createObjectProperty(this, "axisLabelTextAlignment", TextAlignment.CENTER, StyleConverter.getEnumConverter(TextAlignment.class)); /** * The axis label */ - private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", "", invalidateAxisName); + private final transient StyleableStringProperty axisName = CSS.createStringProperty(this, "axisName", ""); /** * true if tick marks should be displayed */ - private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true, invalidateLayout); + private final transient StyleableBooleanProperty tickMarkVisible = CSS.createBooleanProperty(this, "tickMarkVisible", true); /** * true if tick mark labels should be displayed */ - private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true, invalidateLayout); + private final transient StyleableBooleanProperty tickLabelsVisible = CSS.createBooleanProperty(this, "tickLabelsVisible", true); /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0, invalidateLayout); + private final transient StyleableDoubleProperty axisPadding = CSS.createDoubleProperty(this, "axisPadding", 15.0); /** * The length of tick mark lines */ - private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0, invalidateLayout); + private final transient StyleableDoubleProperty tickLength = CSS.createDoubleProperty(this, "tickLength", 8.0); /** * This is true when the axis determines its range from the data automatically @@ -160,20 +152,18 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { if (isAutoRanging()) { setAutoGrowRanging(false); } - invalidateAxisRange.run(); }); private final transient StyleableBooleanProperty autoGrowRanging = CSS.createBooleanProperty(this, "autoGrowRanging", false, () -> { if (isAutoGrowRanging()) { setAutoRanging(false); } - invalidateAxisRange.run(); }); /** * The font for all tick labels */ - private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null, invalidateLayout); + private final transient StyleableObjectProperty tickLabelFont = CSS.createObjectProperty(this, "tickLabelFont", Font.font("System", 8), false, StyleConverter.getFontConverter(), null); /** * The fill for all tick labels @@ -183,32 +173,32 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * The gap between tick marks and the canvas area */ - private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0, invalidateLayout); + private final transient StyleableDoubleProperty tickMarkGap = CSS.createDoubleProperty(this, "tickMarkGap", 0.0); /** * The gap between tick labels and the tick mark lines */ - private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0, invalidateLayout); + private final transient StyleableDoubleProperty tickLabelGap = CSS.createDoubleProperty(this, "tickLabelGap", 3.0); /** * The minimum gap between tick labels */ - private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0, invalidateAxisRange); + private final transient StyleableDoubleProperty tickLabelSpacing = CSS.createDoubleProperty(this, "tickLabelSpacing", 3.0); /** * The gap between tick labels and the axis label */ - private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0, invalidateLayout); + private final transient StyleableDoubleProperty axisLabelGap = CSS.createDoubleProperty(this, "axisLabelGap", 3.0); /** * The animation duration in MS */ - private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250, invalidateLayout); + private final transient StyleableIntegerProperty animationDuration = CSS.createIntegerProperty(this, "animationDuration", 250); /** * The maximum number of ticks */ - private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT, invalidateAxisRange); + private final transient StyleableIntegerProperty maxMajorTickLabelCount = CSS.createIntegerProperty(this, "maxMajorTickLabelCount", MAX_TICK_COUNT); /** * When true any changes to the axis and its range will be animated. @@ -218,56 +208,55 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { /** * Rotation in degrees of tick mark labels from their normal horizontal. */ - protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0, invalidateLayout); + protected final transient StyleableDoubleProperty tickLabelRotation = CSS.createDoubleProperty(this, "tickLabelRotation", 0.0); /** * true if minor tick marks should be displayed */ - private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true, invalidateAxisRange); + private final transient StyleableBooleanProperty minorTickVisible = CSS.createBooleanProperty(this, "minorTickVisible", true); /** * The scale factor from data units to visual units */ - private final transient ReadOnlyDoubleWrapper scale = PropUtil.createReadOnlyDoubleWrapper(this, "scale", 1, invalidateCanvas); + private final transient ReadOnlyDoubleWrapper scale = PropUtil.createReadOnlyDoubleWrapper(this, "scale", 1); /** * The axis length in pixels */ - private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN, invalidateAxisRange) ; + private final transient ReadOnlyDoubleWrapper length = PropUtil.createReadOnlyDoubleWrapper(this, "length", Double.NaN) ; /** * The value for the upper bound of this axis, ie max value. This is automatically set if auto ranging is on. */ - protected final transient ReadOnlyDoubleWrapper maxProp = PropUtil.createReadOnlyDoubleWrapper(this, "upperBound", DEFAULT_MAX_RANGE, invalidateCanvas); + protected final transient ReadOnlyDoubleWrapper maxProp = PropUtil.createReadOnlyDoubleWrapper(this, "upperBound", DEFAULT_MAX_RANGE); /** * The value for the lower bound of this axis, ie min value. This is automatically set if auto ranging is on. */ - protected final transient ReadOnlyDoubleWrapper minProp = PropUtil.createReadOnlyDoubleWrapper(this, "lowerBound", DEFAULT_MIN_RANGE, invalidateCanvas); + protected final transient ReadOnlyDoubleWrapper minProp = PropUtil.createReadOnlyDoubleWrapper(this, "lowerBound", DEFAULT_MIN_RANGE); protected double cachedOffset; // for caching /** * StringConverter used to format tick mark labels. If null a default will be used */ - private final transient ObjectProperty> tickLabelFormatter = PropUtil.createObjectProperty(this, "tickLabelFormatter", null, invalidateTickFormatter); + private final transient ObjectProperty> tickLabelFormatter = PropUtil.createObjectProperty(this, "tickLabelFormatter", null); /** * The length of minor tick mark lines. Set to 0 to not display minor tick marks. */ - private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0, invalidateLayout); + private final transient StyleableDoubleProperty minorTickLength = CSS.createDoubleProperty(this, "minorTickLength", 5.0); /** * The number of minor tick divisions to be displayed between each major tick mark. The number of actual minor tick * marks will be one less than this. N.B. number of divisions, minor tick mark is not drawn if minorTickMark == * majorTickMark */ - private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10, invalidateLayout); + private final transient StyleableIntegerProperty minorTickCount = CSS.createIntegerProperty(this, "minorTickCount", 10); protected boolean isInvertedAxis = false; // internal use (for performance reason) private final transient BooleanProperty invertAxis = PropUtil.createBooleanProperty(this, "invertAxis", isInvertedAxis, () -> { isInvertedAxis = invertAxisProperty().get(); - invalidateLayout.run(); }); protected boolean isTimeAxis = false; // internal use (for performance reasons) @@ -278,34 +267,31 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { } else { setMinorTickCount(AbstractAxisParameter.DEFAULT_MINOR_TICK_COUNT); } - invalidateLayout.run(); }); - private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false, invalidateAxisRange); + private final transient StyleableBooleanProperty autoRangeRounding = CSS.createBooleanProperty(this, "autoRangeRounding", false); - private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0, invalidateAxisRange); + private final transient DoubleProperty autoRangePadding = PropUtil.createDoubleProperty(this, "autoRangePadding", 0); /** * The axis unit label */ - private final transient StyleableStringProperty axisUnit = CSS.createStringProperty(this, "axisUnit", "", invalidateAxisName); + private final transient StyleableStringProperty axisUnit = CSS.createStringProperty(this, "axisUnit", ""); /** * The axis unit label */ - private final transient BooleanProperty autoUnitScaling = PropUtil.createBooleanProperty(this, "autoUnitScaling", false, invalidateAxisName); + private final transient BooleanProperty autoUnitScaling = PropUtil.createBooleanProperty(this, "autoUnitScaling", false); /** * The axis unit label */ - private final transient DoubleProperty unitScaling = PropUtil.createDoubleProperty(this, "unitScaling", 1.0, invalidateAxisName); + private final transient DoubleProperty unitScaling = PropUtil.createDoubleProperty(this, "unitScaling", 1.0); /** * The tick units (spacing between the ticks in real units) */ - protected final transient StyleableDoubleProperty tickUnit = CSS.createDoubleProperty(this, "tickUnit", DEFAULT_TICK_UNIT, () -> { - ((isAutoRanging() || isAutoGrowRanging()) ? invalidateAxisRange : invalidateCanvas).run(); - }); + protected final transient StyleableDoubleProperty tickUnit = CSS.createDoubleProperty(this, "tickUnit", DEFAULT_TICK_UNIT); /** * Create a auto-ranging AbstractAxisParameter @@ -318,15 +304,116 @@ public AbstractAxisParameter() { tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill); axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD + // Properties that may be relevant to the layout and must always at least redraw the canvas + PropUtil.runOnChange(invalidateLayout = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas), + // distance to main line + side, + axisPadding, + tickMarkGap, + + // tick marks + tickMarkVisible, + tickLength, + majorTickStyle.changeCounterProperty(), + + // tick labels + tickLabelsVisible, + tickLabelGap, + tickLabelRotation, + overlapPolicy, + tickLabelStyle.changeCounterProperty(), + tickLabelFont, // already in style + + // axis label + axisLabelGap, + axisLabel.changeCounterProperty(), + + // not really relevant? + animationDuration, + dimIndex + ); + + // Forward axis layout requests to the JavaFX layout bit + state.addChangeListener(ChartBits.AxisLayout, (source, bits) -> { + super.requestLayout(); + }); + + // Properties that change the placement of ticks + // We can ignore the layout if labels can only move linearly along + // the axis length. This happens e.g. for an X-axis that displays + // moving time with the default policy. + PropUtil.runOnChange(invalidateAxisRange = () -> { + state.setDirty(ChartBits.AxisRange); + if (!isTickMarkVisible() || !isTickLabelsVisible()) { + return; + } + final int rot = Math.abs(((int) getTickLabelRotation()) % 360); + if (getOverlapPolicy() == AxisLabelOverlapPolicy.SHIFT_ALT || getSide() == null + || (getSide().isHorizontal() && !(rot == 0 || rot == 180)) + || (getSide().isVertical() && !(rot == 90 || rot == 270))) { + state.setDirty(ChartBits.AxisLayout); + } + }, + // tick placement + autoRanging, + autoGrowRanging, + autoRangeRounding, + autoRangePadding, + + // tick labels + tickLabelSpacing, + maxMajorTickLabelCount, + invertAxis + ); + + // Properties that require a redraw of the canvas but won't affect the placement of ticks + PropUtil.runOnChange(invalidateCanvas = state.onAction(ChartBits.AxisCanvas), + // minor ticks + minorTickVisible, + minorTickStyle.changeCounterProperty(), // not used for layout calculation + minorTickCount, + minorTickLength, + + // item placement + axisCenterPosition, + axisLabelTextAlignment, + + // the main properties of what the axis currently shows. + // Used for internal computation, so we don't want them to + // trigger more. + minProp, + maxProp, + length, + scale, + tickUnit + ); + + PropUtil.runOnChange(invalidateAxisName = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisLabelText), + axisName, + unitScaling, + autoUnitScaling, + axisUnit + ); + PropUtil.runOnChange(invalidateTickFormatter = state.onAction(ChartBits.AxisRange, ChartBits.AxisTickFormatter), + tickLabelFormatter, + timeAxis // may change the size of the rendered labels? + ); + // We need to keep the user range in sync with what is being displayed, and also // react in case users set the range via set(min, max). Note that this will not // trigger if the properties are set during layout because the value would either // be auto-ranging or be set to the same value as the user range. - PropUtil.addChangeListener(() -> { + PropUtil.runOnChange(() -> { if (getUserRange().set(getMin(), getMax()) && !isAutoGrowRanging() && !isAutoRanging()) { invalidateAxisRange.run(); } }, minProp, maxProp); + PropUtil.runOnChange(() -> { + if (isAutoRanging() || isAutoGrowRanging()) { + invalidateAxisRange.run(); + } // TODO: don't reset during a set? + }, tickUnit); + } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java index a6a14f087..cadf7e318 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java @@ -3,6 +3,10 @@ import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.*; +import javafx.beans.value.ObservableBooleanValue; +import javafx.beans.value.ObservableDoubleValue; +import javafx.beans.value.ObservableIntegerValue; +import javafx.beans.value.ObservableValue; import java.util.Objects; @@ -29,26 +33,30 @@ public static boolean set(StringProperty prop, String value){ return true; } - public static BooleanProperty createBooleanProperty(Object bean, String name, boolean initial, Runnable onChange) { + public static BooleanProperty createBooleanProperty(Object bean, String name, boolean initial, Runnable... onChange) { return new SimpleBooleanProperty(bean, name, initial) { @Override public void set(final boolean newValue) { final boolean oldValue = get(); if (oldValue != newValue) { super.set(newValue); - onChange.run(); + for (Runnable action : onChange) { + action.run(); + } } } }; } - public static DoubleProperty createDoubleProperty(Object bean, String name, double initial, Runnable onChange) { + public static DoubleProperty createDoubleProperty(Object bean, String name, double initial, Runnable... onChange) { return new SimpleDoubleProperty(bean, name, initial) { @Override public void set(final double newValue) { if (!isEqual(get(), newValue)) { super.set(newValue); - onChange.run(); + for (Runnable action : onChange) { + action.run(); + } } } }; @@ -70,49 +78,65 @@ public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, Str return new ReadOnlyDoubleWrapper(bean, name, initial); } - public static ObjectProperty createObjectProperty(Object bean, String name, T initial, Runnable onChange) { + public static ObjectProperty createObjectProperty(Object bean, String name, T initial) { return new SimpleObjectProperty(bean, name, initial) { @Override public void set(final T newValue) { final T oldValue = getValue(); if (!Objects.equals(oldValue, newValue)) { super.set(newValue); - onChange.run(); } } }; } /** - * subscribes to property changes without boxing values + * subscribes to property changes without requiring value boxing */ - public static void addChangeListener(Runnable action, ReadOnlyDoubleProperty... conditions){ + public static void runOnChange(Runnable action, ObservableValue... conditions){ for (var condition : conditions) { - condition.addListener(new InvalidationListener() { - double prev = condition.get(); - @Override - public void invalidated(Observable observable) { - if (!isEqual(prev, condition.get())) { - prev = condition.get(); - action.run(); + if (condition instanceof ObservableDoubleValue) { + var obs = (ObservableDoubleValue) condition; + condition.addListener(new InvalidationListener() { + double prev = obs.get(); + + @Override + public void invalidated(Observable observable) { + if (!isEqual(prev, obs.get())) { + prev = obs.get(); + action.run(); + } } - } - }); - } - } + }); + } else if (condition instanceof ObservableBooleanValue) { + var obs = (ObservableBooleanValue) condition; + condition.addListener(new InvalidationListener() { + boolean prev = obs.get(); - public static void addChangeListener(Runnable action, ReadOnlyLongProperty... conditions){ - for (var condition : conditions) { - condition.addListener(new InvalidationListener() { - long prev = condition.get(); - @Override - public void invalidated(Observable observable) { - if(prev != condition.get()) { - prev = condition.get(); - action.run(); + @Override + public void invalidated(Observable observable) { + if (prev == obs.get()) { + prev = obs.get(); + action.run(); + } } - } - }); + }); + } else if (condition instanceof ObservableIntegerValue) { + var obs = (ObservableIntegerValue) condition; + condition.addListener(new InvalidationListener() { + int prev = obs.get(); + + @Override + public void invalidated(Observable observable) { + if (prev == obs.get()) { + prev = obs.get(); + action.run(); + } + } + }); + } else { + condition.addListener((observable, oldValue, newValue) -> action.run()); + } } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index c97fabc30..4debe4a8c 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -229,6 +229,10 @@ public static StateListener createDebugPrinter(boolean showStackTrace, IntSuppli return createDebugPrinter(showStackTrace, System.out::println, bits); } + public String getAsString(IntSupplier... bits) { + return appendBitStrings(new StringBuilder(), state, bits).toString(); + } + public static StateListener createDebugPrinter(boolean showStackTrace, Consumer log, IntSupplier... bits) { StringBuilder builder = new StringBuilder(); return (source, mask) -> { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index 07e3a407e..1e33c3067 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -15,6 +15,12 @@ public enum ChartBits implements IntSupplier { public static final int KnownMask = BitState.mask(ChartBits.values()); public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickFormatter, AxisLabelText); + public static StateListener printStackTrace(){ + return STACK_TRACE_PRINTER; + } + + private static final StateListener STACK_TRACE_PRINTER = BitState.createDebugPrinter(true, values()); + @Override public int getAsInt() { return bit; From 8fe9cce502578b9472e9e3cff416e0961275e529 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 23 Jul 2023 11:58:25 +0200 Subject: [PATCH 17/81] fixed cached return value --- .../java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java | 2 +- .../main/java/io/fair_acc/dataset/events/ChartBits.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 76e42f2d5..80c91cd76 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -456,7 +456,7 @@ private double computePrefSize(final double axisLength) { // We can cache the existing layout if nothing has changed. final boolean isHorizontal = getSide().isHorizontal(); if (getLength() == axisLength && state.isClean(ChartBits.AxisLayout)) { - return isHorizontal ? getWidth() : getHeight(); + return isHorizontal ? getHeight() : getWidth(); // secondary dimension } // Compute the ticks with correctly placed labels to determine the diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index 1e33c3067..4201cdee6 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -15,10 +15,15 @@ public enum ChartBits implements IntSupplier { public static final int KnownMask = BitState.mask(ChartBits.values()); public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickFormatter, AxisLabelText); - public static StateListener printStackTrace(){ + public static StateListener printer() { + return PRINTER; + } + + public static StateListener printerWithStackTrace() { return STACK_TRACE_PRINTER; } + private static final StateListener PRINTER = BitState.createDebugPrinter(false, values()); private static final StateListener STACK_TRACE_PRINTER = BitState.createDebugPrinter(true, values()); @Override From f70f900766fb96c26b9bd4fd41522d0634881624 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 23 Jul 2023 13:50:12 +0200 Subject: [PATCH 18/81] cleanup and updated unit tests --- .../chartfx/axes/spi/AbstractAxis.java | 33 ++++--- .../axes/spi/AbstractAxisParameter.java | 39 ++++---- .../io/fair_acc/chartfx/utils/PropUtil.java | 96 +++++++++---------- .../axes/spi/AbstractAxisParameterTests.java | 14 +-- .../chartfx/axes/spi/AbstractAxisTests.java | 9 ++ .../io/fair_acc/dataset/events/ChartBits.java | 4 +- 6 files changed, 97 insertions(+), 98 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 80c91cd76..95e3e6682 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -6,6 +6,7 @@ import io.fair_acc.chartfx.ui.css.PathStyle; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.utils.FXUtils; +import io.fair_acc.chartfx.utils.PropUtil; import io.fair_acc.dataset.event.AxisNameChangeEvent; import io.fair_acc.dataset.event.AxisRangeChangeEvent; import io.fair_acc.dataset.events.ChartBits; @@ -193,7 +194,6 @@ public void drawAxis(final GraphicsContext gc, final double axisWidth, final dou drawAxisLabel(gc, axisWidth, axisHeight, getAxisLabel(), getTickLength()); drawAxisLine(gc, axisLength, axisWidth, axisHeight); drawAxisPost(); - } /** @@ -285,29 +285,35 @@ public boolean isValueOnAxis(final double value) { * Updates the contents for this axis, e.g., tick labels, spacing * range, caches, etc. */ + protected void updateDirtyContent(double length) { + updateAxisRange(length); + updateAxisLabel(); + } + protected void updateAxisRange(double length) { - if (length == getLength() && state.isClean(ChartBits.AxisRange)) { + if(state.isClean(ChartBits.AxisRange, ChartBits.AxisTickLabelText) && length == getLength()) { return; } - // Update range & scale TODO: what is already set? the original implementation updates in many different ways + // Update the new axis range setLength(length); AxisRange range = getRange(); set(range.getMin(), range.getMax()); setScale(range.scale = calculateNewScale(length, getMin(), getMax())); setTickUnit(range.tickUnit = computePreferredTickUnit(length)); + range.axisLength = length; - updateAxisLabelAndUnit(); + // Auto-scale metric units + updateScaleAndUnitPrefix(); - // Update cache to have axis transforms + // Update the cache to the new range updateCachedVariables(); - // Tick marks + // Compute new tick marks and locations updateMajorTickMarks(range); updateMinorTickMarks(); updateTickMarkPositions(getTickMarks()); updateTickMarkPositions(getMinorTickMarks()); - } /** @@ -463,7 +469,7 @@ private double computePrefSize(final double axisLength) { // overlap. The initial estimate is usually correct, so later changes // happen very rarely, e.g., at a point where y axes labels switch to // shifting lines. - updateAxisRange(axisLength); + updateDirtyContent(axisLength); scaleFont = 1.0; maxLabelHeight = 0; @@ -567,7 +573,7 @@ private double computeMinSize() { private double getAxisLabelSize() { final Text axisLabel = getAxisLabel(); - if (axisLabel.getText() != null && !axisLabel.getText().isBlank()) { + if (!PropUtil.isNullOrEmpty(axisLabel.getText())) { var bounds = axisLabel.getBoundsInParent(); return getSide().isHorizontal() ? bounds.getHeight() : bounds.getWidth(); } @@ -652,7 +658,7 @@ protected void updateMajorTickMarks(AxisRange range) { var oldTickValues = getTickMarkValues(); if(newTickValues.size() < 2) { return; // TODO: why did the previous code only update when there are > 2 ticks? - }else if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickFormatter)) { + }else if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickLabelText)) { return; // no need to redo labels, just reposition the ticks } @@ -904,7 +910,7 @@ public void drawAxis() { } // update labels, tick marks etc. - updateAxisRange(getLength()); + updateDirtyContent(getLength()); // redraw outdated canvas if (state.isDirty(ChartBits.AxisCanvas)) { @@ -917,7 +923,6 @@ public void drawAxis() { } finally { gc.translate(-canvasPadX, -canvasPadY); } - } // everything is updated @@ -1098,7 +1103,7 @@ private static boolean isTickLabelsOverlap(final TickMark m1, final TickMark m2, } protected static void drawAxisLabel(final GraphicsContext gc, final double x, final double y, final TextStyle label) { - if (label.getText() == null || label.getText().isBlank()) { + if (PropUtil.isNullOrEmpty(label.getText())) { return; } @@ -1111,7 +1116,7 @@ protected static void drawAxisLabel(final GraphicsContext gc, final double x, fi protected static void drawTickMarkLabel(final GraphicsContext gc, final double x, final double y, final double scaleFont, final TickMark tickMark) { - if (tickMark.getText() == null || tickMark.getText().isBlank()) { + if (PropUtil.isNullOrEmpty(tickMark.getText())) { return; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 012814a1c..cbd4d1b6e 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -46,9 +46,9 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { protected final BitState state = BitState.initDirty(this, ChartBits.AxisMask); protected final Runnable invalidateLayout; protected final Runnable invalidateAxisRange; - protected final Runnable invalidateTickFormatter; protected final Runnable invalidateCanvas; - protected final Runnable invalidateAxisName; + protected final Runnable invalidateTickLabels; + protected final Runnable invalidateAxisLabel; private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Region.getClassCssMetaData()); @@ -388,14 +388,16 @@ public AbstractAxisParameter() { tickUnit ); - PropUtil.runOnChange(invalidateAxisName = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisLabelText), + PropUtil.runOnChange(invalidateAxisLabel = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisLabelText), axisName, unitScaling, autoUnitScaling, axisUnit ); - PropUtil.runOnChange(invalidateTickFormatter = state.onAction(ChartBits.AxisRange, ChartBits.AxisTickFormatter), + PropUtil.runOnChange(invalidateTickLabels = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisTickLabelText), tickLabelFormatter, + unitScaling, + autoUnitScaling, timeAxis // may change the size of the rendered labels? ); @@ -1261,31 +1263,26 @@ public BitState getBitState() { return state; } - protected void updateAxisLabelAndUnit() { - final String axisPrimaryLabel = getName(); - String localAxisUnit = getUnit(); - final boolean isAutoScaling = isAutoUnitScaling(); - if (isAutoScaling) { - updateScaleAndUnitPrefix(); - } - - if (!state.isDirty(ChartBits.AxisLabelText)) { + protected void updateAxisLabel() { + if (state.isClean(ChartBits.AxisLabelText)) { return; } + String unit = getUnit(); + String prefix = MetricPrefix.getShortPrefix(getUnitScaling()); - final String axisPrefix = MetricPrefix.getShortPrefix(getUnitScaling()); - if ((localAxisUnit == null || localAxisUnit.isBlank()) && !axisPrefix.isBlank()) { - localAxisUnit = ""; - } - - if (localAxisUnit == null) { - getAxisLabel().setText(axisPrimaryLabel); + if (unit == null && PropUtil.isNullOrEmpty(prefix)) { + getAxisLabel().setText(getName()); } else { - getAxisLabel().setText(axisPrimaryLabel + " [" + axisPrefix + localAxisUnit + "]"); + unit = (unit == null) ? "" : unit; + prefix = (prefix == null) ? "" : prefix; + getAxisLabel().setText(getName() + " [" + prefix + unit + "]"); } } protected void updateScaleAndUnitPrefix() { + if (isAutoUnitScaling()) { + return; + } final double range = Math.abs(getMax() - getMin()); final double logRange = Math.log10(range); final double power3Upper = 3.0 * Math.ceil(logRange / 3.0); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java index cadf7e318..ed69f61c4 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java @@ -3,10 +3,7 @@ import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.*; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableDoubleValue; -import javafx.beans.value.ObservableIntegerValue; -import javafx.beans.value.ObservableValue; +import javafx.beans.value.*; import java.util.Objects; @@ -34,60 +31,35 @@ public static boolean set(StringProperty prop, String value){ } public static BooleanProperty createBooleanProperty(Object bean, String name, boolean initial, Runnable... onChange) { - return new SimpleBooleanProperty(bean, name, initial) { - @Override - public void set(final boolean newValue) { - final boolean oldValue = get(); - if (oldValue != newValue) { - super.set(newValue); - for (Runnable action : onChange) { - action.run(); - } - } - } - }; + var prop = new SimpleBooleanProperty(bean, name, initial); + for (Runnable action : onChange) { + runOnChange(action, prop); + } + return prop; } public static DoubleProperty createDoubleProperty(Object bean, String name, double initial, Runnable... onChange) { - return new SimpleDoubleProperty(bean, name, initial) { - @Override - public void set(final double newValue) { - if (!isEqual(get(), newValue)) { - super.set(newValue); - for (Runnable action : onChange) { - action.run(); - } - } - } - }; - } - - public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial, Runnable onChange) { - return new ReadOnlyDoubleWrapper(bean, name, initial) { - @Override - public void set(final double newValue) { - if (!isEqual(get(), newValue)) { - super.set(newValue); - onChange.run(); - } - } - }; + var prop = new SimpleDoubleProperty(bean, name, initial); + for (Runnable action : onChange) { + runOnChange(action, prop); + } + return prop; } - public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial) { - return new ReadOnlyDoubleWrapper(bean, name, initial); + public static ReadOnlyDoubleWrapper createReadOnlyDoubleWrapper(Object bean, String name, double initial, Runnable... onChange) { + var prop = new ReadOnlyDoubleWrapper(bean, name, initial); + for (Runnable action : onChange) { + runOnChange(action, prop); + } + return prop; } - public static ObjectProperty createObjectProperty(Object bean, String name, T initial) { - return new SimpleObjectProperty(bean, name, initial) { - @Override - public void set(final T newValue) { - final T oldValue = getValue(); - if (!Objects.equals(oldValue, newValue)) { - super.set(newValue); - } - } - }; + public static ObjectProperty createObjectProperty(Object bean, String name, T initial, Runnable... onChange) { + var prop = new SimpleObjectProperty<>(bean, name, initial); + for (Runnable action : onChange) { + runOnChange(action, prop); + } + return prop; } /** @@ -126,6 +98,19 @@ public void invalidated(Observable observable) { condition.addListener(new InvalidationListener() { int prev = obs.get(); + @Override + public void invalidated(Observable observable) { + if (prev == obs.get()) { + prev = obs.get(); + action.run(); + } + } + }); + } else if (condition instanceof ObservableLongValue) { + var obs = (ObservableLongValue) condition; + condition.addListener(new InvalidationListener() { + long prev = obs.get(); + @Override public void invalidated(Observable observable) { if (prev == obs.get()) { @@ -140,8 +125,15 @@ public void invalidated(Observable observable) { } } - private static boolean isEqual(double a, double b) { + public static boolean isEqual(double a, double b) { return Double.doubleToLongBits(a) == Double.doubleToLongBits(b); // supports NaN } + public static boolean isNullOrEmpty(String string){ + return string == null || string.isBlank(); + } + + private PropUtil() { + } + } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index 0a8128d9a..adbd84f66 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -84,7 +84,7 @@ void testBasicGetterSetters() { var state = axis.getBitState(); state.addChangeListener(ChartBits.AxisLabelText, (src, bits) -> { axis.updateScale(); - axis.updateAxisLabelAndUnit(); + axis.updateAxisLabel(); state.clear(ChartBits.AxisLabelText); }); @@ -166,20 +166,16 @@ void testBasicGetterSetters() { axis.setScale(2.0); assertEquals(2.0, axis.getScale()); - // TODO: behavior changed. Do we still need these tests? - /*assertEquals(Side.BOTTOM, axis.getSide()); + assertEquals(Side.BOTTOM, axis.getSide()); for (Side side : Side.values()) { axis.setSide(side); assertEquals(side, axis.getSide()); - if (side.isHorizontal()) { - assertEquals(axis.getWidth(), axis.getLength()); - } else { - assertEquals(axis.getHeight(), axis.getLength()); - } + assertTrue(state.isDirty(ChartBits.AxisLayout)); + state.clear(ChartBits.AxisLayout); } axis.setSide(null); assertEquals(Double.NaN, axis.getLength()); - axis.setSide(Side.LEFT);*/ + axis.setSide(Side.LEFT); assertFalse(axis.isTimeAxis()); axis.setTimeAxis(true); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java index 37d2fa5ae..5b7d0e110 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java @@ -159,6 +159,7 @@ void testHelper() { AbstractAxis axis = new EmptyAbstractAxis(-5.0, 5.0); assertDoesNotThrow(() -> axis.clearAxisCanvas(axis.getCanvas().getGraphicsContext2D(), 100, 100)); + axis.setUnit(null); axis.setSide(Side.BOTTOM); assertEquals(+1.0, axis.calculateNewScale(10, -5.0, +5.0)); assertEquals(+25, axis.computePrefHeight(100), 2); @@ -168,6 +169,14 @@ void testHelper() { assertEquals(+150, axis.computePrefHeight(-1)); assertEquals(+22, axis.computePrefWidth(100), 2); + axis.setUnit(""); + axis.setSide(Side.BOTTOM); + assertEquals(+44, axis.computePrefHeight(100), 2); + assertEquals(+150.0, axis.computePrefWidth(-1)); + axis.setSide(Side.LEFT); + assertEquals(+150, axis.computePrefHeight(-1)); + assertEquals(+44, axis.computePrefWidth(100), 2); + assertDoesNotThrow(axis::clear); assertDoesNotThrow(axis::forceRedraw); final AtomicInteger counter = new AtomicInteger(); diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index 4201cdee6..33771e91b 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -9,11 +9,11 @@ public enum ChartBits implements IntSupplier { AxisLayout, // size needs to be evaluated (e.g. labels may be larger) AxisCanvas, // needs to be drawn AxisRange, // anything related to min/max, tick marks, etc. - AxisTickFormatter, // tick label formatting + AxisTickLabelText, // the tick label display w/ unit scaling AxisLabelText; // display name or units public static final int KnownMask = BitState.mask(ChartBits.values()); - public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickFormatter, AxisLabelText); + public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickLabelText, AxisLabelText); public static StateListener printer() { return PRINTER; From 5b4a1efd87d010402f42d0bba5f8563473f59838 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 23 Jul 2023 15:08:28 +0200 Subject: [PATCH 19/81] moved constructor up to improve readability --- .../chartfx/axes/spi/AbstractAxis.java | 15 -- .../axes/spi/AbstractAxisParameter.java | 250 +++++++++--------- 2 files changed, 125 insertions(+), 140 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 95e3e6682..fd2526e36 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -99,21 +99,6 @@ protected AbstractAxis() { } getChildren().add(canvas); - // We can ignore the layout if labels can only move linearly along - // the axis length. This happens e.g. for an X-axis that displays - // moving time with the default policy. - state.addChangeListener(ChartBits.AxisRange, (source, bits) -> { - if (!isTickMarkVisible() || !isTickLabelsVisible()) { - return; - } - final int rot = Math.abs(((int) getTickLabelRotation()) % 360); - if (getOverlapPolicy() == AxisLabelOverlapPolicy.SHIFT_ALT || getSide() == null - || (getSide().isHorizontal() && !(rot == 0 || rot == 180)) - || (getSide().isVertical() && !(rot == 90 || rot == 270))) { - state.setDirty(ChartBits.AxisLayout); - } - }); - // set default axis title/label alignment updateTickLabelAlignment(); updateAxisLabelAlignment(); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index cbd4d1b6e..3bedb6fd3 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -43,6 +43,131 @@ * @author rstein */ public abstract class AbstractAxisParameter extends Pane implements Axis { + + /** + * Create a auto-ranging AbstractAxisParameter + */ + public AbstractAxisParameter() { + super(); + // Styles changes that can be removed after moving the styling to sub-nodes + tickLabelStyle.rotateProperty().bindBidirectional(tickLabelRotation); + tickLabelStyle.fontProperty().bindBidirectional(tickLabelFont); + tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill); + axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD + + axisPadding.addListener(state.onPropChange(ChartBits.AxisLayout)::set); + + // Properties that may be relevant to the layout and must always at least redraw the canvas + PropUtil.runOnChange(invalidateLayout = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas), + // distance to main line + side, + axisPadding, + tickMarkGap, + + // tick marks + tickMarkVisible, + tickLength, + majorTickStyle.changeCounterProperty(), + + // tick labels + tickLabelsVisible, + tickLabelGap, + tickLabelRotation, + overlapPolicy, + tickLabelStyle.changeCounterProperty(), + tickLabelFont, // already in style + + // axis label + axisLabelGap, + axisLabel.changeCounterProperty(), + + // not really relevant? + animationDuration, + dimIndex + ); + state.addChangeListener(ChartBits.AxisLayout, (src, bits) -> requestLayout()); // forward to JavaFX + + // Properties that change the placement of ticks + // We can ignore the layout if labels can only move linearly along + // the axis length. This happens e.g. for an X-axis that displays + // moving time with the default policy. + PropUtil.runOnChange(invalidateAxisRange = () -> { + state.setDirty(ChartBits.AxisRange); + if (!isTickMarkVisible() || !isTickLabelsVisible()) { + return; + } + final int rot = Math.abs(((int) getTickLabelRotation()) % 360); + if (getOverlapPolicy() == AxisLabelOverlapPolicy.SHIFT_ALT || getSide() == null + || (getSide().isHorizontal() && !(rot == 0 || rot == 180)) + || (getSide().isVertical() && !(rot == 90 || rot == 270))) { + state.setDirty(ChartBits.AxisLayout); + } + }, + // tick placement + autoRanging, + autoGrowRanging, + autoRangeRounding, + autoRangePadding, + + // tick labels + tickLabelSpacing, + maxMajorTickLabelCount, + invertAxis + ); + + // Properties that require a redraw of the canvas but won't affect the placement of ticks + PropUtil.runOnChange(invalidateCanvas = state.onAction(ChartBits.AxisCanvas), + // minor ticks + minorTickVisible, + minorTickStyle.changeCounterProperty(), // not used for layout calculation + minorTickCount, + minorTickLength, + + // item placement + axisCenterPosition, + axisLabelTextAlignment, + + // the main properties of what the axis currently shows. + // Used for internal computation, so we don't want them to + // trigger more. + minProp, + maxProp, + length, + scale, + tickUnit + ); + + // Properties that change the text of the labels without changing the position, e.g., unit scaling + PropUtil.runOnChange(invalidateAxisLabel = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisLabelText), + axisName, + unitScaling, + autoUnitScaling, + axisUnit + ); + PropUtil.runOnChange(invalidateTickLabels = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisTickLabelText), + tickLabelFormatter, + unitScaling, + autoUnitScaling, + timeAxis // may change the size of the rendered labels? + ); + + // We need to keep the user range in sync with what is being displayed, and also + // react in case users set the range via set(min, max). Note that this will not + // trigger if the properties are set during layout because the value would either + // be auto-ranging or be set to the same value as the user range. + PropUtil.runOnChange(() -> { + if (getUserRange().set(getMin(), getMax()) && !isAutoGrowRanging() && !isAutoRanging()) { + invalidateAxisRange.run(); + } + }, minProp, maxProp); + PropUtil.runOnChange(() -> { + if (isAutoRanging() || isAutoGrowRanging()) { + invalidateAxisRange.run(); + } // TODO: don't reset during a set? + }, tickUnit); + + } + protected final BitState state = BitState.initDirty(this, ChartBits.AxisMask); protected final Runnable invalidateLayout; protected final Runnable invalidateAxisRange; @@ -293,131 +418,6 @@ public abstract class AbstractAxisParameter extends Pane implements Axis { */ protected final transient StyleableDoubleProperty tickUnit = CSS.createDoubleProperty(this, "tickUnit", DEFAULT_TICK_UNIT); - /** - * Create a auto-ranging AbstractAxisParameter - */ - public AbstractAxisParameter() { - super(); - // TODO: remove and use styles directly? - tickLabelStyle.rotateProperty().bindBidirectional(tickLabelRotation); - tickLabelStyle.fontProperty().bindBidirectional(tickLabelFont); - tickLabelStyle.fillProperty().bindBidirectional(tickLabelFill); - axisLabel.textAlignmentProperty().bindBidirectional(axisLabelTextAlignmentProperty()); // NOPMD - - // Properties that may be relevant to the layout and must always at least redraw the canvas - PropUtil.runOnChange(invalidateLayout = state.onAction(ChartBits.AxisLayout, ChartBits.AxisCanvas), - // distance to main line - side, - axisPadding, - tickMarkGap, - - // tick marks - tickMarkVisible, - tickLength, - majorTickStyle.changeCounterProperty(), - - // tick labels - tickLabelsVisible, - tickLabelGap, - tickLabelRotation, - overlapPolicy, - tickLabelStyle.changeCounterProperty(), - tickLabelFont, // already in style - - // axis label - axisLabelGap, - axisLabel.changeCounterProperty(), - - // not really relevant? - animationDuration, - dimIndex - ); - - // Forward axis layout requests to the JavaFX layout bit - state.addChangeListener(ChartBits.AxisLayout, (source, bits) -> { - super.requestLayout(); - }); - - // Properties that change the placement of ticks - // We can ignore the layout if labels can only move linearly along - // the axis length. This happens e.g. for an X-axis that displays - // moving time with the default policy. - PropUtil.runOnChange(invalidateAxisRange = () -> { - state.setDirty(ChartBits.AxisRange); - if (!isTickMarkVisible() || !isTickLabelsVisible()) { - return; - } - final int rot = Math.abs(((int) getTickLabelRotation()) % 360); - if (getOverlapPolicy() == AxisLabelOverlapPolicy.SHIFT_ALT || getSide() == null - || (getSide().isHorizontal() && !(rot == 0 || rot == 180)) - || (getSide().isVertical() && !(rot == 90 || rot == 270))) { - state.setDirty(ChartBits.AxisLayout); - } - }, - // tick placement - autoRanging, - autoGrowRanging, - autoRangeRounding, - autoRangePadding, - - // tick labels - tickLabelSpacing, - maxMajorTickLabelCount, - invertAxis - ); - - // Properties that require a redraw of the canvas but won't affect the placement of ticks - PropUtil.runOnChange(invalidateCanvas = state.onAction(ChartBits.AxisCanvas), - // minor ticks - minorTickVisible, - minorTickStyle.changeCounterProperty(), // not used for layout calculation - minorTickCount, - minorTickLength, - - // item placement - axisCenterPosition, - axisLabelTextAlignment, - - // the main properties of what the axis currently shows. - // Used for internal computation, so we don't want them to - // trigger more. - minProp, - maxProp, - length, - scale, - tickUnit - ); - - PropUtil.runOnChange(invalidateAxisLabel = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisLabelText), - axisName, - unitScaling, - autoUnitScaling, - axisUnit - ); - PropUtil.runOnChange(invalidateTickLabels = state.onAction(ChartBits.AxisCanvas, ChartBits.AxisTickLabelText), - tickLabelFormatter, - unitScaling, - autoUnitScaling, - timeAxis // may change the size of the rendered labels? - ); - - // We need to keep the user range in sync with what is being displayed, and also - // react in case users set the range via set(min, max). Note that this will not - // trigger if the properties are set during layout because the value would either - // be auto-ranging or be set to the same value as the user range. - PropUtil.runOnChange(() -> { - if (getUserRange().set(getMin(), getMax()) && !isAutoGrowRanging() && !isAutoRanging()) { - invalidateAxisRange.run(); - } - }, minProp, maxProp); - PropUtil.runOnChange(() -> { - if (isAutoRanging() || isAutoGrowRanging()) { - invalidateAxisRange.run(); - } // TODO: don't reset during a set? - }, tickUnit); - - } - @Override public String getUserAgentStylesheet() { return AbstractAxisParameter.CHART_CSS; From e164ed5bb415fba11939b4a3ffbe942641721c58 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 23 Jul 2023 17:13:02 +0200 Subject: [PATCH 20/81] removed labels when the axis is nan --- .../java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index fd2526e36..de47d55f8 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -154,7 +154,7 @@ public void drawAxis(final GraphicsContext gc, final double axisWidth, final dou drawAxisPre(); final double axisLength = getLength(); - if (isTickMarkVisible()) { + if (isTickLabelRendered()) { final var majorTicks = getTickMarks(); final var minorTicks = getMinorTickMarks(); @@ -467,7 +467,7 @@ private double computePrefSize(final double axisLength) { // Optional tick mark labels double tickLabelSize = 0; - if (isTickMarkVisible() && isTickLabelsVisible()) { + if (isTickLabelRendered()) { // Figure out maximum sizes for (TickMark tickMark : getTickMarks()) { @@ -578,6 +578,10 @@ private double getExtraLabelOffset() { return 0; } + private boolean isTickLabelRendered() { + return isTickMarkVisible() && isTickLabelsVisible() && Double.isFinite(getMin()) && Double.isFinite(getMax()); + } + private void applyOverlapPolicy(List tickMarks) { // Default to all visible for (TickMark tickMark : tickMarks) { From 4df61d916f310fa52f76c6f281efa6d8992cce2b Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sun, 23 Jul 2023 17:19:46 +0200 Subject: [PATCH 21/81] exposed more debug flags --- .../io/fair_acc/dataset/events/BitState.java | 30 ++++++++++++++----- .../io/fair_acc/dataset/events/ChartBits.java | 7 +++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 4debe4a8c..88e299344 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -222,30 +222,42 @@ public static int clear(int mask, int bit) { } public static StateListener createDebugPrinter(IntSupplier... bits) { - return createDebugPrinter(false, bits); + return createDebugPrinter(System.out::println, bits); } - public static StateListener createDebugPrinter(boolean showStackTrace, IntSupplier... bits) { - return createDebugPrinter(showStackTrace, System.out::println, bits); + public static StateListener createDebugPrinterWithStackTrace(IntSupplier... bits) { + return createDebugPrinterWithStackTrace(System.out::println, bits); } - public String getAsString(IntSupplier... bits) { - return appendBitStrings(new StringBuilder(), state, bits).toString(); + public static StateListener createDebugPrinter(Consumer log, IntSupplier... bits) { + return createDebugPrinter(false, 0, 0, log, bits); + } + + public static StateListener createDebugPrinterWithStackTrace(int maxStackIx, IntSupplier... bits) { + return createDebugPrinter(true, DEFAULT_MIN_STACK_TRACE, maxStackIx, System.out::println, bits); + } + + public static StateListener createDebugPrinterWithStackTrace(Consumer log, IntSupplier... bits) { + return createDebugPrinter(true, DEFAULT_MIN_STACK_TRACE, DEFAULT_MAX_STACK_TRACE, log, bits); } - public static StateListener createDebugPrinter(boolean showStackTrace, Consumer log, IntSupplier... bits) { + public static StateListener createDebugPrinter(boolean showStackTrace, int minStackIx, int maxStackIx, Consumer log, IntSupplier... bits) { StringBuilder builder = new StringBuilder(); return (source, mask) -> { builder.setLength(0); builder.append(source.getSource()).append(": "); appendBitStrings(builder, mask, bits); if (showStackTrace) { - appendStackTrace(builder, 6, 15); // offset to account for internal methods + appendStackTrace(builder, minStackIx, maxStackIx); // offset to account for internal methods } log.accept(builder.toString()); }; } + public String getAsString(IntSupplier... bits) { + return appendBitStrings(new StringBuilder(), state, bits).toString(); + } + public static StringBuilder appendBitStrings(StringBuilder builder, int mask, IntSupplier... bits) { if (mask == 0) { return builder.append("clean"); @@ -279,6 +291,10 @@ public static StringBuilder appendStackTrace(StringBuilder builder, int from, in return builder; } + // Default to hide stack trace lines that are inside the printer. Keep updated. + private static final int DEFAULT_MIN_STACK_TRACE = 6; + private static final int DEFAULT_MAX_STACK_TRACE = 20; + private static class FilteredListener implements StateListener { private FilteredListener(int filter, StateListener listener) { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index 33771e91b..d0e59b65f 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -12,7 +12,8 @@ public enum ChartBits implements IntSupplier { AxisTickLabelText, // the tick label display w/ unit scaling AxisLabelText; // display name or units - public static final int KnownMask = BitState.mask(ChartBits.values()); + private static final ChartBits[] AllBits = ChartBits.values(); + public static final int KnownMask = BitState.mask(AllBits); public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickLabelText, AxisLabelText); public static StateListener printer() { @@ -23,8 +24,8 @@ public static StateListener printerWithStackTrace() { return STACK_TRACE_PRINTER; } - private static final StateListener PRINTER = BitState.createDebugPrinter(false, values()); - private static final StateListener STACK_TRACE_PRINTER = BitState.createDebugPrinter(true, values()); + private static final StateListener PRINTER = BitState.createDebugPrinter(AllBits); + private static final StateListener STACK_TRACE_PRINTER = BitState.createDebugPrinterWithStackTrace(AllBits); @Override public int getAsInt() { From 993b00c07f38d89e74c9a581201e044d1ef5be3e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 24 Jul 2023 01:15:16 +0200 Subject: [PATCH 22/81] fixed scale for inf range --- .../chartfx/axes/spi/AbstractAxis.java | 5 +- .../chartfx/axes/spi/AbstractAxisTests.java | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index de47d55f8..5656734bb 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -387,10 +387,11 @@ protected AxisRange autoRange(final double length) { * @return new scale to fit the range from lower bound to upper bound in the given display length */ protected double calculateNewScale(final double length, final double lowerBound, final double upperBound) { - if (length == 0) return -1.0; final double range = upperBound - lowerBound; final double scale = (range == 0) ? length : length / range; - if (getSide().isVertical()) { + if (scale == 0) { + return -1; // covers inf range input + } else if (getSide().isVertical()) { return isInvertedAxis ? scale : -scale; } else { return isInvertedAxis ? -scale : scale; diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java index 5b7d0e110..1a114b0c4 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java @@ -189,6 +189,59 @@ void testHelper() { assertEquals(2, counter.get()); } + @Test + void calculateNewScale() { + AbstractAxis axis = new EmptyAbstractAxis(-5.0, 5.0); + + axis.setSide(Side.BOTTOM); + assertEquals(1, axis.calculateNewScale(4, -2, 2)); + assertEquals(-1, axis.calculateNewScale(0, -2, +2)); + assertEquals(-2, axis.calculateNewScale(-20, -5, 5)); + assertEquals(1, axis.calculateNewScale(4, -2, +2)); + assertEquals(1, axis.calculateNewScale(1, +2, +2)); + assertEquals(Double.NaN, axis.calculateNewScale(Double.NaN, -2, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, Double.NaN, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, -2, Double.NaN)); + assertEquals(-1, axis.calculateNewScale(1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); + assertEquals(-1, axis.calculateNewScale(1, -2, Double.POSITIVE_INFINITY)); + assertEquals(-1, axis.calculateNewScale(1, Double.NEGATIVE_INFINITY, +2)); + + axis.setSide(Side.LEFT); + assertEquals(-1, axis.calculateNewScale(4, -2, 2)); + assertEquals(-1, axis.calculateNewScale(0, -2, +2)); + assertEquals(2, axis.calculateNewScale(-20, -5, 5)); + assertEquals(-1, axis.calculateNewScale(4, -2, +2)); + assertEquals(-1, axis.calculateNewScale(1, +2, +2)); + assertEquals(Double.NaN, axis.calculateNewScale(Double.NaN, -2, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, Double.NaN, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, -2, Double.NaN)); + assertEquals(-1, axis.calculateNewScale(1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); + + axis.setSide(Side.TOP); + axis.invertAxis(true); + assertEquals(1, axis.calculateNewScale(4, -2, 2)); + assertEquals(-1, axis.calculateNewScale(0, -2, +2)); + assertEquals(-2, axis.calculateNewScale(-20, -5, 5)); + assertEquals(1, axis.calculateNewScale(4, -2, +2)); + assertEquals(1, axis.calculateNewScale(1, +2, +2)); + assertEquals(Double.NaN, axis.calculateNewScale(Double.NaN, -2, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, Double.NaN, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, -2, Double.NaN)); + assertEquals(-1, axis.calculateNewScale(1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); + + axis.setSide(Side.RIGHT); + axis.invertAxis(true); + assertEquals(-1, axis.calculateNewScale(4, -2, 2)); + assertEquals(-1, axis.calculateNewScale(0, -2, +2)); + assertEquals(2, axis.calculateNewScale(-20, -5, 5)); + assertEquals(-1, axis.calculateNewScale(4, -2, +2)); + assertEquals(-1, axis.calculateNewScale(1, +2, +2)); + assertEquals(Double.NaN, axis.calculateNewScale(Double.NaN, -2, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, Double.NaN, 2)); + assertEquals(Double.NaN, axis.calculateNewScale(1, -2, Double.NaN)); + assertEquals(-1, axis.calculateNewScale(1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); + } + @Test void testTickMarks() { AbstractAxis axis = new EmptyAbstractAxis(-5.0, 5.0); From 639f278061b640ff0dc7547ff8e085f40e34ead7 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 24 Jul 2023 02:08:10 +0200 Subject: [PATCH 23/81] fixed wrong conditions --- .../io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java | 2 +- .../src/main/java/io/fair_acc/chartfx/utils/PropUtil.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 3bedb6fd3..b40cd101f 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -1280,7 +1280,7 @@ protected void updateAxisLabel() { } protected void updateScaleAndUnitPrefix() { - if (isAutoUnitScaling()) { + if (!isAutoUnitScaling()) { return; } final double range = Math.abs(getMax() - getMin()); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java index ed69f61c4..043e7960c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/PropUtil.java @@ -87,7 +87,7 @@ public void invalidated(Observable observable) { @Override public void invalidated(Observable observable) { - if (prev == obs.get()) { + if (prev != obs.get()) { prev = obs.get(); action.run(); } @@ -100,7 +100,7 @@ public void invalidated(Observable observable) { @Override public void invalidated(Observable observable) { - if (prev == obs.get()) { + if (prev != obs.get()) { prev = obs.get(); action.run(); } @@ -113,7 +113,7 @@ public void invalidated(Observable observable) { @Override public void invalidated(Observable observable) { - if (prev == obs.get()) { + if (prev != obs.get()) { prev = obs.get(); action.run(); } From 3c64dcb5bfea6a1920e0fce133e3694e5619fd76 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 24 Jul 2023 17:49:11 +0200 Subject: [PATCH 24/81] fixed oscilloscope axis --- .../axes/spi/AbstractAxisParameter.java | 2 +- .../chartfx/axes/spi/OscilloscopeAxis.java | 24 ++++--------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index b40cd101f..ba139122a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -956,7 +956,7 @@ public ObjectProperty overlapPolicyProperty() { @Override public boolean set(final double min, final double max) { - return setMin(min) | setMax(max); + return PropUtil.set(minProp, min) | PropUtil.set(maxProp, max); } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java index 0d9b0da53..2eef93f3d 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java @@ -375,6 +375,7 @@ protected void recomputeClampedRange() { @Override protected void updateCachedVariables() { + super.updateCachedVariables(); if (cache == null) { // lgtm [java/useless-null-check] NOPMD NOSONAR -- called from static initializer return; } @@ -418,15 +419,11 @@ protected class Cache { protected double upperBoundLog; protected double lowerBoundLog; protected double logScaleLength; - protected double logScaleLengthInv; - protected boolean isVerticalAxis; - protected double axisWidth; - protected double axisHeight; + protected double axisLength; protected double offset; private void updateCachedAxisVariables() { - axisWidth = getWidth(); - axisHeight = getHeight(); + axisLength = getLength(); localCurrentLowerBound = getMin(); localCurrentUpperBound = getMax(); @@ -434,24 +431,11 @@ private void updateCachedAxisVariables() { lowerBoundLog = axisTransform.forward(getMin()); logScaleLength = upperBoundLog - lowerBoundLog; - logScaleLengthInv = 1.0 / logScaleLength; - localScale = scaleProperty().get(); final double zero = OscilloscopeAxis.super.getDisplayPosition(0); localOffset = zero + localCurrentLowerBound * localScale; localOffset2 = localOffset - cache.localCurrentLowerBound * cache.localScale; - - if (getSide() != null) { - isVerticalAxis = getSide().isVertical(); - } - - if (isVerticalAxis) { - logScaleLengthInv = axisHeight / logScaleLength; - } else { - logScaleLengthInv = axisWidth / logScaleLength; - } - - offset = isVerticalAxis ? getHeight() : getWidth(); + offset = axisLength; } } From 9f998d90d768b25d6b463468d3e35fda0dfb6e4a Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 24 Jul 2023 18:18:37 +0200 Subject: [PATCH 25/81] fixed histogram example --- .../src/main/java/io/fair_acc/chartfx/Chart.java | 10 ++++++---- .../io/fair_acc/sample/chart/Histogram2DimSample.java | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 87dae9761..798f83094 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -1058,16 +1058,18 @@ protected void rendererChanged(final ListChangeListener.Change set.addListener(dataSetDataListener)); - renderer.getAxes().addListener(axesChangeListenerLocal); - renderer.getAxes().forEach(this::addAxisToChildren); + + // TODO: how should it handle renderer axes? this can add automatically-generated axes that aren't wanted +// renderer.getAxes().addListener(axesChangeListenerLocal); +// renderer.getAxes().forEach(this::addAxisToChildren); }); // handle removed renderer change.getRemoved().forEach(renderer -> { renderer.getDatasets().removeListener(datasetChangeListener); renderer.getDatasets().forEach(set -> set.removeListener(dataSetDataListener)); - renderer.getAxes().removeListener(axesChangeListenerLocal); - renderer.getAxes().forEach(this::removeAxisFromChildren); +// renderer.getAxes().removeListener(axesChangeListenerLocal); +// renderer.getAxes().forEach(this::removeAxisFromChildren); }); } // reset change to allow derived classes to add additional listeners to renderer changes diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java index 54251f48e..5257f99b0 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java @@ -94,7 +94,7 @@ public Node getChartPanel(final Stage primaryStage) { final DefaultNumericAxis xAxis1 = new DefaultNumericAxis("x-Axis y-Projection"); xAxis1.setLogAxis(false); - yAxis1.setAnimated(false); + xAxis1.setAnimated(false); xAxis1.setAutoRangeRounding(true); xAxis1.setAutoRangePadding(2.0); xAxis1.setAutoRanging(true); From 3a77694ffbbb459ef6852b40114efa3072514888 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 25 Jul 2023 00:10:53 +0200 Subject: [PATCH 26/81] created a thread safe implementation of BitState --- .../io/fair_acc/dataset/events/BitState.java | 150 ++++++++++++++---- .../fair_acc/dataset/events/BitStateTest.java | 57 +++++++ 2 files changed, 174 insertions(+), 33 deletions(-) create mode 100644 chartfx-dataset/src/test/java/io/fair_acc/dataset/events/BitStateTest.java diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 88e299344..31841ee92 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -2,13 +2,14 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.IntSupplier; /** * @author ennerf */ -public class BitState implements StateListener { +public abstract class BitState implements StateListener { public Runnable onAction(IntSupplier bit0, IntSupplier... more) { return onAction(mask(bit0, more)); @@ -50,16 +51,6 @@ public static int mask(IntSupplier bit0, IntSupplier... more) { return mask; } - public void setDirty(int bits) { - final int filtered = bits & filter; - final int delta = (state ^ filtered) & filtered; - if (delta != 0) { - state |= filtered; - notifyListeners(changeListeners, delta); - } - notifyListeners(invalidateListeners, bits); - } - @Override public void accept(BitState source, int bits) { setDirty(bits); @@ -70,11 +61,11 @@ public void setDirty(IntSupplier bit0, IntSupplier... bits) { } public boolean isDirty() { - return state != 0; + return getBits() != 0; } public boolean isDirty(int mask) { - return (state & mask) != 0; + return (getBits() & mask) != 0; } public boolean isDirty(IntSupplier bit0) { @@ -90,11 +81,11 @@ public boolean isDirty(IntSupplier bit0, IntSupplier bit1, IntSupplier... bits) } public boolean isClean() { - return state == 0; + return getBits() == 0; } public boolean isClean(int mask) { - return (state & mask) == 0; + return (getBits() & mask) == 0; } public boolean isClean(IntSupplier bit0) { @@ -109,22 +100,34 @@ public boolean isClean(IntSupplier bit0, IntSupplier bit1, IntSupplier... bits) return isClean(bit0.getAsInt() | bit1.getAsInt() | mask(bits)); } - public int getBits() { - return state; + public void getBits(StateListener action) { + action.accept(this, getBits()); } - public void getBits(StateListener action) { - action.accept(this, state); + public abstract int getBits(); + + public void setDirty(int bits) { + final int filtered = bits & filter; + final int delta = setDirtyAndGetDelta(filtered); + if (delta != 0) { + notifyListeners(changeListeners, delta); + } + notifyListeners(invalidateListeners, bits); } + /** + * @return the delta that switched from 0 to 1. returns 0 if nothing was changed. + */ + protected abstract int setDirtyAndGetDelta(int bits); + public void clear() { - state = 0; + clear(ALL_BITS); } - public int clear(final int mask) { - state &= ~mask; - return state; - } + /** + * @return the state after clearing the specified bits + */ + public abstract int clear(int bits); public int clear(IntSupplier bit0) { return clear(bit0.getAsInt()); @@ -255,7 +258,7 @@ public static StateListener createDebugPrinter(boolean showStackTrace, int minSt } public String getAsString(IntSupplier... bits) { - return appendBitStrings(new StringBuilder(), state, bits).toString(); + return appendBitStrings(new StringBuilder(), getBits(), bits).toString(); } public static StringBuilder appendBitStrings(StringBuilder builder, int mask, IntSupplier... bits) { @@ -320,25 +323,107 @@ public Object getSource() { } public static BitState initClean(Object source) { - return initClean(source, NO_FILTER); + return initClean(source, ALL_BITS); } public static BitState initDirty(Object source) { - return initDirty(source, NO_FILTER); + return initDirty(source, ALL_BITS); } public static BitState initClean(Object source, int filter) { - return new BitState(source, filter, 0); + return new SingleThreadedBitState(source, filter, 0); } public static BitState initDirty(Object source, int filter) { - return new BitState(source, filter, filter); + return new SingleThreadedBitState(source, filter, filter); + } + + public static BitState initCleanMultiThreaded(Object source, int filter) { + return new MultiThreadedBitState(source, filter, 0); + } + + public static BitState initDirtyMultiThreaded(Object source, int filter) { + return new MultiThreadedBitState(source, filter, filter); + } + + /** + * An single-threaded implementation that should only be modified by a single thread + */ + protected static class SingleThreadedBitState extends BitState { + + protected SingleThreadedBitState(Object source, int filter, int initial) { + super(source, filter); + state = initial; + } + + protected int setDirtyAndGetDelta(int bits) { + int delta = (state ^ bits) & bits; + state |= bits; + return delta; + } + + @Override + public int clear(final int bits) { + state &= ~bits; + return state; + } + + @Override + public int getBits() { + return state; + } + + int state; + + } + + /** + * An implementation that can be written to by multiple threads. The change + * events are sent on the same thread that sent the original request. + */ + protected static class MultiThreadedBitState extends BitState { + + protected MultiThreadedBitState(Object source, int filter, int initial) { + super(source, filter); + state.set(initial); + } + + @Override + public int setDirtyAndGetDelta(int bits) { + // Spin until we succeeded in setting the value + // or there are no remaining bits to be set. + while (true) { + final int oldState = getBits(); + final int newState = oldState | bits; + final int delta = (oldState ^ newState); + if (delta == 0 || state.compareAndSet(oldState, oldState | bits)) { + return delta; + } + } + } + + @Override + public int clear(int bits) { + while (true) { + final int current = getBits(); + final int newState = current & ~bits; + if (state.compareAndSet(current, newState)) { + return newState; + } + } + } + + @Override + public int getBits() { + return state.get(); + } + + private final AtomicInteger state = new AtomicInteger(); } - protected BitState(Object source, int filter, int initial) { + protected BitState(Object source, int filter) { this.source = source; this.filter = filter; - this.state = initial; } @Override @@ -346,10 +431,9 @@ public String toString() { return "bits(" + String.valueOf(source) + ")"; } - int state; final Object source; final int filter; - public static final int NO_FILTER = ~0; + public static final int ALL_BITS = ~0; List changeListeners; List invalidateListeners = null; diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/events/BitStateTest.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/events/BitStateTest.java new file mode 100644 index 000000000..2560ab1de --- /dev/null +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/events/BitStateTest.java @@ -0,0 +1,57 @@ +package io.fair_acc.dataset.events; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author ennerf + */ +class BitStateTest { + + @Test + void compareSingleAndMultiThreaded() { + + AtomicInteger counter = new AtomicInteger(); + StateListener listener = (source, bits) -> counter.incrementAndGet(); + var st = BitState.initDirty(this, ChartBits.KnownMask).addChangeListener(listener); + var mt = BitState.initDirtyMultiThreaded(this, ChartBits.KnownMask).addChangeListener(listener); + + // initial dirty state & clear a single bit + assertEquals(st.getBits(), mt.getBits()); + assertTrue(st.isDirty(ChartBits.AxisRange)); + assertTrue(mt.isDirty(ChartBits.AxisRange)); + assertEquals(st.clear(ChartBits.AxisRange), mt.clear(ChartBits.AxisRange)); + assertEquals(st.getBits(), mt.getBits()); + assertFalse(st.isDirty(ChartBits.AxisRange)); + assertFalse(mt.isDirty(ChartBits.AxisRange)); + + // Clear a single bit and trigger event listeners + assertEquals(0, counter.get()); + st.setDirty(ChartBits.AxisRange); + assertEquals(1, counter.get()); + mt.setDirty(ChartBits.AxisRange); + assertEquals(2, counter.get()); + assertTrue(st.isDirty(ChartBits.AxisRange)); + assertTrue(mt.isDirty(ChartBits.AxisRange)); + assertEquals(st.getBits(), mt.getBits()); + + // Subsequent events should be ignored + st.setDirty(ChartBits.AxisRange); + mt.setDirty(ChartBits.AxisRange); + assertEquals(2, counter.get()); + + st.clear(); + mt.clear(); + assertTrue(st.isClean()); + assertTrue(mt.isClean()); + + st.setDirty(ChartBits.AxisRange, ChartBits.AxisCanvas); + mt.setDirty(ChartBits.AxisRange, ChartBits.AxisLayout); + assertEquals(4, counter.get()); + + } + +} \ No newline at end of file From cd7c64c8e71f110c45b3637a6d7a4808aee1d458 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 25 Jul 2023 12:13:00 +0200 Subject: [PATCH 27/81] setup outline for chart pre/post layout phases --- .../main/java/io/fair_acc/chartfx/Chart.java | 108 ++++++------------ .../java/io/fair_acc/chartfx/axes/Axis.java | 3 + .../chartfx/axes/spi/AbstractAxis.java | 15 ++- .../fair_acc/chartfx/ui/utils/LayoutHook.java | 78 +++++++++++++ .../chartfx/ui/utils/PostLayoutHook.java | 60 ---------- .../io/fair_acc/chartfx/utils/FXUtils.java | 13 +++ .../io/fair_acc/dataset/events/ChartBits.java | 4 +- 7 files changed, 141 insertions(+), 140 deletions(-) create mode 100644 chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java delete mode 100644 chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/PostLayoutHook.java diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 798f83094..ba3c6484d 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -9,7 +9,9 @@ import io.fair_acc.chartfx.ui.layout.ChartPane; import io.fair_acc.chartfx.ui.layout.PlotAreaPane; import io.fair_acc.chartfx.ui.*; -import io.fair_acc.chartfx.ui.utils.PostLayoutHook; +import io.fair_acc.chartfx.ui.utils.LayoutHook; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; @@ -70,6 +72,10 @@ * @author hbraeun, rstein, major refactoring, re-implementation and re-design */ public abstract class Chart extends Region implements Observable { + private final LayoutHook layoutHooks = LayoutHook.newPreAndPostHook(this, this::runPreLayout, this::runPostLayout); + protected final BitState state = BitState.initDirtyMultiThreaded(this, BitState.ALL_BITS) + .addChangeListener(ChartBits.KnownMask, FXUtils.runOnFxThread((src, bits) -> layoutHooks.registerOnce())) // for now trigger on everything + .addChangeListener(ChartBits.ChartCanvas, FXUtils.runOnFxThread((src, bits) -> fireInvalidated())); // for compatibility private static final Logger LOGGER = LoggerFactory.getLogger(Chart.class); private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Control.getClassCssMetaData()); @@ -100,7 +106,6 @@ public abstract class Chart extends Region implements Observable { // isCanvasChangeRequested is a recursion guard to update canvas only once protected boolean isCanvasChangeRequested; // layoutOngoing is a recursion guard to update canvas only once - protected boolean layoutOngoing; protected final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>()); private final Map pluginGroups = new ConcurrentHashMap<>(); private final ObservableList plugins = FXCollections.observableList(new LinkedList<>()); @@ -113,9 +118,6 @@ public abstract class Chart extends Region implements Observable { getRenderers().addListener(this::rendererChanged); } - - protected boolean isAxesUpdate; - // Inner canvas for the drawn content protected final ResizableCanvas canvas = new ResizableCanvas(); protected final Pane canvasForeground = new Pane(); @@ -185,7 +187,6 @@ private static Pane getSidePane(Side side, ChartPane parent, Map map getChildren().add(menuPane); } - private final EventListener axisChangeListener = obs -> FXUtils.runFX(() -> axesInvalidated(obs)); protected final ListChangeListener axesChangeListenerLocal = this::axesChangedLocal; protected final ListChangeListener axesChangeListener = this::axesChanged; protected final ListChangeListener datasetChangeListener = this::datasetsChanged; @@ -677,50 +678,15 @@ public boolean isToolBarPinned() { return toolBarPinned.get(); } - private final PostLayoutHook drawPhase = new PostLayoutHook(this, this::drawChart); - - @Override - public void layoutChildren() { - if (DEBUG && LOGGER.isDebugEnabled()) { - LOGGER.debug("chart layoutChildren() - pre"); - } - if (layoutOngoing) { - return; - } - if (DEBUG && LOGGER.isDebugEnabled()) { - LOGGER.debug("chart layoutChildren() - execute"); - } + protected void runPreLayout() { final long start = ProcessingProfiler.getTimeStamp(); - layoutOngoing = true; - - // update axes range first because this may change the overall layout - updateAxisRange(); // TODO: should be done in a pre-layout hook + updateAxisRange(); // Update data ranges etc. to trigger anything that might need a layout ProcessingProfiler.getTimeDiff(start, "updateAxisRange()"); - - // update chart parent according to possible size changes - doLayout(); - - // request re-layout of canvas - drawPhase.runPostLayout(); - - ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); - - // request re-layout of plugins - layoutPluginsChildren(); - ProcessingProfiler.getTimeDiff(start, "layoutPluginsChildren()"); - - ProcessingProfiler.getTimeDiff(start, "end"); - - layoutOngoing = false; - if (DEBUG && LOGGER.isDebugEnabled()) { - LOGGER.debug("chart layoutChildren() - done"); - } - fireInvalidated(); } - private void doLayout() { - // Do layout w/ existing hierarchy - // Account for margin and border insets + @Override + public void layoutChildren() { + // Size all nodes to full size. Account for margin and border insets. final double x = snappedLeftInset(); final double y = snappedTopInset(); final double w = snapSizeX(getWidth()) - x - snappedRightInset(); @@ -728,6 +694,23 @@ private void doLayout() { for (Node child : getChildren()) { child.resizeRelocate(x, y, w, h); } + + // request re-layout of plugins + layoutPluginsChildren(); + + // Make sure things will get redrawn + state.setDirty(ChartBits.ChartCanvas); + } + + protected void runPostLayout() { + // Update the actual Canvas content + final long start = ProcessingProfiler.getTimeStamp(); + for (Axis axis : axesList) { + axis.drawAxis(); + } + redrawCanvas(); + state.clear(); + ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); } public final ObjectProperty legendProperty() { @@ -756,7 +739,7 @@ public void requestLayout() { } LOGGER.atDebug().addArgument("[..]").log("chart requestLayout() - called by {}"); } - + state.setDirty(ChartBits.ChartLayout); FXUtils.assertJavaFxThread(); super.requestLayout(); } @@ -879,14 +862,14 @@ protected void axesChangedLocal(final ListChangeListener.Change for (Axis axis : change.getRemoved()) { // remove axis invalidation listener AssertUtils.notNull("to be removed axis is null", axis); - axis.removeListener(axisChangeListener); + axis.getBitState().removeChangeListener(state); removeAxisFromChildren(axis); // TODO: don't remove if it is contained in getAxes() } for (final Axis axis : change.getAddedSubList()) { // check if axis is associated with an existing renderer, // if yes -> throw an exception AssertUtils.notNull("to be added axis is null", axis); - axis.addListener(axisChangeListener); + axis.getBitState().addChangeListener(state); addAxisToChildren(axis); } } @@ -916,26 +899,6 @@ private boolean removeAxisFromChildren(Axis axis) { return false; } - /** - * function called whenever a axis has been invalidated (e.g. range change or parameter plotting changes). Typically - * calls 'requestLayout()' but can be overwritten in derived classes. - * - * @param axisObj the calling axis object - */ - protected void axesInvalidated(final Object axisObj) { - if (!(axisObj instanceof Axis) || layoutOngoing || isAxesUpdate) { - return; - } - FXUtils.assertJavaFxThread(); - isAxesUpdate = true; - if (DEBUG && LOGGER.isDebugEnabled()) { - LOGGER.debug("chart axesInvalidated() - called by (1) {}", ProcessingProfiler.getCallingClassMethod(1)); - LOGGER.debug("chart axesInvalidated() - called by (3) {}", ProcessingProfiler.getCallingClassMethod(3)); - } - requestLayout(); - isAxesUpdate = false; - } - protected void dataSetInvalidated() { // DataSet has notified and invalidate if (DEBUG && LOGGER.isDebugEnabled()) { @@ -1018,13 +981,6 @@ protected void pluginsChanged(final ListChangeListener.Change + * 1) animations/timers, e.g., Platform.runLater() + * 2) CSS styling pass (styling etc. gets updated) + * 3) pre-layout hook + * 4) layout pass (layoutChildren) + * 5) post-layout hook + * 6) update bounds + * 7) copy dirty node changes to the rendering thread + *

+ * Drawing inside layout children is problematic because + * the layout may be recursive and can result in many + * unnecessary drawing operations. + *

+ * However, constantly keeping a layout hook is also not ideal because + * (as far as I understand) that will always trigger a JavaFX tick even + * when it is unnecessary. + *

+ * This class registers actions for just one tick and then automatically + * unregisters itself. Only the first invocation per tick has an effect. + *

+ * Note that both hooks get added and removed together to avoid issues + * where users could add a pre-layout hook in the layoutChildren method + * and end up with actions split across ticks. + * + * @author ennerf + */ +public class LayoutHook { + + public static LayoutHook newPreAndPostHook(Node node, Runnable preLayoutAction, Runnable postLayoutAction) { + return new LayoutHook(node, preLayoutAction, postLayoutAction); + } + + private LayoutHook(Node node, Runnable preLayoutAction, Runnable postLayoutAction) { + this.node = node; + this.preLayoutAction = preLayoutAction; + this.postLayoutAction = postLayoutAction; + } + + public void registerOnce() { + // Scene has changed -> remove the old one first + if (registeredScene != null && registeredScene != node.getScene()) { + unregister(); + } + // Register only if we haven't already registered + if (registeredScene == null && node.getScene() != null) { + registeredScene = node.getScene(); + registeredScene.addPreLayoutPulseListener(preLayoutAction); + registeredScene.addPostLayoutPulseListener(postLayoutAndRemove); + } + } + + private void unregister() { + registeredScene.removePreLayoutPulseListener(preLayoutAction); + registeredScene.removePostLayoutPulseListener(postLayoutAndRemove); + registeredScene = null; + } + + private void runPostlayoutAndRemove() { + postLayoutAction.run(); + unregister(); + } + + final Node node; + final Runnable preLayoutAction; + final Runnable postLayoutAction; + + final Runnable postLayoutAndRemove = this::runPostlayoutAndRemove; + Scene registeredScene = null; + +} diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/PostLayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/PostLayoutHook.java deleted file mode 100644 index 3f5c8924c..000000000 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/PostLayoutHook.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.fair_acc.chartfx.ui.utils; - -import javafx.scene.Node; -import javafx.scene.Scene; - -/** - * Utility class for registering a post-layout action - * for one run. It is intended to be registered inside - * the layoutChildren() phase and will only be run once. - * - * Drawing inside layoutChildren is problematic because - * the layout may be done multiple times and can result - * in many unnecessary drawing operations. - * - * Continuously keeping a post-layout action is also problematic - * because it (as far as I understand) forces a pulse to happen - * even if nothing needs to be drawn. - * - * @author ennerf - */ -public class PostLayoutHook { - - public PostLayoutHook(Node node, Runnable action) { - this.node = node; - this.action = action; - } - - public void runPostLayout() { - if (registeredScene != null && registeredScene != node.getScene()) { - unregister(); - } - if (registeredScene == null && node.getScene() != null) { - registeredScene = node.getScene(); - registeredScene.addPostLayoutPulseListener(pulseListener); - } - } - - // Executes immediately (for testing) - @Deprecated - public void runNow() { - action.run(); - } - - private void runListener() { - action.run(); - unregister(); - } - - private void unregister() { - registeredScene.removePostLayoutPulseListener(pulseListener); - registeredScene = null; - } - - final Node node; - final Runnable action; - - final Runnable pulseListener = this::runListener; - Scene registeredScene = null; - -} diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java index 3bcaaa39a..78e00a492 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java @@ -12,6 +12,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import io.fair_acc.dataset.events.StateListener; import javafx.application.Platform; import javafx.scene.Node; import javafx.scene.Scene; @@ -282,4 +283,16 @@ public static List sizedList(List list, int desiredSize return list; } + public static StateListener runOnFxThread(StateListener listener) { + return (src, bits) -> { + if (Platform.isFxApplicationThread()) { + listener.accept(src, bits); + } else { + Platform.runLater(() -> { + listener.accept(src, bits); + }); + } + }; + } + } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index d0e59b65f..cb9e7c534 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -10,7 +10,9 @@ public enum ChartBits implements IntSupplier { AxisCanvas, // needs to be drawn AxisRange, // anything related to min/max, tick marks, etc. AxisTickLabelText, // the tick label display w/ unit scaling - AxisLabelText; // display name or units + AxisLabelText, // display name or units + ChartLayout, + ChartCanvas; private static final ChartBits[] AllBits = ChartBits.values(); public static final int KnownMask = BitState.mask(AllBits); From b5f66e2fd3ac199630fb4e349a644f169ee8f946 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 25 Jul 2023 19:28:16 +0200 Subject: [PATCH 28/81] first massive commit for removing the old event system --- .../main/java/io/fair_acc/chartfx/Chart.java | 130 +++++---------- .../java/io/fair_acc/chartfx/XYChart.java | 4 - .../java/io/fair_acc/chartfx/axes/Axis.java | 14 -- .../chartfx/axes/spi/AbstractAxis.java | 33 ---- .../axes/spi/AbstractAxisParameter.java | 61 +------ .../chartfx/legend/spi/DefaultLegend.java | 24 +-- .../plugins/AbstractSingleValueIndicator.java | 25 +-- .../fair_acc/chartfx/plugins/EditDataSet.java | 7 +- .../fair_acc/chartfx/plugins/TableViewer.java | 147 +++++------------ .../AbstractChartMeasurement.java | 24 ++- .../renderer/spi/MountainRangeRenderer.java | 2 +- .../measurements/SimpleMeasurementsTests.java | 4 +- .../chartfx/viewer/DataViewWindowTests.java | 2 +- .../fair_acc/dataset/event/EventSource.java | 156 ++++-------------- .../io/fair_acc/dataset/events/ChartBits.java | 18 +- .../dataset/locks/DefaultDataSetLock.java | 8 - .../fair_acc/dataset/spi/AbstractDataSet.java | 73 ++------ .../dataset/spi/AbstractErrorDataSet.java | 11 -- .../dataset/spi/AbstractHistogram.java | 3 +- .../dataset/spi/AveragingDataSet.java | 6 +- .../spi/CircularDoubleErrorDataSet.java | 13 +- .../dataset/spi/DefaultAxisDescription.java | 155 ++++------------- .../dataset/spi/DimReductionDataSet.java | 5 +- .../fair_acc/dataset/spi/DoubleDataSet.java | 37 +++-- .../dataset/spi/DoubleErrorDataSet.java | 37 +++-- .../dataset/spi/DoubleGridDataSet.java | 10 +- .../dataset/spi/FifoDoubleErrorDataSet.java | 13 +- .../io/fair_acc/dataset/spi/FloatDataSet.java | 38 +++-- .../dataset/spi/FragmentedDataSet.java | 8 +- .../io/fair_acc/dataset/spi/Histogram.java | 7 +- .../io/fair_acc/dataset/spi/Histogram2.java | 3 +- .../dataset/spi/LabelledMarkerDataSet.java | 19 ++- .../spi/LimitedIndexedTreeDataSet.java | 25 ++- .../dataset/spi/MultiDimDoubleDataSet.java | 40 +++-- .../fair_acc/dataset/spi/RollingDataSet.java | 5 +- .../dataset/spi/TransposedDataSet.java | 20 +-- .../fair_acc/dataset/spi/WrappedDataSet.java | 13 +- .../dataset/testdata/TestDataSet.java | 7 - .../testdata/spi/AbstractTestFunction.java | 5 +- .../testdata/spi/ErrorTestDataSet.java | 15 +- .../event/EventAndHelperClassTests.java | 4 +- .../dataset/event/TestEventSource.java | 2 +- .../dataset/spi/DataSetBuilderTests.java | 2 +- .../testdata/spi/ErrorTestDataSetTest.java | 2 +- .../java/io/fair_acc/math/DataSetMath.java | 2 - .../java/io/fair_acc/math/MathDataSet.java | 25 +-- .../io/fair_acc/math/MathDataSetTests.java | 4 +- .../sample/chart/ChartIndicatorSample.java | 8 - .../chart/ErrorDataSetRendererSample.java | 15 -- .../sample/chart/Histogram2DimSample.java | 6 - .../fair_acc/sample/chart/LogAxisSample.java | 13 +- .../sample/chart/OscilloscopeAxisSample.java | 6 - .../sample/chart/RollingBufferSample.java | 15 -- .../chart/RollingBufferSortedTreeSample.java | 6 - .../sample/chart/SimpleChartSample.java | 8 - .../chart/SimpleInvertedChartSample.java | 8 - .../fair_acc/sample/chart/TimeAxisSample.java | 3 - .../sample/chart/VisibilityToggleSample.java | 25 +-- .../fair_acc/sample/chart/ZoomerSample.java | 7 - .../legacy/ChartHighUpdateRateSample.java | 5 +- .../sample/chart/utils/TestDataSetSource.java | 3 +- .../sample/dataset/legacy/DoubleDataSet.java | 24 ++- .../dataset/legacy/DoubleErrorDataSet.java | 19 ++- .../AbstractBasicFinancialApplication.java | 5 - .../service/SimpleOhlcvReplayDataSet.java | 5 +- .../order/PositionFinancialDataSet.java | 3 +- .../fair_acc/sample/math/TSpectrumSample.java | 3 +- 67 files changed, 471 insertions(+), 984 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index ba3c6484d..7803a71d3 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -10,6 +10,7 @@ import io.fair_acc.chartfx.ui.layout.PlotAreaPane; import io.fair_acc.chartfx.ui.*; import io.fair_acc.chartfx.ui.utils.LayoutHook; +import io.fair_acc.dataset.event.EventSource; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; import javafx.animation.Animation; @@ -55,7 +56,6 @@ import io.fair_acc.chartfx.ui.geometry.Side; import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.dataset.DataSet; -import io.fair_acc.dataset.event.EventListener; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.utils.NoDuplicatesList; import io.fair_acc.dataset.utils.ProcessingProfiler; @@ -71,11 +71,17 @@ * @author original conceptual design by Oracle (2010, 2014) * @author hbraeun, rstein, major refactoring, re-implementation and re-design */ -public abstract class Chart extends Region implements Observable { +public abstract class Chart extends Region implements EventSource { private final LayoutHook layoutHooks = LayoutHook.newPreAndPostHook(this, this::runPreLayout, this::runPostLayout); - protected final BitState state = BitState.initDirtyMultiThreaded(this, BitState.ALL_BITS) - .addChangeListener(ChartBits.KnownMask, FXUtils.runOnFxThread((src, bits) -> layoutHooks.registerOnce())) // for now trigger on everything - .addChangeListener(ChartBits.ChartCanvas, FXUtils.runOnFxThread((src, bits) -> fireInvalidated())); // for compatibility + + // The chart has two different states, one that includes everything and is only ever on the JavaFX thread, and + // a thread-safe one that receives dataSet updates and forwards them on the JavaFX thread. + protected final BitState state = BitState.initClean(this, BitState.ALL_BITS) + .addChangeListener(ChartBits.KnownMask, (src, bits) -> layoutHooks.registerOnce()) + .addChangeListener(ChartBits.ChartLayout, (src, bits) -> super.requestLayout()); + protected final BitState dataSetState = BitState.initDirtyMultiThreaded(this, BitState.ALL_BITS) + .addChangeListener(FXUtils.runOnFxThread(state)); // forward to fx state on JavaFX thread + private static final Logger LOGGER = LoggerFactory.getLogger(Chart.class); private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Control.getClassCssMetaData()); @@ -111,8 +117,6 @@ public abstract class Chart extends Region implements Observable { private final ObservableList plugins = FXCollections.observableList(new LinkedList<>()); private final ObservableList datasets = FXCollections.observableArrayList(); protected final ObservableList allDataSets = FXCollections.observableArrayList(); - protected final List listeners = new ArrayList<>(); - protected final BooleanProperty autoNotification = new SimpleBooleanProperty(this, "autoNotification", true); private final ObservableList renderers = FXCollections.observableArrayList(); { getRenderers().addListener(this::rendererChanged); @@ -190,7 +194,6 @@ private static Pane getSidePane(Side side, ChartPane parent, Map map protected final ListChangeListener axesChangeListenerLocal = this::axesChangedLocal; protected final ListChangeListener axesChangeListener = this::axesChanged; protected final ListChangeListener datasetChangeListener = this::datasetsChanged; - protected final EventListener dataSetDataListener = obs -> FXUtils.runFX(this::dataSetInvalidated); protected final ListChangeListener pluginsChangedListener = this::pluginsChanged; protected final ChangeListener windowPropertyListener = (ch1, oldWindow, newWindow) -> { if (oldWindow != null) { @@ -438,14 +441,13 @@ public Chart(Axis... axes) { } @Override - public String getUserAgentStylesheet() { - return CHART_CSS; + public BitState getBitState() { + return state; } @Override - public void addListener(final InvalidationListener listener) { - Objects.requireNonNull(listener, "InvalidationListener must not be null"); - listeners.add(listener); + public String getUserAgentStylesheet() { + return CHART_CSS; } /** @@ -461,31 +463,6 @@ public final BooleanProperty animatedProperty() { return animated; } - public BooleanProperty autoNotificationProperty() { - return autoNotification; - } - - /** - * Notifies listeners that the data has been invalidated. If the data is added to the chart, it triggers repaint. - * - * @return itself (fluent design) - */ - public Chart fireInvalidated() { - synchronized (autoNotification) { - if (!isAutoNotification() || listeners.isEmpty()) { - return this; - } - } - - if (Platform.isFxApplicationThread()) { - executeFireInvalidated(); - } else { - Platform.runLater(this::executeFireInvalidated); - } - - return this; - } - /** * @return datasets attached to the chart and datasets attached to all renderers */ @@ -659,10 +636,6 @@ public final boolean isAnimated() { return animated.get(); } - public boolean isAutoNotification() { - return autoNotification.get(); - } - public final boolean isLegendVisible() { return legendVisible.getValue(); } @@ -679,6 +652,10 @@ public boolean isToolBarPinned() { } protected void runPreLayout() { + if (state.isDirty(ChartBits.ChartLegend)) { + updateLegend(getDatasets(), getRenderers()); + } + final long start = ProcessingProfiler.getTimeStamp(); updateAxisRange(); // Update data ranges etc. to trigger anything that might need a layout ProcessingProfiler.getTimeDiff(start, "updateAxisRange()"); @@ -699,7 +676,7 @@ public void layoutChildren() { layoutPluginsChildren(); // Make sure things will get redrawn - state.setDirty(ChartBits.ChartCanvas); + fireInvalidated(ChartBits.ChartCanvas); } protected void runPostLayout() { @@ -710,9 +687,22 @@ protected void runPostLayout() { } redrawCanvas(); state.clear(); + forEachDataSet(ds -> ds.getBitState().clear()); + // TODO: plugins etc., do locking ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); } + private void forEachDataSet(Consumer action) { + for (DataSet dataset : datasets) { + action.accept(dataset); + } + for (Renderer renderer : renderers) { + for (DataSet dataset : renderer.getDatasets()) { + action.accept(dataset); + } + } + } + public final ObjectProperty legendProperty() { return legend; } @@ -725,33 +715,10 @@ public final BooleanProperty legendVisibleProperty() { return legendVisible; } - @Override - public void removeListener(final InvalidationListener listener) { - listeners.remove(listener); - } - - @Override - public void requestLayout() { - if (DEBUG && LOGGER.isDebugEnabled()) { - // normal debugDepth = 1 but for more verbose logging (e.g. recursion) use > 10 - for (int debugDepth = 1; debugDepth < 2; debugDepth++) { - LOGGER.atDebug().addArgument(debugDepth).addArgument(ProcessingProfiler.getCallingClassMethod(debugDepth)).log("chart requestLayout() - called by {}: {}"); - } - LOGGER.atDebug().addArgument("[..]").log("chart requestLayout() - called by {}"); - } - state.setDirty(ChartBits.ChartLayout); - FXUtils.assertJavaFxThread(); - super.requestLayout(); - } - public final void setAnimated(final boolean value) { animated.set(value); } - public void setAutoNotification(final boolean flag) { - autoNotification.set(flag); - } - public final void setLegend(final Legend value) { legend.set(value); } @@ -911,35 +878,16 @@ protected void dataSetInvalidated() { } protected void datasetsChanged(final ListChangeListener.Change change) { - boolean dataSetChanges = false; FXUtils.assertJavaFxThread(); while (change.next()) { for (final DataSet set : change.getRemoved()) { - // remove Legend listeners from removed datasets - set.updateEventListener().removeIf(l -> l instanceof DefaultLegend.DatasetVisibilityListener); - - set.removeListener(dataSetDataListener); - dataSetChanges = true; + set.removeListener(dataSetState); } - for (final DataSet set : change.getAddedSubList()) { - set.addListener(dataSetDataListener); - dataSetChanges = true; + set.addListener(dataSetState); } } - - if (dataSetChanges) { - if (DEBUG && LOGGER.isDebugEnabled()) { - LOGGER.debug("chart datasetsChanged(Change) - has dataset changes"); - } - // updateAxisRange(); - updateLegend(getDatasets(), getRenderers()); - requestLayout(); - } - } - - protected void executeFireInvalidated() { - new ArrayList<>(listeners).forEach(listener -> listener.invalidated(this)); + fireInvalidated(ChartBits.ChartLayout, ChartBits.ChartLegend); } /** @@ -1013,19 +961,21 @@ protected void rendererChanged(final ListChangeListener.Change set.addListener(dataSetDataListener)); + renderer.getDatasets().forEach(set -> set.addListener(dataSetState)); // TODO: how should it handle renderer axes? this can add automatically-generated axes that aren't wanted // renderer.getAxes().addListener(axesChangeListenerLocal); // renderer.getAxes().forEach(this::addAxisToChildren); + fireInvalidated(ChartBits.ChartRenderers, ChartBits.ChartDataSets); }); // handle removed renderer change.getRemoved().forEach(renderer -> { renderer.getDatasets().removeListener(datasetChangeListener); - renderer.getDatasets().forEach(set -> set.removeListener(dataSetDataListener)); + renderer.getDatasets().forEach(set -> set.removeListener(dataSetState)); // renderer.getAxes().removeListener(axesChangeListenerLocal); // renderer.getAxes().forEach(this::removeAxisFromChildren); + fireInvalidated(ChartBits.ChartRenderers, ChartBits.ChartDataSets); }); } // reset change to allow derived classes to add additional listeners to renderer changes diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index e1a4a9884..7b4c81395 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -353,7 +353,6 @@ 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); @@ -396,7 +395,6 @@ protected void redrawCanvas() { if (gridRenderer.isDrawOnTop()) { gridRenderer.render(gc, this, 0, null); } - setAutoNotification(true); if (DEBUG && LOGGER.isDebugEnabled()) { LOGGER.debug(" xychart redrawCanvas() - done"); } @@ -407,7 +405,6 @@ protected static void updateNumericAxis(final Axis axis, final List dat return; } - final boolean oldAutoState = axis.autoNotification().getAndSet(false); final Side side = axis.getSide(); final boolean isHorizontal = side.isHorizontal(); @@ -450,6 +447,5 @@ protected static void updateNumericAxis(final Axis axis, final List dat axis.getAutoRange().setAxisLength(axisLength, side); axis.getUserRange().setAxisLength(axisLength, side); - axis.autoNotification().set(oldAutoState); } } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java index d238ec09f..434ac5a93 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/Axis.java @@ -203,20 +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); - } - - public BitState getBitState(); - /** * This is true when the axis determines its range from the data automatically and grows it if necessary * diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 901da6f94..161d9bdfc 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -109,21 +109,6 @@ protected AbstractAxis() { tickLabelRotationProperty().addListener((ch, o, n) -> { updateTickLabelAlignment(); }); - - // Send out events to be backwards compatible with old event system - final AxisChangeEvent axisTransformEvent = new AxisRangeChangeEvent(this); - final AxisChangeEvent axisLabelEvent = new AxisNameChangeEvent(this); - final AxisChangeEvent otherChangeEvent = new AxisChangeEvent(this); - state.addChangeListener((source, bits) -> { - if (ChartBits.AxisRange.isSet(bits)) { - invokeListener(axisTransformEvent, false); - } else if (ChartBits.AxisLabelText.isSet(bits)) { - invokeListener(axisLabelEvent, false); - } else { - invokeListener(otherChangeEvent, false); - } - }); - } protected AbstractAxis(final double lowerBound, final double upperBound) { @@ -181,24 +166,6 @@ public void drawAxis(final GraphicsContext gc, final double axisWidth, final dou drawAxisPost(); } - /** - * Notifies listeners that the data has been invalidated. If the data is added to the chart, it triggers repaint. - */ - @Override - public void fireInvalidated() { - synchronized (autoNotification()) { - if (!autoNotification().get() || updateEventListener().isEmpty()) { - return; - } - } - - if (Platform.isFxApplicationThread()) { - this.invokeListener(new AxisChangeEvent(this), false); - } else { - Platform.runLater(() -> this.invokeListener(new AxisChangeEvent(this), false)); - } - } - public AxisLabelFormatter getAxisLabelFormatter() { return axisFormatter.get(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index ba139122a..7d401147a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -186,9 +186,6 @@ public AbstractAxisParameter() { // == majorTickMark protected static final int DEFAULT_MINOR_TICK_COUNT = 10; - private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); - private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); - private final transient StyleableIntegerProperty dimIndex = CSS.createIntegerProperty(this, "dimIndex", -1); /** @@ -433,35 +430,28 @@ protected void updateCachedVariables() { // NOPMD by rstein function can but doe @Override public boolean add(final double value) { - if (this.contains(value)) { + if (!Double.isFinite(value)) { return false; } - boolean changed = false; - final boolean oldState = autoNotification().getAndSet(false); - if (value > this.getMax()) { - this.setMax(value); - changed = true; + if ((value > getMin()) && (value < getMax())) { + return false; } - if (value < this.getMin()) { - this.setMin(value); - changed = true; + if (value < getMin()) { + setMin(value); } - autoNotification().set(oldState); - if (changed) { - invokeListener(new AxisChangeEvent(this)); + if (value > getMax()) { + setMax(value); } - return changed; + state.setDirty(ChartBits.AxisRange); + return true; } @Override public boolean add(final double[] values, final int length) { boolean changed = false; - final boolean oldState = autoNotification().getAndSet(false); for (int i = 0; i < length; i++) { changed |= add(values[i]); } - autoNotification().set(oldState); - invokeListener(new AxisChangeEvent(this)); return changed; } @@ -483,11 +473,6 @@ public BooleanProperty autoGrowRangingProperty() { return autoGrowRanging; } - @Override - public AtomicBoolean autoNotification() { - return autoNotification; - } - /** * Fraction of the range to be applied as padding on both sides of the axis range. E.g. if set to 0.1 (10%) on axis * with data range [10, 20], the new automatically calculated range will be [9, 21]. @@ -539,11 +524,8 @@ public DoubleProperty axisCenterPositionProperty() { @Override public boolean clear() { - final boolean oldState = autoNotification().getAndSet(false); minProp.set(DEFAULT_MIN_RANGE); maxProp.set(DEFAULT_MAX_RANGE); - autoNotification().set(oldState); - invokeListener(new AxisChangeEvent(this)); return false; } @@ -552,8 +534,6 @@ public boolean contains(final double value) { return Double.isFinite(value) && (value >= getMin()) && (value <= getMax()); } - public abstract void fireInvalidated(); - /** * @return value of the {@link #animationDurationProperty} property */ @@ -802,24 +782,6 @@ public BooleanProperty invertAxisProperty() { return invertAxis; } - /** - * 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 - public void invokeListener(final UpdateEvent updateEvent, final boolean executeParallel) { - synchronized (autoNotification()) { - if (!autoNotification().get()) { - // avoids duplicate update events - return; - } - } - requestAxisLayout(); - Axis.super.invokeListener(updateEvent, executeParallel); - } - /** * @return value of {@link #animatedProperty} property */ @@ -1230,11 +1192,6 @@ public DoubleProperty unitScalingProperty() { return unitScaling; } - @Override - public List updateEventListener() { - return updateListeners; - } - protected void setScale(final double scale) { scalePropertyImpl().set(scale); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java index b31c69e23..0e463eae3 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java @@ -1,9 +1,12 @@ package io.fair_acc.chartfx.legend.spi; +import java.lang.ref.PhantomReference; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import io.fair_acc.dataset.events.ChartBits; +import io.fair_acc.dataset.events.StateListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -116,27 +119,12 @@ public LegendItem getNewLegendItem(final Renderer renderer, final DataSet series var item = new LegendItem(series.getName(), symbol); item.setOnMouseClicked(event -> series.setVisible(!series.isVisible())); item.pseudoClassStateChanged(disabledClass, !series.isVisible()); - series.addListener(new DatasetVisibilityListener(item, series)); + series.getBitState().addInvalidateListener(ChartBits.DataSetVisibility, (obj, bits) -> { + item.pseudoClassStateChanged(disabledClass, !series.isVisible()); // TODO: do we need to unregister? It did not before. + }); return item; } - public static class DatasetVisibilityListener implements EventListener { - private LegendItem item; - private DataSet series; - - public DatasetVisibilityListener(final LegendItem item, final DataSet series) { - this.item = item; - this.series = series; - } - - @Override - public void handle(final UpdateEvent evt) { - if (evt instanceof UpdatedMetaDataEvent) { - item.pseudoClassStateChanged(disabledClass, !series.isVisible()); - } - } - } - @Override public Node getNode() { return this; diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractSingleValueIndicator.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractSingleValueIndicator.java index 8c6047422..1d38a283e 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractSingleValueIndicator.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractSingleValueIndicator.java @@ -4,11 +4,9 @@ package io.fair_acc.chartfx.plugins; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - +import io.fair_acc.chartfx.utils.PropUtil; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.Cursor; @@ -17,9 +15,7 @@ import javafx.scene.shape.Polygon; import io.fair_acc.chartfx.axes.Axis; -import io.fair_acc.dataset.event.EventListener; import io.fair_acc.dataset.event.EventSource; -import io.fair_acc.dataset.event.UpdateEvent; /** * Plugin indicating a specific X or Y value as a line drawn on the plot area, with an optional {@link #textProperty() @@ -38,8 +34,7 @@ public abstract class AbstractSingleValueIndicator extends AbstractValueIndicato protected static final String STYLE_CLASS_LINE = "value-indicator-line"; protected static final String STYLE_CLASS_MARKER = "value-indicator-marker"; protected static double triangleHalfWidth = 5.0; - private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); - private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); + private final transient BitState state = BitState.initDirty(this); private boolean autoRemove = false; /** @@ -108,13 +103,7 @@ protected AbstractSingleValueIndicator(Axis axis, final double value, final Stri // applied and we can calculate label's // width and height getChartChildren().addAll(line, label); - this.value.addListener( - (ch, o, n) -> invokeListener(new UpdateEvent(this, "value changed to " + n + " for axis " + axis))); - } - - @Override - public AtomicBoolean autoNotification() { - return autoNotification; + PropUtil.runOnChange(state.onAction(ChartBits.ChartPluginState), this.value); } /** @@ -299,8 +288,8 @@ public final void setValue(final double newValue) { } @Override - public List updateEventListener() { - return updateListeners; + public BitState getBitState() { + return state; } private void updateMouseListener(final boolean state) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditDataSet.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditDataSet.java index 568f60c8f..ba7851dcb 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditDataSet.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/EditDataSet.java @@ -968,9 +968,10 @@ protected class SelectedDataPoint extends Circle { setOnMouseDragOver(dragOver); // this.setOnMouseExited(dragOver); - xAxis.addListener(evt -> FXUtils.runFX(() -> this.setCenterX(getX()))); - yAxis.addListener(evt -> FXUtils.runFX(() -> this.setCenterY(getY()))); - dataSet.addListener(e -> FXUtils.runFX(this::update)); + // TODO: what are these? +// xAxis.addListener(evt -> FXUtils.runFX(() -> this.setCenterX(getX()))); +// yAxis.addListener(evt -> FXUtils.runFX(() -> this.setCenterY(getY()))); +// dataSet.addListener(e -> FXUtils.runFX(this::update)); } public void applyDrag(final double deltaX, final double deltaY) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java index a450ea83c..45f38957b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java @@ -10,17 +10,15 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; +import io.fair_acc.chartfx.utils.FXUtils; +import io.fair_acc.dataset.events.ChartBits; import javafx.beans.binding.Bindings; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.ObservableListBase; import javafx.geometry.Insets; @@ -46,14 +44,10 @@ import org.slf4j.LoggerFactory; import io.fair_acc.chartfx.Chart; -import io.fair_acc.chartfx.renderer.Renderer; -import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSetError; import io.fair_acc.dataset.EditConstraints; import io.fair_acc.dataset.EditableDataSet; -import io.fair_acc.dataset.event.EventListener; -import io.fair_acc.dataset.event.UpdateEvent; /** * Displays the all visible data sets inside a table on demand. Implements copy-paste functionality into system @@ -235,7 +229,7 @@ protected HBox getInteractorBar() { switchTableView.setGraphic(isTablePresent ? tableView : graphView); getChart().getPlotForeground().setMouseTransparent(isTablePresent); table.setMouseTransparent(isTablePresent); - dsModel.datasetsChanged(null); + dsModel.runPreLayout(); }); buttonBar.getChildren().addAll(separator, switchTableView, copyToClipBoard, saveTableView); @@ -282,23 +276,11 @@ protected class DataSetsModel extends ObservableListBase { protected static final double DEFAULT_COL_WIDTH = 150; private int nRows; private final ObservableList> columns = FXCollections.observableArrayList(); - - private long lastColumnUpdate = 0; - private final AtomicBoolean columnUpdateScheduled = new AtomicBoolean(false); - - private final ListChangeListener rendererChangeListener = this::rendererChanged; - private final InvalidationListener datasetChangeListener = this::datasetsChanged; - private final EventListener dataSetDataUpdateListener = (UpdateEvent evt) -> FXUtils.runFX(() -> this.datasetsChanged(null)); - private TimerTask timerTask; + private boolean forceNextUpdate = false; public DataSetsModel() { super(); columns.add(new RowIndexHeaderTableColumn()); - table.visibleProperty().addListener((prop, oldVal, newVal) -> { - if (Boolean.TRUE.equals(newVal)) { - datasetsChanged(null); - } - }); } @Override @@ -306,57 +288,46 @@ public String toString() { return "TableModel"; } - public void datasetsChanged(@SuppressWarnings("unused") Observable obs) { // unused parameter is needed for listener interface - if (getChart() == null) { // the plugin was removed from the chart + public void runPreLayout() { + final var chart = getChart(); + if (chart == null) { // the plugin was removed from the chart return; } if (!table.isVisible()) { return; } - long now = System.currentTimeMillis(); - if (now - lastColumnUpdate > refreshRate.get()) { - List columnsUpdated = getChart().getAllDatasets().stream().sorted(Comparator.comparing(DataSet::getName)).collect(Collectors.toList()); - int nRowsNew = 0; - for (int i = 0; i < columns.size() - 1 || i < columnsUpdated.size(); i++) { - if (i > MAX_DATASETS_IN_TABLE) { - LOGGER.atWarn().addArgument(columnsUpdated.size()).log("Limiting number of DataSets shown in Table, chart has {} DataSets."); - break; - } - if (i < columnsUpdated.size()) { - if (i >= columns.size() - 1) { - columns.add(new DataSetTableColumns()); - } - DataSet ds = columnsUpdated.get(i); - ds.removeListener(dataSetDataUpdateListener); - ds.addListener(dataSetDataUpdateListener); - ((DataSetTableColumns) columns.get(i + 1)).update(ds); - nRowsNew = Math.max(nRowsNew, ds.getDataCount()); - } else { - ((DataSetTableColumns) columns.get(i + 1)).update(null); - } - } - lastColumnUpdate = now; - if (nRows != nRowsNew) { - // Workaround, let the selection model realize, that the number of cols has changed - // in the process the selection is lost - nRows = nRowsNew; - table.setItems(null); - table.setItems(dsModel); - } else { - table.refresh(); - } + if (!forceNextUpdate && chart.getBitState().isClean(ChartBits.DataSetMask)) { + return; + } + forceNextUpdate = false; + + // TODO: what are the update conditions? We can filter by name, ds changes, etc. + + // Cap at max size + List columnsUpdated = getChart().getAllDatasets().stream().sorted(Comparator.comparing(DataSet::getName)).collect(Collectors.toList()); + if(columnsUpdated.size() > MAX_DATASETS_IN_TABLE) { + LOGGER.atWarn().addArgument(columnsUpdated.size()).log("Limiting number of DataSets shown in Table, chart has {} DataSets."); + } + var cols = FXUtils.sizedList(columns, Math.min(columnsUpdated.size(), MAX_DATASETS_IN_TABLE), DataSetTableColumns::new); + + // Update the datasets + int i = 0, nRowsNew = 0; + for (DataSet ds : columnsUpdated) { + var col = (DataSetTableColumns) cols.get(i++); + col.update(ds); + nRowsNew = Math.max(nRowsNew, ds.getDataCount()); + } + + if (nRows != nRowsNew) { + // Workaround, let the selection model realize, that the number of cols has changed + // in the process the selection is lost + nRows = nRowsNew; + table.setItems(null); + table.setItems(dsModel); } else { - if (columnUpdateScheduled.compareAndExchange(false, true)) { - timerTask = new TimerTask() { - @Override - public void run() { - columnUpdateScheduled.set(false); - FXUtils.runFX(() -> datasetsChanged(null)); - } - }; - timer.schedule(timerTask, refreshRate.get()); - } + table.refresh(); } + } /** @@ -364,27 +335,10 @@ public void run() { * @param newChart The new chart the plugin is operating on */ public void chartChanged(final Chart oldChart, final Chart newChart) { - if (oldChart != null) { - if (timerTask != null) { - timerTask.cancel(); - } - // de-register data set listeners - oldChart.getDatasets().removeListener(datasetChangeListener); - oldChart.getDatasets().forEach(dataSet -> dataSet.removeListener(dataSetDataUpdateListener)); - oldChart.getRenderers().removeListener(rendererChangeListener); - if (newChart != null) { - newChart.getRenderers() - .forEach(renderer -> renderer.getDatasets().removeListener(datasetChangeListener)); - } - } if (newChart != null) { - // register data set listeners - newChart.getDatasets().addListener(datasetChangeListener); - newChart.getDatasets().forEach(dataSet -> dataSet.addListener(dataSetDataUpdateListener)); - newChart.getRenderers().addListener(rendererChangeListener); - newChart.getRenderers().forEach(renderer -> renderer.getDatasets().addListener(datasetChangeListener)); - datasetsChanged(null); + runPreLayout(); } + forceNextUpdate = true; } @Override @@ -507,27 +461,6 @@ public boolean isEmpty() { return (nRows >= 0); } - protected void rendererChanged(final ListChangeListener.Change change) { - boolean dataSetChanges = false; - while (change.next()) { - // handle added renderer - change.getAddedSubList().forEach(renderer -> renderer.getDatasets().addListener(datasetChangeListener)); - if (!change.getAddedSubList().isEmpty()) { - dataSetChanges = true; - } - - // handle removed renderer - change.getRemoved().forEach(renderer -> renderer.getDatasets().removeListener(datasetChangeListener)); - if (!change.getRemoved().isEmpty()) { - dataSetChanges = true; - } - } - - if (dataSetChanges) { - datasetsChanged(null); - } - } - @Override public int size() { return nRows; @@ -677,7 +610,7 @@ private void updateEditableState() { } /** - * Columns for a DataSet. Manages the the nested subcolumns for the actual data and handles updates of the + * Columns for a DataSet. Manages the nested subcolumns for the actual data and handles updates of the * DataSet. * * @author akrimm diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java index 41733c89d..6d39f7612 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java @@ -7,9 +7,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import io.fair_acc.dataset.events.BitState; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -78,8 +78,6 @@ public abstract class AbstractChartMeasurement implements EventListener, EventSo public static final int DEFAULT_SMALL_AXIS = 6; // [orders of magnitude], e.g. '4' <-> [1,10000] protected final DecimalFormat formatterSmall = new DecimalFormat(FORMAT_SMALL_SCALE); protected final DecimalFormat formatterLarge = new DecimalFormat(FORMAT_LARGE_SCALE); - private final AtomicBoolean autoNotify = new AtomicBoolean(true); - private final List updateListeners = Collections.synchronizedList(new LinkedList<>()); private final CheckedValueField valueField = new CheckedValueField(); private final StringProperty title = new SimpleStringProperty(this, "title", null); private final ObjectProperty dataSet = new SimpleObjectProperty<>(this, "dataSet", null); @@ -132,7 +130,7 @@ public abstract class AbstractChartMeasurement implements EventListener, EventSo while (change.next()) { change.getRemoved().forEach(oldIndicator -> oldIndicator.removeListener(sliderChanged)); - change.getAddedSubList().stream().filter(newIndicator -> !newIndicator.updateEventListener().contains(sliderChanged)).forEach(newIndicator -> newIndicator.addListener(sliderChanged)); + change.getAddedSubList().stream().filter(newIndicator -> !newIndicator.getBitState().contains(sliderChanged)).forEach(newIndicator -> newIndicator.addListener(sliderChanged)); } }; @@ -184,11 +182,6 @@ public AbstractChartMeasurement(final ParameterMeasurements plugin, final String getMeasurementPlugin().getDataView().getVisibleChildren().add(dataViewWindow); } - @Override - public AtomicBoolean autoNotification() { - return autoNotify; - } - public ObjectProperty dataSetProperty() { return dataSet; } @@ -267,9 +260,14 @@ public StringProperty titleProperty() { return title; } + @Deprecated + private void invokeListener(Object event, boolean parallel) { + // TODO: figure out what all this does + } + @Override - public List updateEventListener() { - return updateListeners; + public BitState getBitState() { + return null; // TODO: refactor class } public DoubleProperty valueProperty() { @@ -341,7 +339,7 @@ protected void cleanUpSuperfluousIndicators() { return; } final List allIndicators = chart.getPlugins().stream().filter(p -> p instanceof AbstractSingleValueIndicator).map(p -> (AbstractSingleValueIndicator) p).collect(Collectors.toList()); - allIndicators.stream().filter((final AbstractSingleValueIndicator indicator) -> indicator.isAutoRemove() && indicator.updateEventListener().isEmpty()).forEach((final AbstractSingleValueIndicator indicator) -> getMeasurementPlugin().getChart().getPlugins().remove(indicator)); + allIndicators.stream().filter((final AbstractSingleValueIndicator indicator) -> indicator.isAutoRemove() && indicator.getBitState().isEmpty()).forEach((final AbstractSingleValueIndicator indicator) -> getMeasurementPlugin().getChart().getPlugins().remove(indicator)); } protected void updateSlider() { @@ -378,7 +376,7 @@ protected AbstractSingleValueIndicator updateSlider(final int requestedIndex) { getMeasurementPlugin().getChart().getPlugins().add(sliderIndicator); } - if (!sliderIndicator.updateEventListener().contains(sliderChanged)) { + if (!sliderIndicator.getBitState().contains(sliderChanged)) { sliderIndicator.addListener(sliderChanged); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java index d10a88394..808c565d1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java @@ -316,7 +316,7 @@ public DataSet setVisible(boolean visible) { } @Override - public List updateEventListener() { + public List getBitState() { return updateListener; } } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java index 283fefa27..21beddb15 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java @@ -190,7 +190,7 @@ public void testSimpleMeasurements() throws Exception { // NOPMD assertNotNull(field.getDataSet(), "DataSet is null for type = " + type); - field.getValueIndicators().forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(1, indicator.updateEventListener().size(), "error for type = " + type)); + field.getValueIndicators().forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(1, indicator.getBitState().size(), "error for type = " + type)); final int nXIndicators = (int) chart.getPlugins().stream().filter(p -> p instanceof XValueIndicator).count(); assertEquals(type.isVerticalMeasurement() ? type.getRequiredSelectors() : 0, nXIndicators, "error for type = " + type); final int nYIndicators = (int) chart.getPlugins().stream().filter(p -> p instanceof YValueIndicator).count(); @@ -219,7 +219,7 @@ public void testSimpleMeasurements() throws Exception { // NOPMD final List tmp = new ArrayList<>(field.getValueIndicators()); FXUtils.runAndWait(field::removeAction); - tmp.forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(0, indicator.updateEventListener().size())); + tmp.forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(0, indicator.getBitState().size())); // Assert that there are no Indicators left after removing the measurement assertEquals(0, chart.getPlugins().stream().filter(p -> p instanceof AbstractSingleValueIndicator).count(), "error for type = " + type); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java index 52721f7be..8ac6a7a80 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java @@ -57,7 +57,7 @@ public void testSetterGetter() throws InterruptedException, ExecutionException { assertEquals(content, field.getContent()); assertTrue(field.getChildren().contains(content), "content in children list"); assertNotNull(field.autoNotification()); - assertNotNull(field.updateEventListener()); + assertNotNull(field.getBitState()); assertTrue(field.isDetachableWindow()); field.setDetachableWindow(false); diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java index ac0cfafd9..b3d493756 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java @@ -1,21 +1,18 @@ package io.fair_acc.dataset.event; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.IntSupplier; -import io.fair_acc.dataset.utils.AggregateException; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.StateListener; /** * @author rstein */ @SuppressWarnings("PMD.DoNotUseThreads") // thread handling is the declared purpose of this class -public interface EventSource { +public interface EventSource extends StateListener { /** - * Adds an {@link EventListener} which will be notified whenever the {@code Observable} becomes invalid. If the same + * Adds an {@link EventListener} which will be notified whenever the {@code Observable} state changed. If the same * listener is added more than once, then it will be notified more than once. That is, no check is made to ensure * uniqueness. *

@@ -25,122 +22,15 @@ public interface EventSource { * The {@code UpdateSource} stores a strong reference to the listener which will prevent the listener from being * garbage collected and may result in a memory leak. * - * @see #removeListener(EventListener) + * @see #removeListener(StateListener) * @param listener The listener to register * @throws NullPointerException if the listener is null */ - default void addListener(EventListener listener) { - synchronized (updateEventListener()) { - Objects.requireNonNull(listener, "UpdateListener must not be null"); - if (!updateEventListener().contains(listener)) { - updateEventListener().add(listener); - } - } - } - - /** - * Set the automatic notification of invalidation listeners. In general, data sets should notify registered - * invalidation listeners, if the data in the data set has changed. Charts usually register an invalidation listener - * with the data set to be notified of any changes and update the charts. Setting the automatic notification to - * false, allows applications to prevent this behaviour, in case data sets are updated multiple times during an - * acquisition cycle but the chart update is only required at the end of the cycle. true for automatic - * notification - * - * @return the atomic boolean - */ - AtomicBoolean autoNotification(); - - /** - * invoke object within update listener list - */ - default void invokeListener() { - invokeListener(null); - } - - /** - * invoke object within update listener list - * - * @param updateEvent the event the listeners are notified with - */ - default void invokeListener(final UpdateEvent updateEvent) { - invokeListener(updateEvent, true); - } - - /** - * 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 - */ - @SuppressWarnings("PMD.NPathComplexity") // cannot be further split w/o adding unwanted further public default implementations (N.B. 'private default' ... is forbidden) - default void invokeListener(final UpdateEvent updateEvent, final boolean executeParallel) { - if (updateEventListener() == null || !isAutoNotification()) { - return; - } - List eventListener; - synchronized (updateEventListener()) { - if (!isAutoNotification() || updateEventListener() == null || updateEventListener().isEmpty()) { - return; - } - eventListener = new ArrayList<>(updateEventListener()); - } - if (!executeParallel || eventListener.size() == 1) { - // alt implementation: - final AggregateException exceptions = new AggregateException( - EventSource.class.getSimpleName() + "(NonParallel)"); - for (EventListener listener : eventListener) { - try { - listener.handle(updateEvent); - } catch (Exception e) { // NOPMD -- necessary since these are forwarded - exceptions.add(e); - } - } - if (!exceptions.isEmpty()) { - throw exceptions; - } - return; - } - - // execute event listeners in parallel - final UpdateEvent event = updateEvent == null ? new UpdateEvent(this) : updateEvent; - final AggregateException exceptions = new AggregateException( - EventSource.class.getSimpleName() + "(Parallel)"); - final ExecutorService es = EventThreadHelper.getExecutorService(); - final List> jobs = new ArrayList<>(eventListener.size()); - for (EventListener listener : eventListener) { - jobs.add(es.submit(() -> { - try { - listener.handle(event); - return Boolean.TRUE; - } catch (Exception e) { // NOPMD -- necessary since these are forwarded - exceptions.add(e); - exceptions.fillInStackTrace(); - } - return Boolean.FALSE; - })); - } - - try { - // wait for submitted tasks to complete - for (final Future future : jobs) { - future.get(); - } - } catch (final Exception e) { // NOPMD -- necessary since these are forwarded - exceptions.add(new IllegalStateException("one parallel worker thread finished execution with error", e)); - } - // all submitted tasks are completed - if (!exceptions.isEmpty()) { - throw exceptions; - } - } - - /** - * Checks it automatic notification is enabled. - * - * @return true if automatic notification is enabled - */ - default boolean isAutoNotification() { - return autoNotification().get(); + default void addListener(StateListener listener) { + // TODO: handle multithreaded changes to the listener? + Objects.requireNonNull(listener, "UpdateListener must not be null"); + getBitState().addChangeListener(listener); + getBitState().getBits(listener); // initialize to the current state } /** @@ -151,19 +41,33 @@ default boolean isAutoNotification() { * no-op. If it had been previously added then it will be removed. If it had been added more than once, then only * the first occurrence will be removed. * - * @see #addListener(EventListener) + * TODO: fix comment or behavior + * + * @see #addListener(StateListener) * @param listener The listener to remove * @throws NullPointerException if the listener is null */ - default void removeListener(EventListener listener) { - synchronized (updateEventListener()) { + default void removeListener(StateListener listener) { + synchronized (getBitState()) { Objects.requireNonNull(listener, "UpdateListener must not be null"); - updateEventListener().remove(listener); + getBitState().removeChangeListener(listener); } } + default void accept(BitState source, int bits) { + getBitState().accept(source, bits); + } + + default void fireInvalidated(IntSupplier bits) { + getBitState().setDirty(bits.getAsInt()); + } + + default void fireInvalidated(IntSupplier bit0, IntSupplier bit1) { + getBitState().setDirty(bit0.getAsInt() | bit1.getAsInt()); + } + /** * @return list containing all update event listener (needs to be provided by implementing class) */ - List updateEventListener(); + BitState getBitState(); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index cb9e7c534..52682981e 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -11,12 +11,28 @@ public enum ChartBits implements IntSupplier { AxisRange, // anything related to min/max, tick marks, etc. AxisTickLabelText, // the tick label display w/ unit scaling AxisLabelText, // display name or units + AxisDescriptionRange, + AxisDescriptionName, + ChartLayout, - ChartCanvas; + ChartCanvas, + ChartAxes, + ChartRenderers, + ChartDataSets, + ChartLegend, + ChartPlugins, + ChartPluginState, + DataSetVisibility, + DataSetData, + DataSetRange, + DataSetMetaData, + DataSetPermutation, + DataViewWindow; // TODO: WindowMinimisedEvent/WindowMaximisedEvent/... necessary? private static final ChartBits[] AllBits = ChartBits.values(); public static final int KnownMask = BitState.mask(AllBits); public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickLabelText, AxisLabelText); + public static final int DataSetMask = BitState.mask(ChartDataSets, DataSetVisibility, DataSetData, DataSetRange, DataSetMetaData, DataSetPermutation); public static StateListener printer() { return PRINTER; diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/locks/DefaultDataSetLock.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/locks/DefaultDataSetLock.java index bffcc81f0..dab471bfe 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/locks/DefaultDataSetLock.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/locks/DefaultDataSetLock.java @@ -56,7 +56,6 @@ public class DefaultDataSetLock implements DataSetLock { private final AtomicLong writerLockedByThreadId = new AtomicLong(-1L); private final AtomicInteger readerCount = new AtomicInteger(0); private final AtomicInteger writerCount = new AtomicInteger(0); - private final transient AtomicBoolean autoNotifyState = new AtomicBoolean(true); private final transient D dataSet; /** @@ -215,7 +214,6 @@ public D writeLock() { // acquired lock writerLockedByThreadId.set(callingThreadId); lastWriteStamp.set(stamp); - autoNotifyState.set(dataSet.autoNotification().getAndSet(false)); } // we acquired a new lock or are already owner of a previously acquired lock writerCount.incrementAndGet(); @@ -225,11 +223,9 @@ public D writeLock() { @Override public D writeLockGuard(final Runnable writing) { // NOPMD -- runnable not used in a thread context writeLock(); - final boolean oldAutoNotificationState = dataSet.autoNotification().getAndSet(false); try { writing.run(); } finally { - dataSet.autoNotification().set(oldAutoNotificationState); writeUnLock(); } return dataSet; @@ -238,13 +234,10 @@ public D writeLockGuard(final Runnable writing) { // NOPMD -- runnable not used @Override public R writeLockGuard(final Supplier writing) { writeLock(); - final boolean oldAutoNotificationState = dataSet.autoNotification().getAndSet(false); - R result; try { result = writing.get(); } finally { - dataSet.autoNotification().set(oldAutoNotificationState); writeUnLock(); } return result; @@ -259,7 +252,6 @@ public D writeUnLock() { } // restore present auto-notify state - dataSet.autoNotification().set(autoNotifyState.get()); writerLockedByThreadId.set(-1L); stampedLock.unlockWrite(lastWriteStamp.getAndSet(-1L)); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java index a6b5a0996..ce374026e 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java @@ -2,17 +2,15 @@ import java.util.Map; import java.util.List; -import java.util.LinkedList; import java.util.ArrayList; import java.util.Objects; -import java.util.Collections; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.IntSupplier; import java.util.function.IntToDoubleFunction; import io.fair_acc.dataset.*; -import io.fair_acc.dataset.event.*; -import io.fair_acc.dataset.event.EventListener; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.locks.DataSetLock; import io.fair_acc.dataset.locks.DefaultDataSetLock; import io.fair_acc.dataset.spi.utils.MathUtils; @@ -37,12 +35,11 @@ public abstract class AbstractDataSet> extends Abs private static final long serialVersionUID = -7612136495756923417L; private static final String[] DEFAULT_AXES_NAME = { "x-Axis", "y-Axis", "z-Axis" }; - private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); private String name; protected final int dimension; private boolean isVisible = true; private final List axesDescriptions = new ArrayList<>(); - private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); + private final transient BitState state = BitState.initDirty(this); private final transient DataSetLock lock = new DefaultDataSetLock<>(this); private final StringHashMapList dataLabels = new StringHashMapList(); private final StringHashMapList dataStyles = new StringHashMapList(); @@ -51,29 +48,6 @@ public abstract class AbstractDataSet> extends Abs private final List errorList = new ArrayList<>(); private transient EditConstraints editConstraints; private final Map metaInfoMap = new ConcurrentHashMap<>(); - private final transient AtomicBoolean axisUpdating = new AtomicBoolean(false); - protected final transient io.fair_acc.dataset.event.EventListener axisListener = e -> { - if (!isAutoNotification() || !(e instanceof AxisChangeEvent) || axisUpdating.get()) { - return; - } - - axisUpdating.set(true); - final AxisChangeEvent evt = (AxisChangeEvent) e; - final int axisDim = evt.getDimension(); - AxisDescription axisdescription = getAxisDescription(axisDim); - - if (!axisdescription.isDefined() && evt instanceof AxisRecomputationEvent) { - recomputeLimits(axisDim); - // do not invoke this listener as there is no actual update to the data - // invokeListener(new AxisRangeChangeEvent(this, "updated axis range for '" + axisdescription.getName() + "' '[" + axisdescription.getUnit() + "]'", axisDim)); - axisUpdating.set(false); - return; - } - - // forward axis description event to DataSet listener - invokeListener(e); - axisUpdating.set(false); - }; /** * default constructor @@ -89,8 +63,7 @@ public AbstractDataSet(final String name, final int dimension) { for (int i = 0; i < this.dimension; i++) { final String axisName = i < DEFAULT_AXES_NAME.length ? DEFAULT_AXES_NAME[i] : "dim" + (i + 1) + "-Axis"; final AxisDescription axisDescription = new DefaultAxisDescription(i, axisName, "a.u."); - axisDescription.autoNotification().set(false); - axisDescription.addListener(axisListener); + axisDescription.addListener(state); axesDescriptions.add(axisDescription); } } @@ -105,7 +78,7 @@ public AbstractDataSet(final String name, final int dimension) { */ public String addDataLabel(final int index, final String label) { final String retVal = lock().writeLockGuard(() -> dataLabels.put(index, label)); - fireInvalidated(new UpdatedMetaDataEvent(this, "added label")); + fireInvalidated(ChartBits.DataSetMetaData); return retVal; } @@ -119,15 +92,10 @@ public String addDataLabel(final int index, final String label) { */ public String addDataStyle(final int index, final String style) { final String retVal = lock().writeLockGuard(() -> dataStyles.put(index, style)); - fireInvalidated(new UpdatedMetaDataEvent(this, "added style")); + fireInvalidated(ChartBits.DataSetMetaData); return retVal; } - @Override - public AtomicBoolean autoNotification() { - return autoNotification; - } - protected int binarySearch(final int dimIndex, final double search, final int indexMin, final int indexMax) { if (indexMin == indexMax) { return indexMin; @@ -153,7 +121,8 @@ public D clearMetaInfo() { infoList.clear(); warningList.clear(); errorList.clear(); - return fireInvalidated(new UpdatedMetaDataEvent(this, "cleared meta data")); + fireInvalidated(ChartBits.DataSetMetaData); + return getThis(); } /** @@ -385,22 +354,11 @@ public boolean isVisible() { public D setVisible(boolean visible) { if (visible != isVisible) { isVisible = visible; - fireInvalidated(new UpdatedMetaDataEvent(this, "changed visibility")); + fireInvalidated(ChartBits.DataSetVisibility); } return getThis(); } - /** - * Notifies listeners that the data has been invalidated. If the data is added to the chart, it triggers repaint. - * - * @param event the change event - * @return itself (fluent design) - */ - public D fireInvalidated(final UpdateEvent event) { - invokeListener(event); - return getThis(); - } - /** * @return axis descriptions of the primary and secondary axes */ @@ -518,7 +476,7 @@ public DataSetLock lock() { */ public String removeDataLabel(final int index) { final String retVal = lock().writeLockGuard(() -> dataLabels.remove(index)); - fireInvalidated(new UpdatedMetaDataEvent(this, "removed label")); + fireInvalidated(ChartBits.DataSetMetaData); return retVal; } @@ -531,13 +489,14 @@ public String removeDataLabel(final int index) { */ public String removeStyle(final int index) { final String retVal = lock().writeLockGuard(() -> dataStyles.remove(index)); - fireInvalidated(new UpdatedMetaDataEvent(this, "removed style")); + fireInvalidated(ChartBits.DataSetMetaData); return retVal; } public D setEditConstraints(final EditConstraints constraints) { lock().writeLockGuard(() -> editConstraints = constraints); - return fireInvalidated(new UpdatedMetaDataEvent(this, "new edit constraints")); + fireInvalidated(ChartBits.DataSetMetaData); + return getThis(); } /** @@ -675,8 +634,8 @@ public DataSet recomputeLimits(final int dimIndex) { } @Override - public synchronized List updateEventListener() { - return updateListeners; + public BitState getBitState() { + return state; } protected boolean copyMetaData(final DataSet other) { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractErrorDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractErrorDataSet.java index 69cdbcaf1..65619db01 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractErrorDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractErrorDataSet.java @@ -37,12 +37,6 @@ protected AbstractErrorDataSet(final String name, final int dimension, final Err this.errorType = errorTypes; } - @Override - public D fireInvalidated(final UpdateEvent event) { - super.fireInvalidated(event); - return getThis(); - } - /** * return the DataSetError.ErrorType of the dataset * @@ -59,11 +53,6 @@ protected D getThis() { return (D) this; } - @Override - public DataSetLock lock() { - return (DataSetLock) super.lock(); - } - /** * Computes limits (ranges) of this DataSet including data point errors. */ diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java index d5d682ece..c2ee88ce3 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java @@ -5,6 +5,7 @@ import java.util.Arrays; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.Histogram; @@ -150,7 +151,7 @@ public void addBinContent(final int bin, final double w) { data[bin] = data[bin] + w; getAxisDescription(this.getDimension() - 1).add(data[bin]); }); - fireInvalidated(new UpdatedDataEvent(this, "addBinContent()")); + fireInvalidated(ChartBits.DataSetData); } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java index 32eb43dae..42ee0cdcc 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java @@ -6,6 +6,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; +import io.fair_acc.dataset.events.ChartBits; /** * TODO: Change to ErrorDataSet and calculate standard deviation. @@ -68,7 +69,7 @@ public void add(DataSet ds) { } dataset.recomputeLimits(DIM_X); dataset.recomputeLimits(DIM_Y); - fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); } /** @@ -228,6 +229,7 @@ public DataSet set(final DataSet other, final boolean copy) { recomputeLimits(dim); } })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java index 1a34c4c97..893950acf 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java @@ -4,6 +4,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.utils.CircularBuffer; import io.fair_acc.dataset.utils.DoubleCircularBuffer; @@ -94,7 +95,8 @@ public CircularDoubleErrorDataSet add(final double x, final double y, final doub getAxisDescription(DIM_Y).clear(); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -155,7 +157,8 @@ public CircularDoubleErrorDataSet add(final double[] xVals, final double[] yVals getAxisDescription(DIM_Y).clear(); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -224,7 +227,8 @@ public CircularDoubleErrorDataSet reset() { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -246,6 +250,7 @@ public DataSet set(final DataSet other, final boolean copy) { copyDataLabelsAndStyles(other, copy); copyAxisDescription(other); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java index 835695274..25b512cb4 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java @@ -7,6 +7,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import io.fair_acc.dataset.AxisDescription; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.MathUtils; import io.fair_acc.dataset.event.AxisChangeEvent; import io.fair_acc.dataset.event.AxisNameChangeEvent; @@ -20,8 +22,9 @@ * @author rstein */ public class DefaultAxisDescription extends DataRange implements AxisDescription { - private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); - private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); + private final transient BitState state = BitState.initDirty(this); + private final Runnable notifyRangeChanged = state.onAction(ChartBits.AxisDescriptionRange); + private final Runnable notifyNameChanged = state.onAction(ChartBits.AxisDescriptionName); private final int dimIndex; private String name; private String unit; @@ -73,44 +76,6 @@ public DefaultAxisDescription(final int dimIndex, final String axisName, final S this.set(axisName, axisUnit, rangeMin, rangeMax); } - /** - * Adds value to this range. - * - * @param value value to be added - * @return true if the value becomes min or max. - */ - @Override - public boolean add(final double value) { - if (!super.add(value)) { - return false; - } - - notifyRangeChange(); - return true; - } - - /** - * Adds values to this range. - * - * @param values values to be added - * @param length the maximum array length that should be taken into account - * @return true if the value becomes min or max. - */ - @Override - public boolean add(final double[] values, final int length) { - if (!super.add(values, length)) { - return false; - } - - notifyRangeChange(); - return true; - } - - @Override - public AtomicBoolean autoNotification() { - return autoNotification; - } - @Override public boolean equals(final Object obj) { if (!(obj instanceof AxisDescription)) { @@ -166,36 +131,6 @@ public final int getDimIndex() { return dimIndex; } - @Override - public final double getMax() { - if (this.isMaxDefined()) { - return super.getMax(); - } - // axis range min value is invalid -- attempt to recompute - // the recomputeLimits is usually recomputed when validating the axis, - // this function is called in case e.g. a point has been modified and range invalidated - final boolean oldNotifyState = autoNotification.getAndSet(true); - invokeListener(new AxisRecomputationEvent(this, updateMessage(), getDimIndex())); - autoNotification.getAndSet(oldNotifyState); - - return super.getMax(); - } - - @Override - public final double getMin() { - if (this.isMinDefined()) { - return super.getMin(); - } - // axis range min value is invalid -- attempt to recompute - // the recomputeLimits is usually recomputed when validating the axis, - // this function is called in case e.g. a point has been modified and range invalidated - final boolean oldNotifyState = autoNotification.getAndSet(true); - invokeListener(new AxisRecomputationEvent(this, updateMessage(), getDimIndex())); - autoNotification.getAndSet(oldNotifyState); - - return super.getMin(); - } - @Override public final String getName() { return name; @@ -217,31 +152,11 @@ public int hashCode() { return result; } - @Override - public boolean set(final DataRange range) { - if (!super.set(range)) { - return false; - } - notifyRangeChange(); - return true; - } - - @Override - public boolean set(final double min, final double max) { - final boolean a = super.setMin(min); - final boolean b = super.setMax(max); - if (a || b) { - notifyRangeChange(); - return true; - } - return false; - } - @Override public final boolean set(final String axisName, final String... axisUnit) { boolean namesHaveChanged = !strEqual(name, axisName); name = axisName; - if ((axisUnit != null) && (axisUnit.length > 0) && !strEqual(unit, axisUnit[0])) { + if ((axisUnit.length > 0) && !strEqual(unit, axisUnit[0])) { unit = axisUnit[0]; namesHaveChanged = true; if (axisUnit.length > 1) { @@ -249,7 +164,7 @@ public final boolean set(final String axisName, final String... axisUnit) { } } if (namesHaveChanged) { - notifyNameChange(); + notifyNameChanged.run(); } return false; } @@ -257,23 +172,23 @@ public final boolean set(final String axisName, final String... axisUnit) { @Override public final boolean set(final String axisName, final String axisUnit, final double rangeMin, final double rangeMax) { - final boolean namesHaveChanged = !strEqual(name, axisName) || !strEqual(unit, axisUnit); - name = axisName; - unit = axisUnit; - - boolean rangeHasChanged = false; - if ((getMin() != rangeMin) || (getMax() != rangeMax)) { - rangeHasChanged = true; - set(rangeMin, rangeMax); - } + boolean rangeChanged = set(rangeMin, rangeMax); + boolean nameChanged = set(axisName, axisUnit); + return rangeChanged || nameChanged; + } - if (namesHaveChanged && rangeHasChanged) { - notifyFullChange(); - } else if (namesHaveChanged) { - notifyNameChange(); - } else if (rangeHasChanged) { - notifyRangeChange(); + /** + * Adds value to this range. + * + * @param value value to be added + * @return true if the value becomes min or max. + */ + @Override + public boolean add(final double value) { + if (!super.add(value)) { + return false; } + notifyRangeChanged.run(); return true; } @@ -282,8 +197,9 @@ public boolean setMax(final double max) { if (!super.setMax(max)) { return false; } - - notifyRangeChange(); + if(notifyRangeChanged != null) { // called from parent initializer + notifyRangeChanged.run(); + } return true; } @@ -292,8 +208,9 @@ public boolean setMin(final double min) { if (!super.setMin(min)) { return false; } - - notifyRangeChange(); + if(notifyRangeChanged != null) { // called from parent initializer + notifyRangeChanged.run(); + } return true; } @@ -303,20 +220,8 @@ public String toString() { } @Override - public List updateEventListener() { - return updateListeners; - } - - private void notifyFullChange() { - invokeListener(new AxisChangeEvent(this, "updated axis for '" + name + "' '[" + unit + "]'", getDimIndex())); - } - - private void notifyNameChange() { - invokeListener(new AxisNameChangeEvent(this, "updated axis names for '" + name + "' '[" + unit + "]'", getDimIndex())); - } - - private void notifyRangeChange() { - invokeListener(new AxisRangeChangeEvent(this, updateMessage(), getDimIndex())); + public BitState getBitState() { + return state; } private String updateMessage() { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java index b7808aff4..f0e1d1af9 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java @@ -6,6 +6,7 @@ import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSetMetaData; import io.fair_acc.dataset.GridDataSet; +import io.fair_acc.dataset.events.ChartBits; /** * Reduces 3D data to 2D DataSet either via slicing, min, mean, max or integration @@ -92,10 +93,8 @@ public void handle(UpdateEvent event) { return; } // recompute min/max indices based on actual new value range - final boolean oldValue = source.autoNotification().getAndSet(false); minIndex = source.getGridIndex(dimIndex == DIM_X ? DIM_Y : DIM_X, minValue); maxIndex = source.getGridIndex(dimIndex == DIM_X ? DIM_Y : DIM_X, maxValue); - source.autoNotification().set(oldValue); switch (reductionOption) { case MIN: @@ -117,7 +116,7 @@ public void handle(UpdateEvent event) { } })); - this.fireInvalidated(new AddedDataEvent(this, "updated " + DimReductionDataSet.class.getSimpleName() + " name = " + this.getName())); + this.fireInvalidated(ChartBits.DataSetData); } public void setMaxValue(final double val) { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java index ff84551b5..d23e1819f 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java @@ -4,6 +4,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet2D; @@ -112,7 +113,8 @@ public DoubleDataSet add(final double x, final double y, final String label) { getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - return fireInvalidated(new UpdatedDataEvent(this, "add")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -138,7 +140,8 @@ public DoubleDataSet add(final double[] xValuesNew, final double[] yValuesNew) { getAxisDescription(DIM_Y).add(yValuesNew); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -185,7 +188,8 @@ public DoubleDataSet add(final int index, final double x, final double y, final getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -211,7 +215,8 @@ public DoubleDataSet add(final int index, final double[] x, final double[] y) { getDataLabelMap().shiftKeys(indexAt, xValues.size()); getDataStyleMap().shiftKeys(indexAt, xValues.size()); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -229,7 +234,8 @@ public DoubleDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this, "clearData()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -301,7 +307,8 @@ public DoubleDataSet remove(final int fromIndex, final int toIndex) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -315,7 +322,8 @@ public DoubleDataSet resize(final int size) { xValues.size(size); yValues.size(size); }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -335,7 +343,8 @@ public DoubleDataSet set(final DataSet other, final boolean copy) { copyDataLabelsAndStyles(other, copy); copyAxisDescription(other); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -412,7 +421,8 @@ public DoubleDataSet set(final double[] xValues, final double[] yValues, final i // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -448,7 +458,8 @@ public DoubleDataSet set(final int index, final double x, final double y) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this, "set - single")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } public DoubleDataSet set(final int index, final double[] x, final double[] y) { @@ -462,7 +473,8 @@ public DoubleDataSet set(final int index, final double[] x, final double[] y) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this, "set - via arrays")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -476,6 +488,7 @@ public DoubleDataSet trim() { xValues.trim(0); yValues.trim(0); }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java index b6686cf16..1dc25d4b6 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java @@ -4,6 +4,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet2D; @@ -137,7 +138,8 @@ public DoubleErrorDataSet add(final double x, final double y, final double yErro getAxisDescription(DIM_Y).add(y - yErrorNeg); getAxisDescription(DIM_Y).add(y + yErrorPos); }); - return fireInvalidated(new UpdatedDataEvent(this, "add")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -169,7 +171,8 @@ public DoubleErrorDataSet add(final double[] xValuesNew, final double[] yValuesN getAxisDescription(DIM_X).add(xValuesNew); getAxisDescription(DIM_Y).add(yValuesNew); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -238,7 +241,8 @@ public DoubleErrorDataSet add(final int index, final double x, final double y, f getAxisDescription(DIM_Y).add(y - yErrorNeg); getAxisDescription(DIM_Y).add(y + yErrorPos); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -275,7 +279,8 @@ public DoubleErrorDataSet add(final int index, final double[] x, final double[] getDataStyleMap().shiftKeys(indexAt, xValues.size()); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -295,7 +300,8 @@ public DoubleErrorDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this, "clearData()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -383,7 +389,8 @@ public DoubleErrorDataSet remove(final int fromIndex, final int toIndex) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -399,7 +406,8 @@ public DoubleErrorDataSet resize(final int size) { yErrorsPos.size(size); yErrorsNeg.size(size); }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -425,7 +433,8 @@ public DoubleErrorDataSet set(final DataSet other, final boolean copy) { copyDataLabelsAndStyles(other, copy); copyAxisDescription(other); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -525,7 +534,8 @@ public DoubleErrorDataSet set(final double[] xValues, final double[] yValues, fi // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -583,7 +593,8 @@ public DoubleErrorDataSet set(final int index, final double x, final double y, f getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this, "set - single")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } public DoubleErrorDataSet set(final int index, final double[] x, final double[] y, final double[] yErrorNeg, final double[] yErrorPos) { @@ -599,7 +610,8 @@ public DoubleErrorDataSet set(final int index, final double[] x, final double[] // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this, "set - via arrays")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -615,6 +627,7 @@ public DoubleErrorDataSet trim() { yErrorsPos.trim(0); yErrorsNeg.trim(0); }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java index 8764f8d9e..0032c4d1b 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java @@ -7,6 +7,7 @@ import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet3D; import io.fair_acc.dataset.GridDataSet; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.MultiArrayDouble; /** @@ -192,7 +193,7 @@ public void set(final boolean copy, final double[][] grid, final double[]... val values[i - shape.length] = MultiArrayDouble.wrap(copy ? vals[i - shape.length].clone() : vals[i - shape.length], 0, containerShape); } }); - fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); } @Override @@ -244,8 +245,8 @@ public GridDataSet set(final DataSet another, final boolean copy) { } })); - fireInvalidated(new UpdatedDataEvent(this)); - return this; + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -258,7 +259,8 @@ public GridDataSet set(final DataSet another, final boolean copy) { */ public GridDataSet set(int dimIndex, int[] indices, double value) { lock().writeLockGuard(() -> values[dimIndex - shape.length].set(indices, value)); - return fireInvalidated(new UpdatedDataEvent(this, "set x_" + dimIndex + Arrays.toString(indices) + " = " + value)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } public void clearData() { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java index 2761ef8b7..2262fff39 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java @@ -7,6 +7,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.DoublePointError; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.utils.LimitedQueue; @@ -106,8 +107,7 @@ public FifoDoubleErrorDataSet add(final double x, final double y, final double y // remove old fields if necessary expire(x); }); - fireInvalidated(new AddedDataEvent(this)); - + fireInvalidated(ChartBits.DataSetData); return this; } @@ -151,7 +151,7 @@ public FifoDoubleErrorDataSet add(final double[] xValues, final double[] yValues this.add(xValues[i], yValues[i], yErrorsNeg[i], yErrorsPos[i]); } }); - fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); return this; } @@ -181,7 +181,7 @@ public int expire(final double now) { return toRemoveList.size(); }); if (dataPointsToRemove != 0) { - fireInvalidated(new RemovedDataEvent(this, "expired data")); + fireInvalidated(ChartBits.DataSetData); } return dataPointsToRemove; } @@ -235,7 +235,7 @@ public String getStyle(final int index) { */ public void reset() { data.clear(); - fireInvalidated(new RemovedDataEvent(this, "reset")); + fireInvalidated(ChartBits.DataSetData); } /** @@ -281,6 +281,7 @@ public DataSet set(final DataSet other, final boolean copy) { copyDataLabelsAndStyles(other, copy); copyAxisDescription(other); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java index 64c0629fc..6ac2f456b 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java @@ -4,6 +4,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.MathUtils; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; @@ -114,7 +115,8 @@ public FloatDataSet add(final float x, final float y, final String label) { getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -143,7 +145,8 @@ public FloatDataSet add(final float[] xValuesNew, final float[] yValuesNew) { getAxisDescription(DIM_Y).add(v); } }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -190,7 +193,8 @@ public FloatDataSet add(final int index, final float x, final float y, final Str getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -219,7 +223,8 @@ public FloatDataSet add(final int index, final float[] x, final float[] y) { getDataLabelMap().shiftKeys(indexAt, xValues.size()); getDataStyleMap().shiftKeys(indexAt, xValues.size()); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -237,7 +242,8 @@ public FloatDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this, "clearData()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -305,7 +311,8 @@ public FloatDataSet remove(final int fromIndex, final int toIndex) { // -> fireInvalidated calls computeLimits for autoNotification this.getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -319,7 +326,8 @@ public FloatDataSet resize(final int size) { xValues.size(size); yValues.size(size); }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -345,7 +353,8 @@ public FloatDataSet set(final DataSet other, final boolean copy) { copyDataLabelsAndStyles(other, copy); copyAxisDescription(other); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -421,7 +430,8 @@ public FloatDataSet set(final float[] xValues, final float[] yValues, final int // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -448,8 +458,8 @@ public FloatDataSet set(final int index, final double x, final double y) { // -> fireInvalidated calls computeLimits for autoNotification getAxisDescriptions().forEach(AxisDescription::clear); }); - - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } public FloatDataSet set(final int index, final double[] x, final double[] y) { @@ -463,7 +473,8 @@ public FloatDataSet set(final int index, final double[] x, final double[] y) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -477,7 +488,8 @@ public FloatDataSet trim() { xValues.trim(0); yValues.trim(0); }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java index 9d0ed0454..9dafbbb88 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java @@ -8,6 +8,7 @@ import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet2D; +import io.fair_acc.dataset.events.ChartBits; /** * @author braeun @@ -38,7 +39,7 @@ public void add(final DataSet set) { getAxisDescription(DIM_Y).add(set.getAxisDescription(DIM_Y).getMax()); getAxisDescription(DIM_Y).add(set.getAxisDescription(DIM_Y).getMin()); }); - fireInvalidated(new AddedDataEvent(this, "added data set")); + fireInvalidated(ChartBits.DataSetData); } /** @@ -63,7 +64,7 @@ public void clear() { lock().writeLockGuard(() -> { dataCount = 0; list.clear(); - fireInvalidated(new UpdatedDataEvent(this, "clear()")); + fireInvalidated(ChartBits.DataSetData); }); } @@ -133,6 +134,7 @@ public DataSet set(final DataSet other, final boolean copy) { copyDataLabelsAndStyles(other, copy); copyAxisDescription(other); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java index 55f33fb50..c8913b142 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java @@ -9,6 +9,7 @@ import io.fair_acc.dataset.Histogram1D; import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; /** @@ -89,7 +90,7 @@ public void addBinContent(final int bin, final double w) { getAxisDescription(getDimension() - 1).add(data[bin]); } }); - fireInvalidated(new UpdatedDataEvent(this, "addBinContent()")); + fireInvalidated(ChartBits.DataSetData); } @Override @@ -99,7 +100,7 @@ public int fill(final double x, final double w) { addBinContent(bin, w); return bin; }); - fireInvalidated(new AddedDataEvent(this, "fill(double x, double w)")); + fireInvalidated(ChartBits.DataSetData); return retVal; } @@ -110,7 +111,7 @@ public void fillN(double[] x, double[] w, int stepSize) { this.fill(x[i], w[i]); } }); - fireInvalidated(new AddedDataEvent(this, "fillN")); + fireInvalidated(ChartBits.DataSetData); } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java index 30bfb1b57..41fbc8313 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java @@ -9,6 +9,7 @@ import io.fair_acc.dataset.Histogram1D; import io.fair_acc.dataset.Histogram2D; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; /** @@ -61,7 +62,7 @@ public int fill(double x, double y, double w) { super.addBinContent(bin, w); return bin; }); - fireInvalidated(new UpdatedDataEvent(this, "fill()")); + fireInvalidated(ChartBits.DataSetData); return ret; } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java index f078b5478..539bc404a 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java @@ -16,6 +16,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.DoublePoint; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; @@ -51,8 +52,8 @@ public LabelledMarkerDataSet add(LabelledMarker marker) { dataLabels.add(marker.getLabel()); dataStyles.add(marker.getStyle()); }); - fireInvalidated(new AddedDataEvent(this)); - return this; + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -66,7 +67,8 @@ public LabelledMarkerDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this, "clear")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -148,7 +150,8 @@ public LabelledMarkerDataSet remove(final int fromIndex, final int toIndex) { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -166,8 +169,8 @@ public LabelledMarkerDataSet set(int index, LabelledMarker marker) { dataLabels.set(index, marker.getLabel()); dataStyles.set(index, marker.getStyle()); }); - fireInvalidated(new UpdatedDataEvent(this)); - return this; + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -203,8 +206,8 @@ public LabelledMarkerDataSet set(List markers) { dataStyles.add(marker.getStyle()); } }); - fireInvalidated(new UpdatedDataEvent(this, "fill")); - return this; + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java index 95555ccd8..acad86a22 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java @@ -8,6 +8,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.utils.trees.IndexedNavigableSet; import io.fair_acc.dataset.utils.trees.IndexedTreeSet; @@ -98,7 +99,8 @@ public LimitedIndexedTreeDataSet add(final double x, final double y, final doubl getAxisDescription(DIM_Y).add(y + ey); expire(); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -147,7 +149,8 @@ public LimitedIndexedTreeDataSet add(final double[] xValues, final double[] yVal } expire(); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -190,7 +193,8 @@ public LimitedIndexedTreeDataSet clearData() { data.clear(); getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this, "clear")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -337,7 +341,8 @@ public LimitedIndexedTreeDataSet remove(final int fromIndex, final int toIndex) getAxisDescription(DIM_X).setMax(Double.NaN); getAxisDescription(DIM_Y).setMax(Double.NaN); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -362,7 +367,8 @@ public LimitedIndexedTreeDataSet remove(final int[] indices) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -453,7 +459,8 @@ public LimitedIndexedTreeDataSet set(final double[] xValues, final double[] yVal } expire(); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -521,7 +528,8 @@ public LimitedIndexedTreeDataSet set(final int index, final double x, final doub getAxisDescription(DIM_Y).add(y + dy); expire(); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -548,7 +556,8 @@ public DataSet set(final DataSet other, final boolean copy) { super.copyMetaData(other); super.copyAxisDescription(other); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java index 5a0ab612d..89032e9d0 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java @@ -4,6 +4,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.EditableDataSet; @@ -129,7 +130,8 @@ public MultiDimDoubleDataSet add(final double[] newValues, final String label) { addDataLabel(this.values[0].size() - 1, label); } }); - return fireInvalidated(new UpdatedDataEvent(this, "add")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -152,7 +154,8 @@ public MultiDimDoubleDataSet add(final double[][] valuesNew) { } }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -189,7 +192,8 @@ public MultiDimDoubleDataSet add(final int index, final double[] newValues, fina getDataLabelMap().addValueAndShiftKeys(indexAt, this.values[0].size(), label); getDataStyleMap().shiftKeys(indexAt, this.values[0].size()); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -216,7 +220,8 @@ public MultiDimDoubleDataSet add(final int index, final double[][] newValues) { getDataLabelMap().shiftKeys(indexAt, this.values[0].size()); getDataStyleMap().shiftKeys(indexAt, this.values[0].size()); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -235,7 +240,8 @@ public MultiDimDoubleDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this, "clearData()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -308,7 +314,8 @@ public MultiDimDoubleDataSet remove(final int fromIndex, final int toIndex) { // -> fireInvalidated calls computeLimits for autoNotification getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -323,7 +330,8 @@ public MultiDimDoubleDataSet resize(final int size) { value.size(size); } }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -368,7 +376,8 @@ public MultiDimDoubleDataSet set(final DataSet other, final boolean copy) { } setStyle(other.getStyle()); })); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -436,7 +445,8 @@ public MultiDimDoubleDataSet set(final double[][] values, final int dataCount, f // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -465,7 +475,8 @@ public MultiDimDoubleDataSet setValues(final int dimIndex, final double[] values // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -490,7 +501,8 @@ public MultiDimDoubleDataSet set(final int index, final double... newValue) { // -> fireInvalidated calls computeLimits for autoNotification getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this, "set - single")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -514,7 +526,8 @@ public MultiDimDoubleDataSet set(final int index, final double[][] values) { // -> fireInvalidated calls computeLimits for autoNotification getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this, "set - via arrays")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -529,7 +542,8 @@ public MultiDimDoubleDataSet trim() { values[i].trim(i); } }); - return fireInvalidated(new UpdatedDataEvent(this, "increaseCapacity()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java index a8a201d0a..1e9e06b2f 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java @@ -4,6 +4,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; +import io.fair_acc.dataset.events.ChartBits; /** * @author braeun @@ -34,7 +35,7 @@ public void add(final DataSet set) { lastLength = set.getAxisDescription(DIM_X).getMax(); // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); - fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); } /** @@ -73,7 +74,7 @@ public void shift(double value) { this.getValues(DIM_X)[i] += value; } }); - fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); } } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/TransposedDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/TransposedDataSet.java index 45b029f2c..f7e671ed3 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/TransposedDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/TransposedDataSet.java @@ -2,13 +2,12 @@ import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.stream.IntStream; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AxisChangeEvent; -import io.fair_acc.dataset.event.EventListener; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.locks.DataSetLock; import io.fair_acc.dataset.utils.AssertUtils; import org.slf4j.Logger; @@ -69,11 +68,6 @@ private TransposedDataSet(final DataSet dataSet, final int[] permutation) { this.transposed = false; } - @Override - public AtomicBoolean autoNotification() { - return dataSet.autoNotification(); - } - @Override public double get(int dimIndex, int index) { return dataSet.get(permutation[dimIndex], index); @@ -177,7 +171,7 @@ public void setPermutation(final int[] permutation) { LOGGER.atDebug().addArgument(this.permutation).log("applied permutation: {}"); } }); - this.invokeListener(new AxisChangeEvent(this, "Permutation changed", -1)); + fireInvalidated(ChartBits.DataSetPermutation); } @Override @@ -214,12 +208,12 @@ public void setTransposed(final boolean transposed) { this.transposed = transposed; } }); - this.invokeListener(new AxisChangeEvent(this, "(Un)transposed", -1)); + fireInvalidated(ChartBits.DataSetPermutation); } @Override - public List updateEventListener() { - return dataSet.updateEventListener(); + public BitState getBitState() { + return dataSet.getBitState(); } public static TransposedDataSet permute(DataSet dataSet, int[] permutation) { @@ -272,7 +266,7 @@ public void setPermutation(final int[] permutation) { } super.setPermutation(permutation); }); - this.invokeListener(new AxisChangeEvent(this, "Permutation changed", -1)); + fireInvalidated(ChartBits.DataSetPermutation); } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java index ba1357a24..bddeccf1b 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java @@ -7,6 +7,7 @@ import io.fair_acc.dataset.event.EventListener; import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; +import io.fair_acc.dataset.events.ChartBits; /** * A data set implementation which wraps another data set. @@ -16,7 +17,6 @@ public class WrappedDataSet extends AbstractDataSet implements DataSet { private static final long serialVersionUID = -2324840899629186284L; private DataSet dataset; - private final transient EventListener listener = s -> datasetInvalidated(); /** * @param name data set name @@ -28,7 +28,7 @@ public WrappedDataSet(final String name) { private void datasetInvalidated() { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); - fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); } @Override @@ -78,18 +78,19 @@ public String getStyle(final int index) { */ public void setDataset(final DataSet dataset) { if (this.dataset != null) { - this.dataset.removeListener(listener); + this.dataset.removeListener(this); } this.dataset = dataset; if (this.dataset != null) { - this.dataset.addListener(listener); + this.dataset.addListener(this); } - fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); } @Override public DataSet set(final DataSet other, final boolean copy) { lock().writeLockGuard(() -> other.lock().writeLockGuard(() -> this.setDataset(other))); - return fireInvalidated(new UpdatedDataEvent(this, "set(DataSet, boolean=" + copy + ")")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/TestDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/TestDataSet.java index a9c811dc3..370dda883 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/TestDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/TestDataSet.java @@ -10,13 +10,6 @@ * @param generics for fluent design */ public interface TestDataSet> extends DataSet2D { - /** - * notify listener with given event that the data set has changed - * - * @param evt the modification event - * @return itself (fluent design) - */ - D fireInvalidated(UpdateEvent evt); /** * generate test data set diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/AbstractTestFunction.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/AbstractTestFunction.java index 39c50cce4..117c57967 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/AbstractTestFunction.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/AbstractTestFunction.java @@ -1,7 +1,7 @@ package io.fair_acc.dataset.testdata.spi; import io.fair_acc.dataset.DataSet; -import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.AbstractErrorDataSet; import io.fair_acc.dataset.testdata.TestDataSet; @@ -68,7 +68,8 @@ public D update() { recomputeLimits(DIM_X); recomputeLimits(DIM_Y); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSet.java index 885f6683f..b77811ef1 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSet.java @@ -1,13 +1,12 @@ package io.fair_acc.dataset.testdata.spi; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import io.fair_acc.dataset.AxisDescription; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSetError; -import io.fair_acc.dataset.event.EventListener; +import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.locks.DataSetLock; import io.fair_acc.dataset.locks.DefaultDataSetLock; import io.fair_acc.dataset.spi.DefaultAxisDescription; @@ -23,8 +22,7 @@ public class ErrorTestDataSet implements DataSetError { private final int nSamples; private final ErrorType errorType; private final DataSetLock lock = new DefaultDataSetLock<>(this); - private final AtomicBoolean autoNotification = new AtomicBoolean(); - private final List eventListeners = new ArrayList<>(); + private final BitState state = BitState.initDirty(this); private static final double STEP = 0.4; // multiples of this will be the step sizes in x direction private static final int N_STEP_SWEEP = 10; // how many times to increase step size before returning to original step size @@ -205,13 +203,8 @@ public DataSetError.ErrorType getErrorType(final int dimIndex) { } @Override - public AtomicBoolean autoNotification() { - return autoNotification; - } - - @Override - public List updateEventListener() { - return eventListeners; + public BitState getBitState() { + return state; } public enum ErrorType { diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java index e5e934276..b0ccf1a15 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java @@ -23,7 +23,7 @@ public AtomicBoolean autoNotification() { } @Override - public List updateEventListener() { + public List getBitState() { return null; } }; @@ -31,7 +31,7 @@ public List updateEventListener() { @Test public void testEventSource() { assertNull(testEventSource.autoNotification()); - assertNull(testEventSource.updateEventListener()); + assertNull(testEventSource.getBitState()); } @DisplayName("UpdateEvent class constructors") diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java index 76dc7079d..be303cc5b 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java @@ -19,7 +19,7 @@ public AtomicBoolean autoNotification() { } @Override - public List updateEventListener() { + public List getBitState() { return eventListener; } } diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java index 75c422cd6..31ef672d2 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java @@ -309,7 +309,7 @@ public double getValue(final int dimIndex, final double... x) { } @Override - public List updateEventListener() { + public List getBitState() { return Collections.emptyList(); } diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java index b69929a46..5aeb667f0 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java @@ -36,7 +36,7 @@ void testErrorTestDataSet() { Assertions.assertEquals(5, dsUnderTest.getIndex(DataSet.DIM_X, 6.5)); assertNotNull(dsUnderTest.lock()); assertNotNull(dsUnderTest.autoNotification()); - assertNotNull(dsUnderTest.updateEventListener()); + assertNotNull(dsUnderTest.getBitState()); assertNull(dsUnderTest.getDataLabel(5)); assertNull(dsUnderTest.getStyle(5)); assertNull(dsUnderTest.getStyle()); diff --git a/chartfx-math/src/main/java/io/fair_acc/math/DataSetMath.java b/chartfx-math/src/main/java/io/fair_acc/math/DataSetMath.java index 714ec2032..89040c545 100644 --- a/chartfx-math/src/main/java/io/fair_acc/math/DataSetMath.java +++ b/chartfx-math/src/main/java/io/fair_acc/math/DataSetMath.java @@ -1135,14 +1135,12 @@ public static EditableDataSet setFunction(final EditableDataSet function, final xMaxLocal = xMax; } - final boolean oldState = function.autoNotification().getAndSet(false); for (var i = 0; i < nLength; i++) { final double x = function.get(DIM_X, i); if (x >= xMinLocal && x <= xMaxLocal) { function.set(i, x, value); } } - function.autoNotification().set(oldState); return function; } diff --git a/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java b/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java index 7e07822e8..28d592b91 100644 --- a/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java +++ b/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java @@ -10,12 +10,12 @@ import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSetError; import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.EventListener; import io.fair_acc.dataset.event.EventRateLimiter; import io.fair_acc.dataset.event.EventRateLimiter.UpdateStrategy; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdateEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.DoubleErrorDataSet; /** @@ -28,7 +28,6 @@ public class MathDataSet extends DoubleErrorDataSet { private static final long serialVersionUID = -4978160822533565009L; private static final long DEFAULT_UPDATE_LIMIT = 40; - private final transient EventListener eventListener; private final transient List sourceDataSets; private final transient DataSetFunction dataSetFunction; private final transient DataSetsFunction dataSetsFunction; @@ -133,19 +132,15 @@ protected MathDataSet(final String transformName, DataSetFunction dataSetFunctio // the 'DataSetFunction' interface } - if (minUpdatePeriod > 0) { - eventListener = new EventRateLimiter(this::handle, this.minUpdatePeriod, this.updateStrategy); - } else { - eventListener = this::handle; - } - registerListener(); // NOPMD + // TODO: when should the updates happen? Should this be multithreaded? on invalidate or change? + getBitState().addInvalidateListener(ChartBits.DataSetData, (obj, bits) -> update()); // exceptionally call handler during DataSet creation - handle(new UpdatedDataEvent(this, MathDataSet.class.getSimpleName() + " - initial constructor update")); + registerListener(); // NOPMD } public final void deregisterListener() { - sourceDataSets.forEach(srcDataSet -> srcDataSet.removeListener(eventListener)); + sourceDataSets.forEach(srcDataSet -> srcDataSet.removeListener(this)); } public final List getSourceDataSets() { @@ -153,7 +148,7 @@ public final List getSourceDataSets() { } public final void registerListener() { - sourceDataSets.forEach(srcDataSet -> srcDataSet.addListener(eventListener)); + sourceDataSets.forEach(srcDataSet -> srcDataSet.addListener(this)); } private void handleDataSetValueFunctionInterface() { @@ -186,11 +181,7 @@ private void handleDataSetValueFunctionInterface() { // existing array } - protected void handle(UpdateEvent event) { - boolean isKnownEvent = event instanceof AddedDataEvent || event instanceof RemovedDataEvent || event instanceof UpdatedDataEvent; - if (event == null || !isKnownEvent) { - return; - } + protected void update() { this.lock().writeLockGuard(() -> { if (dataSetFunction != null) { set(dataSetFunction.transform(sourceDataSets.get(0))); @@ -205,7 +196,7 @@ protected void handle(UpdateEvent event) { this.setName(getCompositeDataSetName(transformName, sourceDataSets.toArray(new DataSet[0]))); }); - fireInvalidated(new UpdatedDataEvent(this, "propagated update from source " + this.getName())); + fireInvalidated(ChartBits.DataSetData); } protected static String getCompositeDataSetName(final String transformName, final DataSet... sources) { diff --git a/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java b/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java index 7f434627f..2619e706a 100644 --- a/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java +++ b/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java @@ -178,9 +178,9 @@ public void testNotifies() { assertEquals(4, counter1.get()); assertEquals(4, counter2.get()); - assertEquals(1, rawDataSetRef.updateEventListener().size()); + assertEquals(1, rawDataSetRef.getBitState().size()); identityDataSet.deregisterListener(); - assertEquals(0, rawDataSetRef.updateEventListener().size()); + assertEquals(0, rawDataSetRef.getBitState().size()); rawDataSetRef.invokeListener(new UpdatedDataEvent(rawDataSetRef, "OK reference", false)); assertEquals(4, counter1.get()); assertEquals(4, counter2.get()); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java index ad2a94ad6..11304c6fc 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartIndicatorSample.java @@ -67,9 +67,6 @@ private void generateData() { final double now = System.currentTimeMillis() / 1000.0 + 1; // N.B. '+1' to check for resolution if (rollingBufferDipoleCurrent.getDataCount() == 0) { - rollingBufferBeamIntensity.autoNotification().set(false); - rollingBufferDipoleCurrent.autoNotification().set(false); - rollingSine.autoNotification().set(false); for (int n = ChartIndicatorSample.N_SAMPLES; n > 0; n--) { final double t = now - n * ChartIndicatorSample.UPDATE_PERIOD / 1000.0; final double y = 25 * ChartIndicatorSample.rampFunctionDipoleCurrent(t); @@ -81,11 +78,7 @@ private void generateData() { rollingSine.add(t + 1 + ChartIndicatorSample.UPDATE_PERIOD / 1000.0 * RandomDataGenerator.random(), y * 0.8, ey, ey); } - rollingBufferBeamIntensity.autoNotification().set(true); - rollingBufferDipoleCurrent.autoNotification().set(true); - rollingSine.autoNotification().set(true); } else { - rollingBufferDipoleCurrent.autoNotification().set(false); final double y = 25 * ChartIndicatorSample.rampFunctionDipoleCurrent(now); final double y2 = 100 * ChartIndicatorSample.rampFunctionBeamIntensity(now); final double ey = 1; @@ -93,7 +86,6 @@ private void generateData() { rollingBufferBeamIntensity.add(now, y2, ey, ey); final double val = 1500 + 1000.0 * Math.sin(Math.PI * 2 * 0.1 * now); rollingSine.add(now + 1, val, ey, ey); - rollingBufferDipoleCurrent.autoNotification().set(true); } ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererSample.java index 49fe8894d..2b691252b 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererSample.java @@ -154,12 +154,6 @@ private static void generateData(final DoubleErrorDataSet dataSet, final DoubleD final long startTime = ProcessingProfiler.getTimeStamp(); dataSet.lock().writeLockGuard(() -> dataSetNoErrors.lock().writeLockGuard(() -> { - // suppress auto notification since we plan to add multiple data points - // N.B. this is for illustration of the 'setAutoNotification(..)' functionality - // one may use also the add(double[], double[], ...) method instead - dataSet.autoNotification().set(false); - dataSetNoErrors.autoNotification().set(false); - dataSet.clearData(); dataSetNoErrors.clearData(); double oldY = 0; @@ -171,22 +165,13 @@ private static void generateData(final DoubleErrorDataSet dataSet, final DoubleD final double ey = 10; dataSet.add(n, y, ex, ey); dataSetNoErrors.add(n, y + 20); - // N.B. update events suppressed by 'setAutoNotification(false)' above if (n == 500000) { // NOPMD this point is really special ;-) dataSet.getDataLabelMap().put(n, "special outlier"); dataSetNoErrors.getDataLabelMap().put(n, "special outlier"); } } - - dataSet.autoNotification().set(true); - dataSetNoErrors.autoNotification().set(true); })); - // need to issue a separate update notification - // N.B. for performance reasons we let only 'dataSet' fire an event, since we modified both - // dataSetNoErrors will be updated alongside dataSet. - dataSet.fireInvalidated(new AddedDataEvent(dataSet)); - // disabled on purpose -- dataSetNoErrors.fireInvalidated(new AddedDataEvent(dataSet)) -- ProcessingProfiler.getTimeDiff(startTime, "generating data DataSet"); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java index 5257f99b0..3f5773c11 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java @@ -38,8 +38,6 @@ public class Histogram2DimSample extends ChartSample { private void fillData() { counter++; - histogram1.autoNotification().set(false); - histogram2.autoNotification().set(false); final double angle = Math.PI / 4; for (int i = 0; i < Histogram2DimSample.UPDATE_N_SAMPLES; i++) { final double x0 = rnd.nextGaussian() * 0.5 + 5.0; @@ -51,10 +49,6 @@ private void fillData() { histogram1.fill(x0, y0); histogram2.fill(x1 + 14.0, x2 + 20.0); } - histogram1.autoNotification().set(true); - histogram2.autoNotification().set(true); - histogram1.fireInvalidated(null); - histogram2.fireInvalidated(null); if (counter % 500 == 0) { // reset distribution every now and then diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/LogAxisSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/LogAxisSample.java index 2913b8790..77a61cc21 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/LogAxisSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/LogAxisSample.java @@ -42,14 +42,7 @@ public Node getChartPanel(final Stage primaryStage) { final DoubleDataSet dataSet3 = new DoubleDataSet("data set #2"); chart.getDatasets().addAll(dataSet1, dataSet2, dataSet3); - // classic way of adding data points - // N.B. in a life-update context every new points triggers a chart - // repaint. This can be suppressed by adding/setting full arrays and/or - // by selecting dataSet1.setAutoNotification(false/true) for the data - // sets (or chart) concerned to suppress this repaint. - dataSet1.autoNotification().set(false); - dataSet2.autoNotification().set(false); - dataSet3.autoNotification().set(false); + // adding data points for (int n = 0; n < N_SAMPLES; n++) { final double x = n + 1.0; double y = 0.01 * (n + 1); @@ -58,10 +51,6 @@ public Node getChartPanel(final Stage primaryStage) { dataSet2.add(x, Math.pow(2, y)); dataSet3.add(x, Math.exp(y)); } - dataSet1.autoNotification().set(true); - dataSet2.autoNotification().set(true); - dataSet3.autoNotification().set(true); - return root; } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java index 4f87ff91a..b091e97b5 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java @@ -63,8 +63,6 @@ private void generateData() { // N.B. '+1' to check for resolution if (rollingBufferDipoleCurrent.getDataCount() == 0) { - rollingBufferBeamIntensity.autoNotification().set(false); - rollingBufferDipoleCurrent.autoNotification().set(false); for (int n = RollingBufferSample.N_SAMPLES; n > 0; n--) { final double t = now - n * RollingBufferSample.UPDATE_PERIOD / 1000.0; final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(t); @@ -73,17 +71,13 @@ private void generateData() { rollingBufferDipoleCurrent.add(t, y, ey, ey); rollingBufferBeamIntensity.add(t, y2, ey, ey); } - rollingBufferBeamIntensity.autoNotification().set(true); - rollingBufferDipoleCurrent.autoNotification().set(true); } else { - rollingBufferDipoleCurrent.autoNotification().set(false); final double t = now; final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(t); final double y2 = AXIS_CENTRE_VALUE + 100 * Math.cos(2.0 * Math.PI * 0.01 * t) * RollingBufferSample.rampFunctionBeamIntensity(t); final double ey = 1; rollingBufferDipoleCurrent.add(t, y, ey, ey); rollingBufferBeamIntensity.add(t, y2, ey, ey); - rollingBufferDipoleCurrent.autoNotification().set(true); } ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java index a88e2e3d1..c6dfefa69 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSample.java @@ -57,10 +57,6 @@ private void generateBeamIntensityData() { // N.B. '+1' to check for resolution if (rollingBufferBeamIntensity.getDataCount() == 0) { - // suppress auto notification since we plan to add multiple data points - // N.B. this is for illustration of the 'setAutoNotification(..)' functionality - // one may use also the add(double[], double[], ...) method instead - boolean oldState = rollingBufferBeamIntensity.autoNotification().getAndSet(false); for (int n = RollingBufferSample.N_SAMPLES; n >= 0; --n) { final double t = now - n * RollingBufferSample.UPDATE_PERIOD / 1000.0; final double y = 100 * RollingBufferSample.rampFunctionBeamIntensity(t); @@ -68,9 +64,6 @@ private void generateBeamIntensityData() { rollingBufferBeamIntensity.add(t, y, ey, ey); // N.B. update events suppressed by 'setAutoNotification(false)' above } - rollingBufferBeamIntensity.autoNotification().set(oldState); - // need to issue a separate update notification - rollingBufferBeamIntensity.fireInvalidated(new AddedDataEvent(rollingBufferBeamIntensity)); } else { final double t = now; final double y2 = 100 * RollingBufferSample.rampFunctionBeamIntensity(t); @@ -90,20 +83,12 @@ private void generateDipoleCurrentData() { // resolution if (rollingBufferDipoleCurrent.getDataCount() == 0) { - // suppress auto notification since we plan to add multiple data points - // N.B. this is for illustration of the 'setAutoNotification(..)' functionality - // one may use also the add(double[], double[], ...) method instead - boolean oldState = rollingBufferDipoleCurrent.autoNotification().getAndSet(false); for (int n = RollingBufferSample.N_SAMPLES; n >= 0; --n) { final double t = now - n * RollingBufferSample.UPDATE_PERIOD / 1000.0; final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(t); final double ey = 1; rollingBufferDipoleCurrent.add(t, y, ey, ey); - // N.B. update events suppressed by 'setAutoNotification(false)' above } - rollingBufferDipoleCurrent.autoNotification().set(oldState); - // need to issue a separate update notification - rollingBufferDipoleCurrent.fireInvalidated(new AddedDataEvent(rollingBufferDipoleCurrent)); } else { final double t = now; final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(t); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java index 0c6f4c5b3..4db151502 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/RollingBufferSortedTreeSample.java @@ -54,8 +54,6 @@ private void generateData() { // resolution if (rollingBufferDipoleCurrent.getDataCount() == 0) { - rollingBufferBeamIntensity.autoNotification().set(false); - rollingBufferDipoleCurrent.autoNotification().set(false); for (int n = RollingBufferSample.N_SAMPLES; n > 0; n--) { final double t = now - n * RollingBufferSample.UPDATE_PERIOD / 1000.0; final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(t); @@ -64,16 +62,12 @@ private void generateData() { rollingBufferDipoleCurrent.add(t, y, ey, ey); rollingBufferBeamIntensity.add(t, y2, ey, ey); } - rollingBufferBeamIntensity.autoNotification().set(true); - rollingBufferDipoleCurrent.autoNotification().set(true); } else { - rollingBufferDipoleCurrent.autoNotification().set(false); final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(now); final double y2 = 100 * RollingBufferSample.rampFunctionBeamIntensity(now); final double ey = 1; rollingBufferDipoleCurrent.add(now, y, ey, ey); rollingBufferBeamIntensity.add(now, y2, ey, ey); - rollingBufferDipoleCurrent.autoNotification().set(true); } ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java index ba3ec337c..39561bf35 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleChartSample.java @@ -38,16 +38,11 @@ public Node getChartPanel(final Stage primaryStage) { final DoubleDataSet dataSet1 = new DoubleDataSet("data set #1"); final DoubleDataSet dataSet2 = new DoubleDataSet("data set #2"); - // some custom listeners (optional) - dataSet1.addListener(evt -> LOGGER.atInfo().log("dataSet1 - event: " + evt.toString())); - dataSet2.addListener(evt -> LOGGER.atInfo().log("dataSet2 - event: " + evt.toString())); - // chart.getDatasets().add(dataSet1); // for single data set chart.getDatasets().addAll(dataSet1, dataSet2); // for two data sets final double[] xValues = new double[N_SAMPLES]; final double[] yValues1 = new double[N_SAMPLES]; - dataSet2.autoNotification().set(false); // to suppress auto notification for (int n = 0; n < N_SAMPLES; n++) { final double x = n; final double y1 = Math.cos(Math.toRadians(10.0 * n)); @@ -57,9 +52,6 @@ public Node getChartPanel(final Stage primaryStage) { dataSet2.add(n, y2); // style #1 how to set data, notifies re-draw for every 'add' } dataSet1.set(xValues, yValues1); // style #2 how to set data, notifies once per set - // to manually trigger an update (optional): - dataSet2.autoNotification().set(true); // to suppress auto notification - dataSet2.invokeListener(new UpdatedDataEvent(dataSet2 /* pointer to update source */, "manual update event")); // alternatively (optional via default constructor): // final DoubleDataSet dataSet3 = new DoubleDataSet("data set #1", xValues, yValues1, N_SAMPLES, false) diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleInvertedChartSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleInvertedChartSample.java index c02e14ce6..f05060ac2 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleInvertedChartSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/SimpleInvertedChartSample.java @@ -48,16 +48,11 @@ public Node getChartPanel(final Stage primaryStage) { final var dataSet1 = new DoubleDataSet("data set #1"); final var dataSet2 = new DoubleDataSet("data set #2"); - // some custom listeners (optional) - dataSet1.addListener(evt -> LOGGER.atInfo().log("dataSet1 - event: " + evt.toString())); - dataSet2.addListener(evt -> LOGGER.atInfo().log("dataSet2 - event: " + evt.toString())); - // chart.getDatasets().add(dataSet1); // for single data set chart.getDatasets().addAll(dataSet1, dataSet2); // for two data sets final var xValues = new double[N_SAMPLES]; final var yValues1 = new double[N_SAMPLES]; - dataSet2.autoNotification().set(false); // to suppress auto notification for (var x = 0; x < N_SAMPLES; x++) { final double y1 = Math.cos(Math.toRadians(10.0 * x)); final double y2 = Math.sin(Math.toRadians(10.0 * x)); @@ -66,9 +61,6 @@ public Node getChartPanel(final Stage primaryStage) { dataSet2.add(x, y2); // style #1 how to set data, notifies re-draw for every 'add' } dataSet1.set(xValues, yValues1); // style #2 how to set data, notifies once per set - // to manually trigger an update (optional): - dataSet2.autoNotification().set(true); // to suppress auto notification - dataSet2.invokeListener(new UpdatedDataEvent(dataSet2 /* pointer to update source */, "manual update event")); // alternatively (optional via default constructor): // final DoubleDataSet dataSet3 = new DoubleDataSet("data set #1", xValues, yValues1, N_SAMPLES, false) diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisSample.java index 4a4a35a86..35254d652 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisSample.java @@ -25,7 +25,6 @@ public class TimeAxisSample extends ChartSample { private static void generateData(final DefaultErrorDataSet dataSet) { final long startTime = ProcessingProfiler.getTimeStamp(); - dataSet.autoNotification().set(false); dataSet.clearData(); final double now = System.currentTimeMillis() / 1000.0 + 1; // N.B. '+1' // to check @@ -39,9 +38,7 @@ private static void generateData(final DefaultErrorDataSet dataSet) { final double ey = 10; dataSet.add(t, y, ex, ey); } - dataSet.autoNotification().set(true); - Platform.runLater(() -> dataSet.fireInvalidated(null)); ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/VisibilityToggleSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/VisibilityToggleSample.java index 8077b4165..d4f4f02e3 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/VisibilityToggleSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/VisibilityToggleSample.java @@ -1,5 +1,6 @@ package io.fair_acc.sample.chart; +import io.fair_acc.dataset.events.ChartBits; import javafx.application.Application; import javafx.scene.Node; import javafx.scene.control.CheckBox; @@ -36,14 +37,13 @@ public Node getChartPanel(final Stage primaryStage) { final DoubleDataSet dataSet2 = new DoubleDataSet("data set #2"); // some custom listeners (optional) - dataSet1.addListener(evt -> LOGGER.atInfo().log("dataSet1 - event: " + evt.toString())); - dataSet2.addListener(evt -> LOGGER.atInfo().log("dataSet2 - event: " + evt.toString())); + // TODO: dataSet1.addListener(evt -> LOGGER.atInfo().log("dataSet1 - event: " + evt.toString())); + // TODO: dataSet2.addListener(evt -> LOGGER.atInfo().log("dataSet2 - event: " + evt.toString())); chart.getDatasets().addAll(dataSet1, dataSet2); // for two data sets final double[] xValues = new double[N_SAMPLES]; final double[] yValues1 = new double[N_SAMPLES]; - dataSet2.autoNotification().set(false); // to suppress auto notification for (int n = 0; n < N_SAMPLES; n++) { final double x = n; final double y1 = Math.cos(Math.toRadians(10.0 * n)); @@ -53,9 +53,6 @@ public Node getChartPanel(final Stage primaryStage) { dataSet2.add(n, y2); // style #1 how to set data, notifies re-draw for every 'add' } dataSet1.set(xValues, yValues1); // style #2 how to set data, notifies once per set - // to manually trigger an update (optional): - dataSet2.autoNotification().set(true); // to suppress auto notification - dataSet2.invokeListener(new UpdatedDataEvent(dataSet2 /* pointer to update source */, "manual update event")); final BorderPane borderPane = new BorderPane(chart); final HBox toolbar = new HBox(); @@ -64,21 +61,17 @@ public Node getChartPanel(final Stage primaryStage) { visibility1.selectedProperty().addListener((observable, oldValue, newValue) -> { dataSet1.setVisible(newValue); }); - dataSet1.addListener(event -> { - if (event instanceof UpdatedMetaDataEvent) { - FXUtils.runFX(() -> visibility1.setSelected(dataSet1.isVisible())); - } - }); + dataSet1.getBitState().addInvalidateListener(ChartBits.DataSetVisibility, FXUtils.runOnFxThread((obs, bits) -> { + visibility1.setSelected(dataSet1.isVisible()); + })); final CheckBox visibility2 = new CheckBox("show Dataset 2"); visibility2.setSelected(true); visibility2.selectedProperty().addListener((observable, oldValue, newValue) -> { dataSet2.setVisible(newValue); }); - dataSet2.addListener(event -> { - if (event instanceof UpdatedMetaDataEvent) { - FXUtils.runFX(() -> visibility2.setSelected(dataSet2.isVisible())); - } - }); + dataSet2.getBitState().addInvalidateListener(ChartBits.DataSetVisibility, FXUtils.runOnFxThread((obs, bits) -> { + visibility2.setSelected(dataSet2.isVisible()); + })); toolbar.getChildren().addAll(visibility1, visibility2); borderPane.setTop(toolbar); return borderPane; diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ZoomerSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ZoomerSample.java index be4fd3746..2aa3b341c 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ZoomerSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ZoomerSample.java @@ -107,14 +107,7 @@ private static DataSet generateData() { dataSet.getDataLabelMap().put(n, "special outlier"); } } - - dataSet.autoNotification().set(true); }); - // need to issue a separate update notification - // N.B. for performance reasons we let only 'dataSet' fire an event, since we modified both - // dataSetNoErrors will be updated alongside dataSet. - dataSet.fireInvalidated(new AddedDataEvent(dataSet)); - return dataSet; } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/ChartHighUpdateRateSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/ChartHighUpdateRateSample.java index 9bfdb4d46..b314502ed 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/ChartHighUpdateRateSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/legacy/ChartHighUpdateRateSample.java @@ -22,13 +22,10 @@ public class ChartHighUpdateRateSample extends RollingBufferSample { @Override public BorderPane initComponents() { BorderPane pane = super.initComponents(); - rollingBufferDipoleCurrent.autoNotification().set(true); - rollingBufferBeamIntensity.autoNotification().set(true); Toolkit.getToolkit().addSceneTkPulseListener(() -> { counter = (counter + 1) % 100; if (counter == 0) { - System.err.println("pulse auto dipole = " + rollingBufferDipoleCurrent.autoNotification().get() - + " auto beam " + rollingBufferBeamIntensity.autoNotification().get()); + System.err.println("pulse auto dipole, "+ " auto beam "); } }); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java index 51603bfd4..1be3db70c 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java @@ -12,6 +12,7 @@ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.TargetDataLine; +import io.fair_acc.dataset.events.ChartBits; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -341,7 +342,7 @@ public void run() { circIndex = (circIndex + frameSize) % (frameSize * frameCount); }); - fireInvalidated(new AddedDataEvent(TestDataSetSource.this, "new frame")); + fireInvalidated(ChartBits.DataSetData); } }; } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java index 9788f501d..62a594315 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java @@ -6,6 +6,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet2D; @@ -165,7 +166,8 @@ public DoubleDataSet add(final double[] xValuesNew, final double[] yValuesNew) { recomputeLimits(DIM_X); recomputeLimits(DIM_Y); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -236,7 +238,8 @@ public DoubleDataSet add(final int index, final double x, final double y, final getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -254,8 +257,8 @@ public DoubleDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - - return fireInvalidated(new RemovedDataEvent(this, "clearData()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -332,7 +335,8 @@ public DoubleDataSet remove(final int fromIndex, final int toIndex) { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -362,7 +366,8 @@ public DoubleDataSet set(final DataSet2D other) { this.set(other.getXValues(), other.getYValues(), true); })); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -419,8 +424,8 @@ public DoubleDataSet set(final double[] xValues, final double[] yValues, final b recomputeLimits(DIM_X); recomputeLimits(DIM_Y); }); - - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -432,7 +437,8 @@ public DoubleDataSet set(final int index, final double... newValue) { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java index bb147916c..de1c5899b 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java @@ -6,6 +6,7 @@ import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.RemovedDataEvent; import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.spi.AbstractErrorDataSet; import io.fair_acc.dataset.spi.DoubleDataSet; @@ -162,7 +163,8 @@ public DoubleErrorDataSet add(final double x, final double y, final double yErro getAxisDescription(DIM_Y).add(y - yErrorNeg); getAxisDescription(DIM_Y).add(y + yErrorPos); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -217,7 +219,8 @@ public DoubleErrorDataSet add(final double[] xValues, final double[] yValues, fi recomputeLimits(DIM_X); recomputeLimits(DIM_Y); }); - return fireInvalidated(new AddedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -243,7 +246,8 @@ public DoubleErrorDataSet clearData() { getAxisDescription(DIM_X).clear(); getAxisDescription(DIM_Y).clear(); }); - return fireInvalidated(new RemovedDataEvent(this, "clearData()")); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override @@ -328,7 +332,8 @@ public DoubleErrorDataSet remove(final int fromIndex, final int toIndex) { getAxisDescriptions().forEach(AxisDescription::clear); }); - return fireInvalidated(new RemovedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -440,7 +445,8 @@ public DoubleErrorDataSet set(final double[] xValues, final double[] yValues, fi recomputeLimits(DIM_X); recomputeLimits(DIM_Y); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } /** @@ -481,7 +487,8 @@ public DoubleErrorDataSet set(final int index, final double x, final double y, f getAxisDescription(DIM_Y).add(y - yErrorNeg); getAxisDescription(DIM_Y).add(y + yErrorPos); }); - return fireInvalidated(new UpdatedDataEvent(this)); + fireInvalidated(ChartBits.DataSetData); + return getThis(); } @Override diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java index 48a7b619d..622911d95 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java @@ -330,10 +330,7 @@ protected void loadTestData(String data, final OhlcvDataSet dataSet, DefaultData final long startTime = ProcessingProfiler.getTimeStamp(); IOhlcv ohlcv = new SimpleOhlcvDailyParser().getContinuousOHLCV(data); - - dataSet.autoNotification().set(false); dataSet.setData(ohlcv); - dataSet.autoNotification().set(true); DescriptiveStatistics stats = new DescriptiveStatistics(24); for (IOhlcvItem ohlcvItem : ohlcv) { @@ -341,8 +338,6 @@ protected void loadTestData(String data, final OhlcvDataSet dataSet, DefaultData stats.addValue(ohlcvItem.getClose()); indiSet.add(timestamp, stats.getMean()); } - - Platform.runLater(() -> dataSet.fireInvalidated(null)); ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java index c1d15d451..96eaf9721 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java @@ -11,6 +11,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import io.fair_acc.dataset.events.ChartBits; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; @@ -96,11 +97,9 @@ public void fillTestData(IntradayPeriod period, Interval timeRange, In consolidation = OhlcvTimeframeConsolidation.createConsolidation(period, tt, addons); - autoNotification().set(false); setData(ohlcv); // try first tick in the fill part tick(); - autoNotification().set(true); } catch (TickDataFinishedException e) { LOGGER.info(e.getMessage()); @@ -205,7 +204,7 @@ protected Runnable getDataUpdateTask() { while (running.get()) { try { tick(); - fireInvalidated(new AddedDataEvent(SimpleOhlcvReplayDataSet.this, "tick")); + fireInvalidated(ChartBits.DataSetData); // pause simple support while (paused.get()) { synchronized (pauseSemaphore) { diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java index e37955861..86c034666 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java @@ -4,6 +4,7 @@ import java.util.*; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.sample.financial.service.execution.ExecutionPlatformListener; import io.fair_acc.sample.financial.service.execution.OrderEvent; import org.slf4j.Logger; @@ -179,7 +180,7 @@ public void orderFilled(OrderEvent event) { // update internal memories Order order = event.getOrder(); includePosition(order.isExitOrder() ? order.getExitOfPosition() : order.getEntryOfPosition(), false); - fireInvalidated(new AddedDataEvent(PositionFinancialDataSet.this, "filled-order")); + fireInvalidated(ChartBits.DataSetData); } @Override diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java index eff959765..359a01350 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Objects; +import io.fair_acc.dataset.events.ChartBits; import javafx.application.Application; import javafx.application.Platform; import javafx.collections.FXCollections; @@ -261,7 +262,7 @@ public void start(final Stage primaryStage) { } private void triggerDataSetUpdate() { - demoDataSet.invokeListener(new UpdatedDataEvent(demoDataSet, "GUI related update")); + demoDataSet.fireInvalidated(ChartBits.DataSetData); } protected static DoubleDataSet generateDemoSineWaveData(final int nData) { From 3c995f103570cdb245578ed3808fa19a54e1c540 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 26 Jul 2023 10:51:25 +0200 Subject: [PATCH 29/81] fixed waterfall and mountain range examples --- .../main/java/io/fair_acc/chartfx/Chart.java | 1 + .../renderer/spi/MountainRangeRenderer.java | 18 +++--------------- .../chart/WaterfallPerformanceSample.java | 2 +- .../sample/chart/utils/TestDataSetSource.java | 3 +++ 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 7803a71d3..8b8c5a871 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -687,6 +687,7 @@ protected void runPostLayout() { } redrawCanvas(); state.clear(); + dataSetState.clear(); forEachDataSet(ds -> ds.getBitState().clear()); // TODO: plugins etc., do locking ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java index 808c565d1..2b9a577db 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java @@ -8,8 +8,8 @@ import java.util.Arrays; import java.util.List; import java.util.WeakHashMap; -import java.util.concurrent.atomic.AtomicBoolean; +import io.fair_acc.dataset.events.BitState; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.FXCollections; @@ -169,13 +169,11 @@ private void checkAndRecreateRenderer(final int nRenderer) { private class Demux3dTo2dDataSet implements DataSet { private static final long serialVersionUID = 3914728138839091421L; private final transient DataSetLock localLock = new DefaultDataSetLock<>(this); - private final transient AtomicBoolean autoNotify = new AtomicBoolean(true); private final GridDataSet dataSet; private final int yIndex; private final double zMin; private final double zMax; private final double yShift; - private final transient List updateListener = new ArrayList<>(); private final transient List axesDescriptions = new ArrayList<>(Arrays.asList( // new DefaultAxisDescription(DIM_X, "x-Axis", "a.u."), // new DefaultAxisDescription(DIM_Y, "y-Axis", "a.u."))); @@ -189,11 +187,6 @@ public Demux3dTo2dDataSet(final GridDataSet sourceDataSet, final int selectedYIn yShift = dataSet.getShape(DIM_Y) > 0 ? mountainRangeExtra * dataSet.getAxisDescription(DIM_Z).getMax() * yIndex / dataSet.getShape(DIM_Y) : 0; } - @Override - public AtomicBoolean autoNotification() { - return autoNotify; - } - @Override public double get(final int dimIndex, final int i) { switch (dimIndex) { @@ -272,11 +265,6 @@ public double[] getValues(final int dimIndex) { } } - @Override - public boolean isAutoNotification() { - return autoNotify.get(); - } - @Override public DataSetLock lock() { // empty implementation since the superordinate DataSet3D lock is being held/protecting this data set @@ -316,8 +304,8 @@ public DataSet setVisible(boolean visible) { } @Override - public List getBitState() { - return updateListener; + public BitState getBitState() { + throw new AssertionError("Mountain range ds wrapper keeps no state"); } } } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java index fab1789c2..11d186e42 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java @@ -257,7 +257,7 @@ private ToolBar getDataSetToolBar(Chart chart) { dataSetDimension.setText( dataSet.getShape(DataSet.DIM_X) + " x " + dataSet.getShape(DataSet.DIM_Y) + " data points"); - dataSet.addListener(evt -> { + dataSet.addListener((src, bits) -> { final int dimX = dataSet.getShape(DataSet.DIM_X); final int dimY = dataSet.getShape(DataSet.DIM_Y); Platform.runLater(() -> { diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java index 1be3db70c..1b216657f 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java @@ -110,6 +110,7 @@ protected void openLineIn() { public void fillTestData() { lock().writeLockGuard( () -> synth.decode(history[2].elements(), frameSize, updatePeriod, samplingRate, N_SYNTHESISER_BITS)); + fireInvalidated(ChartBits.DataSetData); } @Override @@ -165,6 +166,7 @@ public void pause() { public void reset() { synth.reset(); + fireInvalidated(ChartBits.DataSetData); } public void setFrameCount(int frameCount) { @@ -365,6 +367,7 @@ protected void reinitializeData() { ArrayUtils.fillArray(history[DIM_Z].elements(), 0.0f); synth.setBufferLength(2 * frameSize); lineBuffer = new DoubleCircularBuffer(2 * frameSize); + fireInvalidated(ChartBits.DataSetData); if (taskDataUpdate != null) { start(); From 621cc04a17334f62eb2adb0878f658ecf9dd36ab Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 26 Jul 2023 11:38:13 +0200 Subject: [PATCH 30/81] fixed an initialization issue where postLayout would run by itself --- .../main/java/io/fair_acc/chartfx/Chart.java | 21 +++++------ .../fair_acc/chartfx/ui/utils/LayoutHook.java | 36 +++++++++++++------ .../fair_acc/dataset/event/EventSource.java | 8 +++++ .../fair_acc/dataset/utils/AssertUtils.java | 3 +- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 8b8c5a871..b268d323f 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -76,7 +76,7 @@ public abstract class Chart extends Region implements EventSource { // The chart has two different states, one that includes everything and is only ever on the JavaFX thread, and // a thread-safe one that receives dataSet updates and forwards them on the JavaFX thread. - protected final BitState state = BitState.initClean(this, BitState.ALL_BITS) + protected final BitState state = BitState.initDirty(this, BitState.ALL_BITS) .addChangeListener(ChartBits.KnownMask, (src, bits) -> layoutHooks.registerOnce()) .addChangeListener(ChartBits.ChartLayout, (src, bits) -> super.requestLayout()); protected final BitState dataSetState = BitState.initDirtyMultiThreaded(this, BitState.ALL_BITS) @@ -104,10 +104,7 @@ public abstract class Chart extends Region implements EventSource { /** * When true the chart will display a legend if the chart implementation supports a legend. */ - private final StyleableBooleanProperty legendVisible = CSS.createBooleanProperty(this, "legendVisible", true, () -> { - updateLegend(getDatasets(), getRenderers()); - requestLayout(); - }); + private final StyleableBooleanProperty legendVisible = CSS.createBooleanProperty(this, "legendVisible", true, state.onAction(ChartBits.ChartLegend)); // isCanvasChangeRequested is a recursion guard to update canvas only once protected boolean isCanvasChangeRequested; @@ -218,6 +215,7 @@ private static Pane getSidePane(Side side, ChartPane parent, Map map showing.set(false); return; } + layoutHooks.registerOnce(); // add listener newScene.windowProperty().addListener(windowPropertyListener); @@ -256,7 +254,7 @@ protected void invalidated() { AssertUtils.notNull("Side must not be null", newVal); ChartPane.setSide(titleLabel, newVal); return newVal; - }, this::requestLayout); + }, state.onAction(ChartBits.ChartLayout)); /** * The side of the chart where the legend should be displayed default value Side.BOTTOM @@ -273,7 +271,7 @@ protected void invalidated() { legend.setVertical(newVal.isVertical()); return newVal; - }, this::requestLayout); + }, state.onAction(ChartBits.ChartLayout)); /** * The node to display as the Legend. Subclasses can set a node here to be displayed on a side as the legend. If no @@ -652,7 +650,7 @@ public boolean isToolBarPinned() { } protected void runPreLayout() { - if (state.isDirty(ChartBits.ChartLegend)) { + if (state.isDirty(ChartBits.ChartLegend, ChartBits.ChartDataSets, ChartBits.ChartRenderers)) { updateLegend(getDatasets(), getRenderers()); } @@ -888,7 +886,7 @@ protected void datasetsChanged(final ListChangeListener.Change set.removeListener(dataSetState)); // renderer.getAxes().removeListener(axesChangeListenerLocal); // renderer.getAxes().forEach(this::removeAxisFromChildren); - fireInvalidated(ChartBits.ChartRenderers, ChartBits.ChartDataSets); }); } // reset change to allow derived classes to add additional listeners to renderer changes change.reset(); + fireInvalidated(ChartBits.ChartLayout, ChartBits.ChartRenderers, ChartBits.ChartLegend); - requestLayout(); - updateLegend(getDatasets(), getRenderers()); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java index f33f63e59..08e210f0a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java @@ -1,5 +1,6 @@ package io.fair_acc.chartfx.ui.utils; +import io.fair_acc.dataset.utils.AssertUtils; import javafx.scene.Node; import javafx.scene.Scene; @@ -40,11 +41,11 @@ public static LayoutHook newPreAndPostHook(Node node, Runnable preLayoutAction, private LayoutHook(Node node, Runnable preLayoutAction, Runnable postLayoutAction) { this.node = node; - this.preLayoutAction = preLayoutAction; - this.postLayoutAction = postLayoutAction; + this.preLayoutAction = AssertUtils.notNull("preLayoutAction", preLayoutAction); + this.postLayoutAction = AssertUtils.notNull("preLayoutAction", postLayoutAction); } - public void registerOnce() { + public LayoutHook registerOnce() { // Scene has changed -> remove the old one first if (registeredScene != null && registeredScene != node.getScene()) { unregister(); @@ -52,27 +53,40 @@ public void registerOnce() { // Register only if we haven't already registered if (registeredScene == null && node.getScene() != null) { registeredScene = node.getScene(); - registeredScene.addPreLayoutPulseListener(preLayoutAction); - registeredScene.addPostLayoutPulseListener(postLayoutAndRemove); + registeredScene.addPreLayoutPulseListener(preLayoutAndAdd); } + return this; } - private void unregister() { - registeredScene.removePreLayoutPulseListener(preLayoutAction); - registeredScene.removePostLayoutPulseListener(postLayoutAndRemove); - registeredScene = null; + private void runPreLayoutAndAdd() { + // Called before proper initialization + if (preLayoutAction == null) { + return; + } + // We don't want to be in a position where the post layout listener + // runs by itself, so we don't register until we made sure that the + // pre-layout action ran before. + preLayoutAction.run(); + registeredScene.addPostLayoutPulseListener(postLayoutAndRemove); } - private void runPostlayoutAndRemove() { + private void runPostLayoutAndRemove() { postLayoutAction.run(); unregister(); } + private void unregister() { + registeredScene.removePreLayoutPulseListener(preLayoutAndAdd); + registeredScene.removePostLayoutPulseListener(postLayoutAndRemove); + registeredScene = null; + } + final Node node; final Runnable preLayoutAction; final Runnable postLayoutAction; - final Runnable postLayoutAndRemove = this::runPostlayoutAndRemove; + final Runnable preLayoutAndAdd = this::runPreLayoutAndAdd; + final Runnable postLayoutAndRemove = this::runPostLayoutAndRemove; Scene registeredScene = null; } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java index b3d493756..63411e6ed 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java @@ -66,6 +66,14 @@ default void fireInvalidated(IntSupplier bit0, IntSupplier bit1) { getBitState().setDirty(bit0.getAsInt() | bit1.getAsInt()); } + default void fireInvalidated(IntSupplier bit0, IntSupplier bit1, IntSupplier bit2) { + getBitState().setDirty(bit0.getAsInt() | bit1.getAsInt() | bit2.getAsInt()); + } + + default void fireInvalidated(IntSupplier bit0, IntSupplier bit1, IntSupplier bit2, IntSupplier bit3) { + getBitState().setDirty(bit0.getAsInt() | bit1.getAsInt() | bit2.getAsInt()| bit3.getAsInt()); + } + /** * @return list containing all update event listener (needs to be provided by implementing class) */ diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/AssertUtils.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/AssertUtils.java index babffed3f..458e48804 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/AssertUtils.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/AssertUtils.java @@ -385,9 +385,10 @@ public static void nonEmptyArray(final String name, final Object[] array) { * @param name name to be included in exception message. * @param obj object to be checked */ - public static void notNull(final String name, final T obj) { + public static T notNull(final String name, final T obj) { if (obj == null) { throw new IllegalArgumentException("The " + name + " must be non-null!"); } + return obj; } } \ No newline at end of file From 2dba3d4d220df925a94d96ef5aabc24e7342060a Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 26 Jul 2023 17:13:37 +0200 Subject: [PATCH 31/81] ported more examples --- .../chartfx/plugins/UpdateAxisLabels.java | 76 +++++++------------ .../io/fair_acc/dataset/events/ChartBits.java | 1 + .../fair_acc/dataset/spi/AbstractDataSet.java | 1 + .../sample/chart/TimeAxisNonLinearSample.java | 9 ++- .../math/ShortTimeFourierTransformSample.java | 11 ++- 5 files changed, 41 insertions(+), 57 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/UpdateAxisLabels.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/UpdateAxisLabels.java index 7c5dc6d84..be65c6c42 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/UpdateAxisLabels.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/UpdateAxisLabels.java @@ -4,6 +4,8 @@ import java.util.Map; import java.util.Optional; +import io.fair_acc.dataset.events.ChartBits; +import io.fair_acc.dataset.events.StateListener; import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -19,22 +21,22 @@ import io.fair_acc.dataset.event.AxisChangeEvent; import io.fair_acc.dataset.event.EventListener; import io.fair_acc.dataset.event.EventRateLimiter; -import io.fair_acc.dataset.event.UpdateEvent; /** * This plugin updates the labels (name and unit) of all axes according to DataSet Metadata. For now the axes are only * updated, if there is exactly one DataSet in the each Renderer or the Chart. * + * TODO: revisit this plugin. we should be able to turn this into a single chart listener and an update method (ennerf) + * * @author akrimm */ public class UpdateAxisLabels extends ChartPlugin { - private static final int UPDATE_RATE_LIMIT = 200; // maximum label update rate private static final Logger LOGGER = LoggerFactory.getLogger(UpdateAxisLabels.class); // listener bookkeeping - private Map> rendererDataSetsListeners = new HashMap<>(); - private Map chartDataSetsListeners = new HashMap<>(); + private Map> rendererDataSetsListeners = new HashMap<>(); + private Map chartDataSetsListeners = new HashMap<>(); private Map> renderersListeners = new HashMap<>(); // called whenever renderers are added or removed @@ -48,7 +50,7 @@ public class UpdateAxisLabels extends ChartPlugin { } dataSetsChanged(dataSetsChange, renderer); }; - renderer.getDatasets().forEach(ds -> dataSetChange(new AxisChangeEvent(ds, -1), renderer)); + renderer.getDatasets().forEach(ds -> dataSetChange(ds, renderer)); renderer.getDatasets().addListener(dataSetsListener); renderersListeners.put(renderer, dataSetsListener); if (LOGGER.isDebugEnabled()) { @@ -90,27 +92,13 @@ private void addRendererAndDataSetListener(Chart newChart) { } // the actual DataSet renaming logic - private void dataSetChange(UpdateEvent update, Renderer renderer) { - if (!(update instanceof AxisChangeEvent)) { - return; - } - if (LOGGER.isDebugEnabled()) { - LOGGER.atDebug().log("axis - dataSetChange for AxisChangeEvent"); - } - AxisChangeEvent axisDataUpdate = (AxisChangeEvent) update; - int dim = axisDataUpdate.getDimension(); - DataSet dataSet = (DataSet) axisDataUpdate.getSource(); + private void dataSetChange(DataSet dataSet, Renderer renderer) { if (renderer == null) { // dataset was added to / is registered at chart if (getChart().getDatasets().size() == 1) { - if (dim == -1) { // update labels for all axes - for (int dimIdx = 0; dimIdx < dataSet.getDimension(); dimIdx++) { - final int dimIndex = dimIdx; - Optional oldAxis = getChart().getAxes().stream().filter(axis -> axis.getDimIndex() == dimIndex).findFirst(); - oldAxis.ifPresent(a -> a.set(dataSet.getAxisDescription(dimIndex).getName(), dataSet.getAxisDescription(dimIndex).getUnit())); - } - } else { // update label for requested axis - Optional oldAxis = getChart().getAxes().stream().filter(axis -> axis.getDimIndex() == dim).findFirst(); - oldAxis.ifPresent(a -> a.set(dataSet.getAxisDescription(dim).getName(), dataSet.getAxisDescription(dim).getUnit())); + for (int dimIdx = 0; dimIdx < dataSet.getDimension(); dimIdx++) { + final int dimIndex = dimIdx; + Optional oldAxis = getChart().getAxes().stream().filter(axis -> axis.getDimIndex() == dimIndex).findFirst(); + oldAxis.ifPresent(a -> a.set(dataSet.getAxisDescription(dimIndex).getName(), dataSet.getAxisDescription(dimIndex).getUnit())); } } else { if (LOGGER.isWarnEnabled()) { @@ -120,17 +108,11 @@ private void dataSetChange(UpdateEvent update, Renderer renderer) { } } else { // dataset was added to / is registered at renderer if (renderer.getDatasets().size() == 1) { - if (dim == -1) { // update labels for all axes - for (int dimIdx = 0; dimIdx < dataSet.getDimension(); dimIdx++) { - final int dimIndex = dimIdx; - Optional oldAxis = renderer.getAxes().stream().filter(axis -> axis.getDimIndex() == dimIndex).findFirst() // - .or(() -> getChart().getAxes().stream().filter(axis -> axis.getDimIndex() == dimIndex).findFirst()); - oldAxis.ifPresent(a -> a.set(dataSet.getAxisDescription(dimIndex).getName(), dataSet.getAxisDescription(dimIndex).getUnit())); - } - } else { // update label for requested axis - Optional oldAxis = renderer.getAxes().stream().filter(axis -> axis.getDimIndex() == dim).findFirst() // - .or(() -> getChart().getAxes().stream().filter(axis -> axis.getDimIndex() == dim).findFirst()); - oldAxis.ifPresent(a -> a.set(dataSet.getAxisDescription(dim).getName(), dataSet.getAxisDescription(dim).getUnit())); + for (int dimIdx = 0; dimIdx < dataSet.getDimension(); dimIdx++) { + final int dimIndex = dimIdx; + Optional oldAxis = renderer.getAxes().stream().filter(axis -> axis.getDimIndex() == dimIndex).findFirst() // + .or(() -> getChart().getAxes().stream().filter(axis -> axis.getDimIndex() == dimIndex).findFirst()); + oldAxis.ifPresent(a -> a.set(dataSet.getAxisDescription(dimIndex).getName(), dataSet.getAxisDescription(dimIndex).getUnit())); } } else { if (LOGGER.isWarnEnabled()) { @@ -142,7 +124,7 @@ private void dataSetChange(UpdateEvent update, Renderer renderer) { } private void dataSetsChanged(ListChangeListener.Change change, Renderer renderer) { - Map dataSetListeners; + Map dataSetListeners; if (renderer == null) { dataSetListeners = chartDataSetsListeners; } else if (rendererDataSetsListeners.containsKey(renderer)) { @@ -157,16 +139,14 @@ private void dataSetsChanged(ListChangeListener.Change change while (change.next()) { if (change.wasAdded()) { for (DataSet dataSet : change.getAddedSubList()) { - EventListener dataSetListener = update -> FXUtils.runFX(() -> dataSetChange(update, renderer)); - EventRateLimiter rateLimitedDataSetListener = new EventRateLimiter(dataSetListener, UPDATE_RATE_LIMIT); - dataSet.addListener(rateLimitedDataSetListener); - dataSetListeners.put(dataSet, rateLimitedDataSetListener); - dataSetChange(new AxisChangeEvent(dataSet, -1), renderer); // NOPMD - normal in-loop instantiation + var dataSetListener = FXUtils.runOnFxThread((src, bits) -> dataSetChange(dataSet, renderer)); + dataSet.getBitState().addChangeListener(ChartBits.DataSetName, dataSetListener); + dataSetListeners.put(dataSet, dataSetListener); } } if (change.wasRemoved()) { for (DataSet dataSet : change.getRemoved()) { - EventListener listener = dataSetListeners.get(dataSet); + var listener = dataSetListeners.get(dataSet); if (listener != null) { dataSet.removeListener(listener); dataSetListeners.remove(dataSet); @@ -187,7 +167,7 @@ private void removeRendererAndDataSetListener(Chart oldChart) { // setup all the listeners private void setupDataSetListeners(Renderer renderer, ObservableList dataSets) { - Map dataSetListeners; + Map dataSetListeners; if (renderer == null) { dataSetListeners = chartDataSetsListeners; } else if (rendererDataSetsListeners.containsKey(renderer)) { @@ -202,17 +182,15 @@ private void setupDataSetListeners(Renderer renderer, ObservableList da renderersListeners.put(renderer, rendererListener); dataSets.forEach((DataSet dataSet) -> { - EventListener dataSetListener = update -> FXUtils.runFX(() -> dataSetChange(update, renderer)); - EventRateLimiter rateLimitedDataSetListener = new EventRateLimiter(dataSetListener, UPDATE_RATE_LIMIT); - dataSet.addListener(rateLimitedDataSetListener); - dataSetListeners.put(dataSet, rateLimitedDataSetListener); - dataSetChange(new AxisChangeEvent(dataSet, -1), renderer); + var dataSetListener = FXUtils.runOnFxThread((src, bits) -> dataSetChange(dataSet, renderer)); + dataSet.getBitState().addChangeListener(ChartBits.DataSetName, dataSetListener); + dataSetListeners.put(dataSet, dataSetListener); }); } // remove Listeners private void teardownDataSetListeners(Renderer renderer, ObservableList dataSets) { - Map dataSetListeners; + Map dataSetListeners; if (renderer == null) { dataSetListeners = chartDataSetsListeners; } else if (rendererDataSetsListeners.containsKey(renderer)) { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index 52682981e..8f6748784 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -25,6 +25,7 @@ public enum ChartBits implements IntSupplier { DataSetVisibility, DataSetData, DataSetRange, + DataSetName, DataSetMetaData, DataSetPermutation, DataViewWindow; // TODO: WindowMinimisedEvent/WindowMaximisedEvent/... necessary? diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java index ce374026e..2f64a9acc 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractDataSet.java @@ -507,6 +507,7 @@ public D setEditConstraints(final EditConstraints constraints) { */ public D setName(final String name) { this.name = name; + fireInvalidated(ChartBits.DataSetName); return getThis(); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java index 1ed42cc2a..a57d4118c 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java @@ -69,7 +69,7 @@ public void run() { dataSet.add(now - (dataSet.getMaxLength()), Double.NaN, 0., 0.); // first point for long-term history dataSet.add(now, 100 * Math.cos(2.0 * Math.PI * now), 0., 0.); } - }, 1000, 40); + }, 0, 40); long startTime = ProcessingProfiler.getTimeStamp(); chart.getDatasets().add(dataSet); @@ -86,10 +86,10 @@ public void run() { final var xValueIndicator = new XValueIndicator(xAxis1, xAxis1.getWidth() * xAxis1.getThreshold(), "long-short"); xValueIndicator.setEditable(false); - dataSet.addListener(evt -> { + dataSet.getBitState().addInvalidateListener(FXUtils.runOnFxThread((src, bits) -> { final var locator = xAxis1.getValueForDisplay(xAxis1.getThreshold() * xAxis1.getWidth()); - FXUtils.runFX(() -> xValueIndicator.setValue(locator)); - }); + xValueIndicator.setValue(locator); + })); chart.getPlugins().add(xValueIndicator); final var spWeight = new Slider(0.0, 1.0, xAxis1.getWeight()); @@ -105,6 +105,7 @@ public void run() { ProcessingProfiler.getTimeDiff(startTime, "adding chart into StackPane"); final var diagChart = new XYChart(); + diagChart.setMaxHeight(300); diagChart.getPlugins().addAll(new Zoomer(), new EditAxis(), new DataPointTooltip()); final var function = new DoubleDataSet("function"); final var inverse = new DoubleDataSet("inverse"); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/math/ShortTimeFourierTransformSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/math/ShortTimeFourierTransformSample.java index 5345ad616..d8c75816c 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/math/ShortTimeFourierTransformSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/math/ShortTimeFourierTransformSample.java @@ -2,6 +2,7 @@ import java.util.Random; +import io.fair_acc.dataset.events.ChartBits; import javafx.application.Application; import javafx.beans.InvalidationListener; import javafx.beans.value.ObservableValue; @@ -50,6 +51,8 @@ * TODO: * - add nonzero imaginary part to sample data? * - move computations out of javaFX application thread + * + * TODO: example was not tested after event refactoring (ennerf) * * @author akrimm */ @@ -106,7 +109,7 @@ public Node getContent() { chart3.getRenderers().add(new MetaDataRenderer(chart3)); chart3.getDatasets().add(rawData); - rawData.addListener(evt -> stft(rawData, stftData)); + rawData.addListener((src, bits) -> stft(rawData, stftData)); // Short Time Fourier Transform chart chart1 = new XYChart(); final ContourDataSetRenderer contourChartRenderer1 = new ContourDataSetRenderer(); @@ -131,7 +134,7 @@ public Node getContent() { chart1.getPlugins().add(new EditAxis()); chart1.getDatasets().add(TransposedDataSet.transpose(stftData, true)); - rawData.addListener(evt -> wavelet(rawData, waveletData)); + rawData.addListener((src, bits) -> wavelet(rawData, waveletData)); // Wavelet Transform Chart chart2 = new XYChart(); final ContourDataSetRenderer contourChartRenderer2 = new ContourDataSetRenderer(); @@ -217,7 +220,7 @@ private void stft(final DataSet inputData, final DoubleGridDataSet outputData) { outputData.clearData(); outputData.clearMetaInfo().getErrorList().add(e.getMessage()); } - outputData.invokeListener(); + outputData.fireInvalidated(ChartBits.DataSetData); } private Node stftSettingsPane() { @@ -310,7 +313,7 @@ private void wavelet(final DataSet inputData, final DoubleGridDataSet outputData } catch (Exception e) { ((DataSetMetaData) outputData).getErrorList().add(e.getMessage()); } - outputData.invokeListener(); + outputData.fireInvalidated(ChartBits.DataSetData); } private Node waveletSettingsPane() { From f620aea307a0b9cdab2f6a3655204f4ed0959673 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 26 Jul 2023 17:31:01 +0200 Subject: [PATCH 32/81] updated data viewer sample --- .../chartfx/viewer/DataViewWindow.java | 55 +++++-------------- .../sample/chart/DataViewerSample.java | 29 +++------- 2 files changed, 24 insertions(+), 60 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java index 3d488a2dd..0fbff744f 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java @@ -1,12 +1,11 @@ package io.fair_acc.chartfx.viewer; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; +import io.fair_acc.dataset.events.BitState; +import io.fair_acc.dataset.events.ChartBits; import javafx.beans.DefaultProperty; import javafx.beans.NamedArg; import javafx.beans.property.BooleanProperty; @@ -54,7 +53,6 @@ import io.fair_acc.chartfx.viewer.event.WindowMinimisingEvent; import io.fair_acc.chartfx.viewer.event.WindowRestoredEvent; import io.fair_acc.chartfx.viewer.event.WindowRestoringEvent; -import io.fair_acc.dataset.event.EventListener; import io.fair_acc.dataset.event.EventSource; /** @@ -66,6 +64,7 @@ @DefaultProperty(value = "content") public class DataViewWindow extends BorderPane implements EventSource { private static final Logger LOGGER = LoggerFactory.getLogger(DataViewWindow.class); + private final BitState state = BitState.initDirty(this); private static final int MIN_DRAG_BORDER_WIDTH = 20; private static final String WINDOW_CSS = "DataViewer.css"; private static final String CSS_WINDOW = "window"; @@ -78,11 +77,7 @@ public class DataViewWindow extends BorderPane implements EventSource { private static final String CSS_TITLE_LABEL = "window-titlelabel"; // needed for EventSource listener interface - protected transient boolean parallelListeners = false; - private final transient AtomicBoolean autoNotification = new AtomicBoolean(true); private final transient AtomicBoolean updatingStage = new AtomicBoolean(false); - private final transient List updateListeners = Collections.synchronizedList(new LinkedList<>()); - private final StringProperty name = new SimpleStringProperty(this, "name", ""); private final HBox leftButtons = new HBox(); private final Label titleLabel = new Label(); @@ -126,22 +121,8 @@ public void set(final WindowState state) { } super.set(state); + fireInvalidated(ChartBits.DataViewWindow); - switch (state) { - case WINDOW_MINIMISED: - invokeListener(new WindowMinimisedEvent(DataViewWindow.this), parallelListeners); - break; - case WINDOW_MAXIMISED: - invokeListener(new WindowMaximisedEvent(DataViewWindow.this), parallelListeners); - break; - case WINDOW_RESTORED: - invokeListener(new WindowRestoredEvent(DataViewWindow.this), parallelListeners); - break; - case WINDOW_CLOSED: - invokeListener(new WindowClosedEvent(DataViewWindow.this), parallelListeners); - break; - default: - } } }; @@ -174,13 +155,13 @@ public void set(final WindowState state) { updatingStage.set(true); if (this.isMaximised()) { - invokeListener(new WindowRestoringEvent(this), parallelListeners); + fireInvalidated(ChartBits.DataViewWindow); } else { // either minimised, normal (and/or detached) state if (isMinimised()) { - invokeListener(new WindowRestoringEvent(this), parallelListeners); + fireInvalidated(ChartBits.DataViewWindow); } else { - invokeListener(new WindowMaximisingEvent(this), parallelListeners); + fireInvalidated(ChartBits.DataViewWindow); } } if (dialog.isShowing()) { @@ -192,7 +173,7 @@ public void set(final WindowState state) { if (getParentView().getMinimisedChildren().contains(this)) { // this DataViewWindow is minimised - invokeListener(new WindowRestoringEvent(this), parallelListeners); + fireInvalidated(ChartBits.DataViewWindow); getParentView().getMinimisedChildren().remove(this); setMinimised(false); getParentView().getVisibleChildren().add(this); @@ -225,7 +206,7 @@ public void set(final WindowState state) { } updatingStage.set(true); - invokeListener(new WindowMinimisingEvent(this), parallelListeners); + fireInvalidated(ChartBits.DataViewWindow); if (dialog.isShowing()) { dialog.hide(); maximizeRestoreButton.getStyleClass().setAll(CSS_WINDOW_MAXIMIZE_ICON); @@ -252,7 +233,7 @@ public void set(final WindowState state) { return; } updatingStage.set(true); - invokeListener(new WindowClosingEvent(this), parallelListeners); + fireInvalidated(ChartBits.DataViewWindow); // asked to remove pane getParentView().getMinimisedChildren().remove(this); @@ -384,11 +365,6 @@ public final void addCloseWindowButton() { } } - @Override - public AtomicBoolean autoNotification() { - return autoNotification; - } - public BooleanProperty closedProperty() { return closedWindow; } @@ -636,8 +612,8 @@ public String toString() { } @Override - public List updateEventListener() { - return updateListeners; + public BitState getBitState() { + return state; } public ObjectProperty windowDecorationProperty() { @@ -780,7 +756,6 @@ public ExternalStage() { } public void hide(final DataViewWindow dataViewWindow) { - dataViewWindow.invokeListener(new WindowRestoringEvent(dataViewWindow), dataViewWindow.parallelListeners); titleProperty().unbind(); scene.setRoot(new StackPane()); @@ -788,7 +763,7 @@ public void hide(final DataViewWindow dataViewWindow) { dataViewWindow.getParentView().getUndockedChildren().remove(dataViewWindow); dataViewWindow.getParentView().getVisibleChildren().add(dataViewWindow); - dataViewWindow.invokeListener(new WindowRestoredEvent(dataViewWindow), dataViewWindow.parallelListeners); + dataViewWindow.fireInvalidated(ChartBits.DataViewWindow); } public boolean isMaximised() { @@ -836,7 +811,7 @@ public void show(final DataViewWindow dataViewWindow, final MouseEvent mouseEven setY(mouseEvent.getScreenY()); } - dataViewWindow.invokeListener(new WindowDetachingEvent(dataViewWindow), dataViewWindow.parallelListeners); + dataViewWindow.fireInvalidated(ChartBits.DataViewWindow); posX = getX(); posY = getY(); @@ -870,7 +845,7 @@ public void show(final DataViewWindow dataViewWindow, final MouseEvent mouseEven dataViewWindow.setWindowState(WindowState.WINDOW_RESTORED); dataViewWindow.setDetached(true); - dataViewWindow.invokeListener(new WindowDetachedEvent(dataViewWindow), dataViewWindow.parallelListeners); + dataViewWindow.fireInvalidated(ChartBits.DataViewWindow); } private String getName() { diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/DataViewerSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/DataViewerSample.java index 88d4412f7..2c289f509 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/DataViewerSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/DataViewerSample.java @@ -1,12 +1,9 @@ package io.fair_acc.sample.chart; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; +import io.fair_acc.dataset.events.ChartBits; +import io.fair_acc.dataset.events.StateListener; import javafx.animation.Animation; import javafx.animation.RotateTransition; import javafx.application.Application; @@ -77,17 +74,13 @@ public class DataViewerSample extends ChartSample { private static final int NUM_OF_POINTS = 20; - private final EventListener dataWindowEventListener = evt -> { - if (evt instanceof WindowUpdateEvent) { - final WindowUpdateEvent wEvt = (WindowUpdateEvent) evt; - LOGGER.atInfo().addArgument(wEvt).addArgument(wEvt.getType()).log("received window update event {} of type {}"); - } else { - LOGGER.atInfo().addArgument(evt).addArgument(evt.getMessage()).log("received generic window update event {} with message {}"); - } - - if (evt instanceof WindowClosedEvent) { - LOGGER.atInfo().addArgument(evt.getSource()).log("window {} closed"); + private final StateListener dataWindowEventListener = (srcState, bits) -> { + if (!ChartBits.DataViewWindow.isSet(bits)) { + return; } + srcState.clear(ChartBits.DataViewWindow); // clear manually to keep example simple + var viewWindow = (DataViewWindow) srcState.getSource(); + LOGGER.atInfo().addArgument(viewWindow.getWindowState()).log("received window update event for new state {}"); }; @Override @@ -173,10 +166,6 @@ private void addChartToView(DataView view1, final ComboBox win } newDataViewerPane.addListener(dataWindowEventListener); - newDataViewerPane.addListener(windowEvent -> { - // print window state explicitly - LOGGER.atInfo().addArgument(newDataViewerPane.getName()).addArgument(newDataViewerPane.getWindowState()).log("explicit '{}' window state is {}"); - }); newDataViewerPane.closedProperty().addListener((ch, o, n) -> { LOGGER.atInfo().log("newDataViewerPane Window '" + newDataViewerPane.getName() + "' has been closed - performing clean-up actions"); From 470b1f5f0a226acaf2d408a9106a953f1c209d44 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 26 Jul 2023 17:46:04 +0200 Subject: [PATCH 33/81] changed data set measurements to compile (broken for now) --- .../AbstractChartMeasurement.java | 22 ++++++++----------- .../measurements/DataSetMeasurements.java | 10 ++++----- .../measurements/SimpleMeasurements.java | 2 +- .../io/fair_acc/dataset/events/ChartBits.java | 3 ++- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java index 6d39f7612..6770a8208 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java @@ -68,6 +68,7 @@ */ public abstract class AbstractChartMeasurement implements EventListener, EventSource { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractChartMeasurement.class); + private final BitState state = BitState.initDirty(this); private static final int MIN_DRAG_BORDER_WIDTH = 30; protected static final double DEFAULT_MIN = Double.NEGATIVE_INFINITY; protected static final double DEFAULT_MAX = Double.POSITIVE_INFINITY; @@ -128,9 +129,9 @@ public abstract class AbstractChartMeasurement implements EventListener, EventSo }; private final ListChangeListener valueIndicatorsUserChangeListener = (final Change change) -> { while (change.next()) { - change.getRemoved().forEach(oldIndicator -> oldIndicator.removeListener(sliderChanged)); + //TODO: change.getRemoved().forEach(oldIndicator -> oldIndicator.removeListener(sliderChanged)); - change.getAddedSubList().stream().filter(newIndicator -> !newIndicator.getBitState().contains(sliderChanged)).forEach(newIndicator -> newIndicator.addListener(sliderChanged)); + //TODO: change.getAddedSubList().stream().filter(newIndicator -> !newIndicator.getBitState().contains(sliderChanged)).forEach(newIndicator -> newIndicator.addListener(sliderChanged)); } }; @@ -260,14 +261,9 @@ public StringProperty titleProperty() { return title; } - @Deprecated - private void invokeListener(Object event, boolean parallel) { - // TODO: figure out what all this does - } - @Override public BitState getBitState() { - return null; // TODO: refactor class + return state; } public DoubleProperty valueProperty() { @@ -328,7 +324,7 @@ protected void removeSliderChangeListener() { } final List allIndicators = chart.getPlugins().stream().filter(p -> p instanceof AbstractSingleValueIndicator).map(p -> (AbstractSingleValueIndicator) p).collect(Collectors.toList()); allIndicators.forEach((final AbstractSingleValueIndicator indicator) -> { - indicator.removeListener(sliderChanged); + //TODO: indicator.removeListener(sliderChanged); getValueIndicatorsUser().remove(indicator); }); } @@ -339,7 +335,7 @@ protected void cleanUpSuperfluousIndicators() { return; } final List allIndicators = chart.getPlugins().stream().filter(p -> p instanceof AbstractSingleValueIndicator).map(p -> (AbstractSingleValueIndicator) p).collect(Collectors.toList()); - allIndicators.stream().filter((final AbstractSingleValueIndicator indicator) -> indicator.isAutoRemove() && indicator.getBitState().isEmpty()).forEach((final AbstractSingleValueIndicator indicator) -> getMeasurementPlugin().getChart().getPlugins().remove(indicator)); + //TODO: allIndicators.stream().filter((final AbstractSingleValueIndicator indicator) -> indicator.isAutoRemove() && indicator.getBitState().isEmpty()).forEach((final AbstractSingleValueIndicator indicator) -> getMeasurementPlugin().getChart().getPlugins().remove(indicator)); } protected void updateSlider() { @@ -376,9 +372,9 @@ protected AbstractSingleValueIndicator updateSlider(final int requestedIndex) { getMeasurementPlugin().getChart().getPlugins().add(sliderIndicator); } - if (!sliderIndicator.getBitState().contains(sliderChanged)) { - sliderIndicator.addListener(sliderChanged); - } + //TODO: if (!sliderIndicator.getBitState().contains(sliderChanged)) { + //TODO: sliderIndicator.addListener(sliderChanged); + //TODO: } return sliderIndicator; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java index 4448f26ee..384398fc0 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java @@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import io.fair_acc.dataset.events.ChartBits; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -240,8 +241,7 @@ public void handle(final UpdateEvent event) { FXUtils.runFX(() -> getValueField().setValue(val)); if (event != null) { - // republish updateEvent - invokeListener(event); + fireInvalidated(ChartBits.DataSetMeasurement); } } @@ -935,7 +935,7 @@ public ExternalStage() { chart.getPlugins().add(new TableViewer()); final Scene scene = new Scene(chart, 640, 480); - renderer.getDatasets().get(0).addListener(titleListener); + // TODO: renderer.getDatasets().get(0).addListener(titleListener); setScene(scene); FXUtils.runFX(this::show); @@ -946,9 +946,9 @@ public ExternalStage() { }); setOnCloseRequest(evt -> { - chart.getRenderers().remove(renderer); + // TODO: chart.getRenderers().remove(renderer); chart.getAxes().clear(); - renderer.getDatasets().get(0).removeListener(titleListener); + // TODO: renderer.getDatasets().get(0).removeListener(titleListener); xAxis.setSide(Side.TOP); yAxis.setSide(Side.RIGHT); graphDetached.set(false); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java index 3c21d361c..2d7e7016c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java @@ -215,7 +215,7 @@ public void handle(final UpdateEvent event) { if (event != null) { // republish updateEvent - invokeListener(event); + // TODO: invokeListener(event); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index 8f6748784..7d790f534 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -28,7 +28,8 @@ public enum ChartBits implements IntSupplier { DataSetName, DataSetMetaData, DataSetPermutation, - DataViewWindow; // TODO: WindowMinimisedEvent/WindowMaximisedEvent/... necessary? + DataViewWindow, // TODO: WindowMinimisedEvent/WindowMaximisedEvent/... necessary? + DataSetMeasurement; private static final ChartBits[] AllBits = ChartBits.values(); public static final int KnownMask = BitState.mask(AllBits); From 5e5dd326c018c47619fea09a91cd5eb99f2c7339 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 26 Jul 2023 18:29:11 +0200 Subject: [PATCH 34/81] removed some outdated unit test code --- .../AbstractChartMeasurement.java | 1 - .../chartfx/axes/spi/AbstractAxisTests.java | 8 -- .../fair_acc/dataset/event/EventSource.java | 6 + .../event/EventAndHelperClassTests.java | 117 ---------------- .../dataset/event/EventBenchmark.java | 129 ------------------ .../dataset/event/EventRateLimiterTests.java | 68 --------- .../dataset/event/EventSourceTests.java | 115 ---------------- .../dataset/event/TestEventSource.java | 13 +- .../dataset/locks/DataSetLockTest.java | 3 - .../dataset/spi/DataSetBuilderTests.java | 13 +- .../dataset/spi/DimReductionDataSetTests.java | 5 +- .../dataset/spi/DoubleGridDataSetTests.java | 4 - .../dataset/spi/GenericDataSetTests.java | 5 +- .../testdata/spi/ErrorTestDataSetTest.java | 1 - 14 files changed, 21 insertions(+), 467 deletions(-) delete mode 100644 chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java delete mode 100644 chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventBenchmark.java delete mode 100644 chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventRateLimiterTests.java delete mode 100644 chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventSourceTests.java diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java index 6770a8208..e8d14a5a3 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/AbstractChartMeasurement.java @@ -92,7 +92,6 @@ public abstract class AbstractChartMeasurement implements EventListener, EventSo protected int lastLayoutRow; protected final int requiredNumberOfIndicators; protected final int requiredNumberOfDataSets; - private final EventListener sliderChanged = new EventRateLimiter(this, DEFAULT_UPDATE_RATE_LIMIT); protected final GridPane gridPane = new GridPane(); private final ParameterMeasurements plugin; private final String measurementName; diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java index 1a114b0c4..bd61acdff 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java @@ -179,14 +179,6 @@ void testHelper() { assertDoesNotThrow(axis::clear); assertDoesNotThrow(axis::forceRedraw); - final AtomicInteger counter = new AtomicInteger(); - assertDoesNotThrow(axis::fireInvalidated); - assertDoesNotThrow(() -> FXUtils.runAndWait(axis::fireInvalidated)); - assertEquals(0, counter.get()); - axis.addListener(evt -> counter.incrementAndGet()); - assertDoesNotThrow(axis::fireInvalidated); - assertDoesNotThrow(() -> FXUtils.runAndWait(axis::fireInvalidated)); - assertEquals(2, counter.get()); } @Test diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java index 63411e6ed..b9f4b7267 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java @@ -1,6 +1,7 @@ package io.fair_acc.dataset.event; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.IntSupplier; import io.fair_acc.dataset.events.BitState; @@ -33,6 +34,11 @@ default void addListener(StateListener listener) { getBitState().getBits(listener); // initialize to the current state } + @Deprecated // for backwards compatibility with tests + default void addListener(Consumer listener) { + getBitState().addInvalidateListener((src, bits) -> listener.accept(new InvalidatedEvent((EventSource) src))); + } + /** * Removes the given listener from the list of listeners, that are notified whenever the value of the * {@code UpdateSource} becomes invalid. diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java deleted file mode 100644 index b0ccf1a15..000000000 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventAndHelperClassTests.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.fair_acc.dataset.event; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -public class EventAndHelperClassTests { - private final static Object payload = new Object(); - private static final EventSource testEventSource = new EventSource() { - @Override - public AtomicBoolean autoNotification() { - return null; - } - - @Override - public List getBitState() { - return null; - } - }; - - @Test - public void testEventSource() { - assertNull(testEventSource.autoNotification()); - assertNull(testEventSource.getBitState()); - } - - @DisplayName("UpdateEvent class constructors") - @ParameterizedTest(name = "event class - {0}") - @ValueSource(classes = { AddedDataEvent.class, AddedDataEvent.class, InvalidatedEvent.class, RemovedDataEvent.class, // - UpdatedDataEvent.class, UpdatedMetaDataEvent.class, UpdateEvent.class }) - public void - testConstructors(final Class eventClass) // - throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { - Constructor constructor1 = eventClass.getConstructor(EventSource.class); - assertNotNull(constructor1); - assertEquals(1, constructor1.getParameterCount()); - UpdateEvent instance1 = constructor1.newInstance(testEventSource); - assertEquals(testEventSource, instance1.getSource(), "event source equality"); - assertNull(instance1.getMessage(), "event message equality"); - assertNull(instance1.getPayLoad(), "payload equality"); - - Constructor constructor2 = eventClass.getConstructor(EventSource.class, String.class); - assertNotNull(constructor2); - assertEquals(2, constructor2.getParameterCount()); - UpdateEvent instance2 = constructor2.newInstance(testEventSource, "test2"); - assertEquals(testEventSource, instance2.getSource(), "event source equality"); - assertEquals("test2", instance2.getMessage(), "event message equality"); - assertNull(instance2.getPayLoad(), "payload equality"); - - Constructor constructor3 = eventClass.getConstructor(EventSource.class, String.class, Object.class); - assertNotNull(constructor3); - assertEquals(3, constructor3.getParameterCount()); - UpdateEvent instance3 = constructor3.newInstance(testEventSource, "test3", payload); - assertEquals(testEventSource, instance3.getSource(), "event source equality"); - assertEquals("test3", instance3.getMessage(), "event message equality"); - assertEquals(payload, instance3.getPayLoad(), "payload equality"); - } - - @DisplayName("UpdateAxisEvent class constructors") - @ParameterizedTest(name = "event class - {0}") - @ValueSource(classes = { AxisChangeEvent.class, AxisNameChangeEvent.class, AxisRangeChangeEvent.class }) - public void testAxisConstructors(final Class eventClass) // - throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { - Constructor constructor0 = eventClass.getConstructor(EventSource.class); - assertNotNull(constructor0); - assertEquals(1, constructor0.getParameterCount()); - AxisChangeEvent instance0 = constructor0.newInstance(testEventSource); - assertEquals(testEventSource, instance0.getSource(), "event source equality"); - assertEquals(-1, instance0.getDimension(), "event message equality"); - assertNull(instance0.getMessage(), "event message equality"); - assertNull(instance0.getPayLoad(), "payload equality"); - - Constructor constructor1 = eventClass.getConstructor(EventSource.class, int.class); - assertNotNull(constructor1); - assertEquals(2, constructor1.getParameterCount()); - AxisChangeEvent instance1 = constructor1.newInstance(testEventSource, 5); - assertEquals(testEventSource, instance1.getSource(), "event source equality"); - assertEquals(5, instance1.getDimension(), "axis id identity"); - assertNull(instance1.getMessage(), "event message equality"); - assertNull(instance1.getPayLoad(), "payload equality"); - - Constructor constructor2 = eventClass.getConstructor(EventSource.class, String.class, int.class); - assertNotNull(constructor2); - assertEquals(3, constructor2.getParameterCount()); - AxisChangeEvent instance2 = constructor2.newInstance(testEventSource, "test2", 5); - assertEquals(testEventSource, instance2.getSource(), "event source equality"); - assertEquals(5, instance2.getDimension(), "axis id identity"); - assertEquals("test2", instance2.getMessage(), "event message equality"); - assertNull(instance2.getPayLoad(), "payload equality"); - - Constructor constructor3 = eventClass.getConstructor(EventSource.class, String.class, Object.class, int.class); - assertNotNull(constructor3); - assertEquals(4, constructor3.getParameterCount()); - AxisChangeEvent instance3 = constructor3.newInstance(testEventSource, "test3", payload, 5); - assertEquals(testEventSource, instance3.getSource(), "event source equality"); - assertEquals(5, instance3.getDimension(), "axis id identity"); - assertEquals("test3", instance3.getMessage(), "event message equality"); - assertEquals(payload, instance3.getPayLoad(), "payload equality"); - } - - @Test - public void testBasicEventHelperCoverage() { - assertNotNull(EventThreadHelper.getExecutorService()); - - assertEquals(Math.max(4, Runtime.getRuntime().availableProcessors()), EventThreadHelper.getMaxThreads()); - } -} diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventBenchmark.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventBenchmark.java deleted file mode 100644 index 3464e2503..000000000 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventBenchmark.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.fair_acc.dataset.event; - -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.infra.Blackhole; - -/** - * Benchmark to compare callback based event listeners to ring buffer based event sourcing. - * Different event topologies - * Different threading models: - * - all same thread - * - spawn new handlers in new threads - * - all handlers have threads polling events - * Measure throughput, latency - * - * @author Alexander Krimm - */ -@State(Scope.Benchmark) -public class EventBenchmark { - @Param({ "true", "false" }) - private boolean parallel; - - private TestEventSource es1; - private TestEventSource es2; - private TestEventSource es3; - - // private TestEventSource es1b; - // private TestEventSource es2b; - // private Blackhole[] payload = new Blackhole[1]; - // private UpdateEvent ev1; - // private UpdateEvent ev2; - - @Setup() - public void initialize() { - // 1on1r - es1 = new TestEventSource(); - es1.addListener(event -> { - Blackhole hole = (Blackhole) event.getPayLoad(); - Blackhole.consumeCPU(100); - hole.consume(event); - }); - // // 1on1r, preallocated - // es1b = new TestEventSource(); - // es1b.addListener(event -> { - // Blackhole hole = ((Blackhole[]) event.getPayLoad())[0]; - // Blackhole.consumeCPU(100); - // hole.consume(event); - // }); - // ev1 = new UpdateEvent(es1b, "test", payload); - // 1 to many - es2 = new TestEventSource(); - final int nListeners = 10; - for (int i = 0; i < nListeners; i++) { - final int index = i; - es2.addListener(event -> { - Blackhole hole = (Blackhole) event.getPayLoad(); - Blackhole.consumeCPU(100); - hole.consume(index); - }); - } - // // 1 to many, preallocated - // es2b = new TestEventSource(); - // for (int i = 0; i < nListeners ; i++) { - // final int index = i; - // es2b.addListener(event -> { - // Blackhole hole = ((Blackhole[]) event.getPayLoad())[0]; - // Blackhole.consumeCPU(100); - // hole.consume(index); - // }); - // ev2=new UpdateEvent(es2b,"test",payload); - // recursive - es3 = new TestEventSource(); - es3.addListener(event -> - - { - int val = (Integer) event.getPayLoad() + 1; - if (val < 10) { - Blackhole.consumeCPU(100); - es3.invokeListener(new UpdateEvent(es3, "test", val), parallel); - } - }); - } - - @Benchmark - @Warmup(iterations = 1) - @Fork(value = 2, warmups = 2) - public void oneToOne(Blackhole blackhole) { - es1.invokeListener(new UpdateEvent(es1, "test", blackhole), parallel); - } - - // @Benchmark - // @Warmup(iterations = 1) - // @Fork(value = 2, warmups = 2) - // public void oneToOnePreallocated(Blackhole blackhole) { - // payload[0] = blackhole; - // es1b.invokeListener(ev1, parallel); - // } - - @Benchmark - @Warmup(iterations = 1) - @Fork(value = 2, warmups = 2) - public void oneToMany(Blackhole blackhole) { - es2.invokeListener(new UpdateEvent(es2, "test", blackhole), parallel); - } - - // @Benchmark - // @Warmup(iterations = 1) - // @Fork(value = 2, warmups = 2) - // public void oneToManyPreallocated(Blackhole blackhole) { - // payload[0] = blackhole; - // es2b.invokeListener(ev2, parallel); - // } - - @Benchmark - @Warmup(iterations = 1) - @Fork(value = 2, warmups = 2) - public void recursive(Blackhole blackhole) { - es3.invokeListener(new UpdateEvent(es3, "test", 0), parallel); - } - - public static void main(String[] args) throws Exception { - org.openjdk.jmh.Main.main(args); - } -} diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventRateLimiterTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventRateLimiterTests.java deleted file mode 100644 index 13669ba13..000000000 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventRateLimiterTests.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.fair_acc.dataset.event; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.awaitility.Awaitility; -import org.junit.jupiter.api.Test; - -/** - * Tests the EventRateLimiter - * - * @author rstein - */ -public class EventRateLimiterTests { - private static final int MAX_UPDATE_PERIOD = 100; - - @Test - public void constructorTests() { - assertDoesNotThrow(() -> new EventRateLimiter(evt -> { /* do nothing */ }, MAX_UPDATE_PERIOD)); - assertDoesNotThrow(() -> new EventRateLimiter(evt -> { /* do nothing */ }, MAX_UPDATE_PERIOD, null)); - assertDoesNotThrow(() -> new EventRateLimiter(evt -> { /* do nothing */ }, MAX_UPDATE_PERIOD, EventRateLimiter.UpdateStrategy.INSTANTANEOUS_RATE)); - } - - @Test - public void rateLimiterTests() { - Timer timer = new Timer(); - final TestEventSource evtSource = new TestEventSource(); - final AtomicInteger updateCount1 = new AtomicInteger(); - final AtomicInteger updateCount2 = new AtomicInteger(); - final AtomicInteger updateCount3 = new AtomicInteger(); - final AtomicInteger updateCount4 = new AtomicInteger(); - - // create anonymous listener - evtSource.addListener(evt -> updateCount1.incrementAndGet()); - evtSource.addListener(new EventRateLimiter(evt -> updateCount2.incrementAndGet(), MAX_UPDATE_PERIOD, null)); - evtSource.addListener(new EventRateLimiter(evt -> updateCount3.incrementAndGet(), MAX_UPDATE_PERIOD, EventRateLimiter.UpdateStrategy.INSTANTANEOUS_RATE)); - - final EventRateLimiter rateLimitAvg = new EventRateLimiter(evt -> updateCount4.incrementAndGet(), MAX_UPDATE_PERIOD, EventRateLimiter.UpdateStrategy.AVERAGE_RATE); - evtSource.addListener(rateLimitAvg); - - assert rateLimitAvg.getRateEstimate() > 0; - - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - evtSource.invokeListener(); - } - }, 10, MAX_UPDATE_PERIOD / 10); - - Awaitility.await().atMost(2, TimeUnit.SECONDS).until(() -> updateCount1.get() >= 100); - timer.cancel(); - final double rateLimit = rateLimitAvg.getRateEstimate(); - assert updateCount1.get() >= 100; - assert updateCount2.get() >= 5; - assert updateCount2.get() <= 15; - assert updateCount3.get() >= 5; - assert updateCount3.get() <= 15; - assert updateCount4.get() >= 5; - assert updateCount4.get() <= 15; - assertAll("rate within [5,15] Hz limits", () -> assertTrue(rateLimit >= 5.0, "min limit"), () -> assertTrue(rateLimit <= 15.0, "max limit")); - } -} diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventSourceTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventSourceTests.java deleted file mode 100644 index e94c38dfa..000000000 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/EventSourceTests.java +++ /dev/null @@ -1,115 +0,0 @@ -package io.fair_acc.dataset.event; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.concurrent.atomic.AtomicInteger; - -import io.fair_acc.dataset.utils.AggregateException; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Tests the default implementation of the EventSource interface - * - * @author rstein - */ -class EventSourceTests { - private static final Logger LOGGER = LoggerFactory.getLogger(EventSourceTests.class); - - @Test - void basicTests() { - final TestEventSource evtSource = new TestEventSource(); - final AtomicInteger updateCount = new AtomicInteger(); - final Object payLoad = new Object(); - final UpdateEvent updateEvent = new UpdateEvent(evtSource, "evtMsg", payLoad); - final EventListener specialEventListener = event -> { - assertEquals(updateEvent, event, "event equivalency"); - assertEquals("evtMsg", event.getMessage(), "event msg equivalency"); - assertEquals(payLoad, event.getPayLoad(), "event payLoad equivalency"); - }; - - // empty invoke listener - evtSource.invokeListener(updateEvent, true); - - // add named listener - evtSource.addListener(specialEventListener); - assertEquals(1, evtSource.eventListener.size(), "event specialEventListener count"); - evtSource.addListener(specialEventListener); - assertEquals(1, evtSource.eventListener.size(), "event specialEventListener count"); - evtSource.invokeListener(updateEvent, true); - evtSource.removeListener(specialEventListener); - assertEquals(0, evtSource.eventListener.size(), "event specialEventListener count"); - - // add three anonymous listener - for (int i = 0; i < 3; i++) { - evtSource.addListener(evt -> updateCount.incrementAndGet()); - } - - assertEquals(3, evtSource.eventListener.size(), "event listener count"); - - evtSource.invokeListener(); - assertEquals(3, updateCount.get(), "invokeListener()"); - - evtSource.invokeListener(updateEvent, false); - assertEquals(6, updateCount.get(), "invokeListener()"); - - // check autonotification - assertTrue(evtSource.isAutoNotification(), "initial autonotification()"); - evtSource.autoNotification.set(false); - assertFalse(evtSource.isAutoNotification(), "false autonotification()"); - evtSource.invokeListener(updateEvent, false); - // N.B. notification count should not increase - assertEquals(6, updateCount.get(), "invokeListener()"); - evtSource.autoNotification.set(true); - - // clear event listener and add exception throwing listener - evtSource.eventListener.clear(); - evtSource.addListener(evt -> { - throw new IllegalStateException("bad bad exception #1"); - }); - evtSource.addListener(evt -> exceptionThrowingFunctionA()); - evtSource.addListener(evt -> exceptionThrowingFunctionB()); - assertThrows(AggregateException.class, evtSource::invokeListener); - try { - // check exception handling for parallel execution - evtSource.invokeListener(updateEvent, true); - } catch (AggregateException e) { - assertEquals(3, e.getThrowableList().size()); - } - - try { - // check exception handling for non-parallel execution - evtSource.invokeListener(updateEvent, false); - } catch (AggregateException e) { - assertEquals(3, e.getThrowableList().size()); - } - - try { - // check stack-trace printout - evtSource.invokeListener(updateEvent, false); - } catch (AggregateException e) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(os); - e.printStackTrace(ps); - } - - // corrupt event listener list (test failure case) - assertTrue(evtSource.isAutoNotification(), "initial autonotification()"); - evtSource.eventListener = null; - evtSource.invokeListener(updateEvent, false); - } - - protected void exceptionThrowingFunctionA() { - throw new IllegalStateException("bad bad exception #2"); - } - - protected void exceptionThrowingFunctionB() { - throw new IllegalStateException("bad bad exception #3"); - } -} diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java index be303cc5b..734a85a06 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/event/TestEventSource.java @@ -1,5 +1,7 @@ package io.fair_acc.dataset.event; +import io.fair_acc.dataset.events.BitState; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -10,16 +12,11 @@ * @author rstein */ public class TestEventSource implements EventSource { - protected final AtomicBoolean autoNotification = new AtomicBoolean(true); - protected List eventListener = Collections.synchronizedList(new ArrayList<>()); // N.B. final omitted for tests - @Override - public AtomicBoolean autoNotification() { - return autoNotification; - } + protected BitState state = BitState.initDirty(this); @Override - public List getBitState() { - return eventListener; + public BitState getBitState() { + return state; } } diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/locks/DataSetLockTest.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/locks/DataSetLockTest.java index e3eef0a91..ce002389b 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/locks/DataSetLockTest.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/locks/DataSetLockTest.java @@ -47,7 +47,6 @@ public void testDataSetLock() { assertEquals(0, myLockImpl.getReaderCount()); assertEquals(0, myLockImpl.getWriterCount()); - Assertions.assertTrue(dataSet.isAutoNotification()); myLock.writeLock().getDataCount(); if (LOGGER.isDebugEnabled()) { @@ -204,7 +203,6 @@ public void testDataSetLock() { Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> myLockImpl.getReaderCount() == 0); Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> myLockImpl.getWriterCount() == 0); - Awaitility.await().atMost(1, TimeUnit.SECONDS).until(dataSet::isAutoNotification); if (LOGGER.isDebugEnabled()) { LOGGER.atDebug().log("finished - testDataSetLock()"); @@ -260,7 +258,6 @@ public void testDataSetLockReadWrite() { // assert initial state assertEquals(0, myLockImpl.getReaderCount()); assertEquals(0, myLockImpl.getWriterCount()); - Assertions.assertTrue(dataSet.isAutoNotification()); myLock.readLock(); assertEquals(0, dataSet.getDataCount()); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java index 31ef672d2..8237bdd71 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetBuilderTests.java @@ -13,7 +13,7 @@ import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSetError; import io.fair_acc.dataset.GridDataSet; -import io.fair_acc.dataset.event.EventListener; +import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.locks.DataSetLock; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -225,12 +225,7 @@ void testMultiDimDataSet5() { */ private static class MinimalDataSet implements DataSet { private static final long serialVersionUID = 1L; - private final AtomicBoolean autoNotify = new AtomicBoolean(); - - @Override - public AtomicBoolean autoNotification() { - return autoNotify; - } + private BitState state = BitState.initDirty(this); @Override public double get(int dimIndex, int index) { @@ -309,8 +304,8 @@ public double getValue(final int dimIndex, final double... x) { } @Override - public List getBitState() { - return Collections.emptyList(); + public BitState getBitState() { + return state; } @Override diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java index 6e81f9c08..321e76603 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java @@ -10,6 +10,7 @@ import io.fair_acc.dataset.GridDataSet; import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.event.UpdateEvent; +import io.fair_acc.dataset.events.ChartBits; import org.awaitility.Awaitility; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -269,7 +270,7 @@ public void testSliceOptions() { nEvent.incrementAndGet(); } }); - testData.invokeListener(new UpdateEvent(testData, "testX"), true); + testData.fireInvalidated(ChartBits.DataSetData); Awaitility.await().atMost(1, TimeUnit.SECONDS).alias("DataSet3D event propagated").until(() -> nEvent.get() == 1); assertArrayEquals(testData.getGridValues(DataSet.DIM_X), sliceDataSetX.getValues(DataSet.DIM_X)); @@ -294,7 +295,7 @@ public void testInvalid2DInputDataSet() { GridDataSet testData = new DoubleGridDataSet("test", false, new double[][] { { 1, 2, 3 } }, new double[] { 6, 7, 8 }); DimReductionDataSet sliceDataSetX = new DimReductionDataSet(testData, DataSet.DIM_X, DimReductionDataSet.Option.SLICE); - testData.invokeListener(new UpdateEvent(testData, "testX"), true); + testData.fireInvalidated(ChartBits.DataSetData); assertEquals("input data set not 3 dim grid data set", sliceDataSetX.getWarningList().get(0)); } } diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java index aab0388a8..bc1de9ba3 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java @@ -180,12 +180,8 @@ void testSettersAndListeners() { double[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", false, new double[][] { { 0.1, 0.2 }, { 1.1, 2.2, 3.3 }, { -0.5, 0.5 } }, data); - final List events = Collections.synchronizedList(new ArrayList<>(20)); - dataset.addListener(events::add); dataset.set(3, new int[] { 1, 2, 1 }, 23.0); assertEquals(23.0, dataset.get(3, 1, 2, 1)); - assertSame(dataset, events.get(0).getSource()); - assertEquals("set x_3[1, 2, 1] = 23.0", events.get(0).getMessage()); assertEquals(1, dataset.getValue(3, 0.1, 1.1, -0.5)); assertEquals(6.5, dataset.getValue(3, 0.15, 2.2, 0)); assertEquals(Double.NaN, dataset.getValue(3, -1, 2.2, 0)); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java index b9b01e34e..8e0fbb909 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java @@ -12,6 +12,7 @@ import java.util.stream.Stream; import io.fair_acc.dataset.event.UpdatedMetaDataEvent; +import io.fair_acc.dataset.events.ChartBits; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -61,8 +62,8 @@ void testEquality(final Class clazz) throws AssertionError { void testVisibility(final Class clazz) throws AssertionError { DataSet dataSet = getDefaultTestDataSet(clazz, DEFAULT_DATASET_NAME1, DEFAULT_COUNT_MAX + 2); final AtomicBoolean visibilityChanged = new AtomicBoolean(false); - dataSet.addListener(event -> { - if (event instanceof UpdatedMetaDataEvent && event.getMessage().equals("changed visibility")) { + dataSet.getBitState().addInvalidateListener((src, bits) -> { + if (ChartBits.DataSetVisibility.isSet(bits)) { visibilityChanged.set(true); } }); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java index 5aeb667f0..9ff6c5574 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/testdata/spi/ErrorTestDataSetTest.java @@ -35,7 +35,6 @@ void testErrorTestDataSet() { Assertions.assertEquals(10, dsUnderTest.getIndex(DataSet.DIM_X, 22.0)); Assertions.assertEquals(5, dsUnderTest.getIndex(DataSet.DIM_X, 6.5)); assertNotNull(dsUnderTest.lock()); - assertNotNull(dsUnderTest.autoNotification()); assertNotNull(dsUnderTest.getBitState()); assertNull(dsUnderTest.getDataLabel(5)); assertNull(dsUnderTest.getStyle(5)); From 62b3c3298d5699026f33940a297ad01018235c4c Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 27 Jul 2023 14:11:53 +0200 Subject: [PATCH 35/81] added a default locale for decimal formatter tests --- .../src/test/java/io/fair_acc/dataset/FormatterTests.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/FormatterTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/FormatterTests.java index c9879d2e4..e5ddc7ad2 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/FormatterTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/FormatterTests.java @@ -8,12 +8,20 @@ import static io.fair_acc.dataset.DefaultNumberFormatter.SignConvention; import java.text.ParsePosition; +import java.util.Locale; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class FormatterTests { + + @BeforeAll + public static void setDefaultLocale() { + Locale.setDefault(Locale.US); + } + @Test void basicTests() { Formatter testFormatter = new Formatter<>() { From 28279e45dea49974061c24d32690d10cb7f6726d Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 27 Jul 2023 14:49:42 +0200 Subject: [PATCH 36/81] cleaned up listener code --- .../main/java/io/fair_acc/chartfx/Chart.java | 111 ++++-------------- .../java/io/fair_acc/chartfx/XYChart.java | 7 +- .../renderer/spi/MetaDataRenderer.java | 6 +- 3 files changed, 32 insertions(+), 92 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index b268d323f..f1ffd8789 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -13,12 +13,12 @@ import io.fair_acc.dataset.event.EventSource; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; +import io.fair_acc.dataset.events.StateListener; +import io.fair_acc.dataset.locks.DataSetLock; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.*; @@ -52,7 +52,6 @@ import io.fair_acc.chartfx.renderer.Renderer; import io.fair_acc.chartfx.renderer.spi.LabelledMarkerRenderer; import io.fair_acc.chartfx.ui.css.CssPropertyFactory; -import io.fair_acc.chartfx.ui.geometry.Corner; import io.fair_acc.chartfx.ui.geometry.Side; import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.dataset.DataSet; @@ -79,8 +78,7 @@ public abstract class Chart extends Region implements EventSource { protected final BitState state = BitState.initDirty(this, BitState.ALL_BITS) .addChangeListener(ChartBits.KnownMask, (src, bits) -> layoutHooks.registerOnce()) .addChangeListener(ChartBits.ChartLayout, (src, bits) -> super.requestLayout()); - protected final BitState dataSetState = BitState.initDirtyMultiThreaded(this, BitState.ALL_BITS) - .addChangeListener(FXUtils.runOnFxThread(state)); // forward to fx state on JavaFX thread + protected final StateListener dataSetListener = FXUtils.runOnFxThread(state); // datasets can be updated from different threads private static final Logger LOGGER = LoggerFactory.getLogger(Chart.class); private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); @@ -139,35 +137,6 @@ public abstract class Chart extends Region implements EventSource { protected final ToolBarFlowPane toolBar = new ToolBarFlowPane(this); protected final BooleanProperty toolBarPinned = new SimpleBooleanProperty(this, "toolBarPinned", false); - // ========================================= Optional elements for backwards compatibility - // Chart used to always add all elements, even if unused. For backwards compatibility - // we can instantiate items on demand, but many of them can probably be removed in the future. - // TODO: remove after refactoring - private final Map axesCornerMap = new ConcurrentHashMap<>(4); - private final Map axesMap = new ConcurrentHashMap<>(4); - private final Map titleLegendCornerMap = new ConcurrentHashMap<>(4); - private final Map titleLegendMap = new ConcurrentHashMap<>(4); - - private static StackPane getCornerPane(Corner corner, ChartPane parent, Map map) { - return map.computeIfAbsent(corner, key -> { - var node = new StackPane(); // NOPMD - default init - parent.addCorner(key, node); - return node; - }); - } - - private static Pane getSidePane(Side side, ChartPane parent, Map map, Consumer onCenter) { - return map.computeIfAbsent(side, key -> { - var node = key.isVertical() ? new ChartHBox() : new ChartVBox(); // NOPMD - default init - parent.addSide(key, node); - if (key == Side.CENTER_HOR || key == Side.CENTER_VER) { - onCenter.accept(node); - } - return node; - }); - } - // ========================================= - { // Build hierarchy // > menuPane (hidden toolbars that slide in from top/bottom) @@ -484,15 +453,6 @@ public ChartPane getAxesAndCanvasPane() { return axesAndCanvasPane; } - public final StackPane getAxesCornerPane(final Corner corner) { - return getCornerPane(corner, axesAndCanvasPane, axesCornerMap); - } - - @Deprecated // use ChartPane::setSide property - public final Pane getAxesPane(final Side side) { - return getSidePane(side, axesAndCanvasPane, axesMap, center -> center.setMouseTransparent(true)); - } - /** * @return the actual canvas the data is being drawn upon */ @@ -596,19 +556,10 @@ public final String getTitle() { return title.get(); } - public final StackPane getTitleLegendCornerPane(final Corner corner) { - return getCornerPane(corner, titleLegendPane, titleLegendCornerMap); - } - public final ChartPane getTitleLegendPane() { return titleLegendPane; } - @Deprecated // use ChartPane::setSide property - public final Pane getTitleLegendPane(final Side side) { - return getSidePane(side, titleLegendPane, titleLegendMap, Node::toBack); // don't draw over chart area - } - public final Side getTitleSide() { return titleSide.get(); } @@ -671,7 +622,7 @@ public void layoutChildren() { } // request re-layout of plugins - layoutPluginsChildren(); + layoutPluginsChildren(); // TODO: what if the datasets were not locked properly? // Make sure things will get redrawn fireInvalidated(ChartBits.ChartCanvas); @@ -685,7 +636,6 @@ protected void runPostLayout() { } redrawCanvas(); state.clear(); - dataSetState.clear(); forEachDataSet(ds -> ds.getBitState().clear()); // TODO: plugins etc., do locking ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); @@ -824,23 +774,21 @@ protected void animate(final Animation animation) { */ protected void axesChangedLocal(final ListChangeListener.Change change) { while (change.next()) { - var children = getAxesAndCanvasPane().getChildren(); for (Axis axis : change.getRemoved()) { // remove axis invalidation listener AssertUtils.notNull("to be removed axis is null", axis); - axis.getBitState().removeChangeListener(state); + axis.removeListener(state); removeAxisFromChildren(axis); // TODO: don't remove if it is contained in getAxes() } for (final Axis axis : change.getAddedSubList()) { // check if axis is associated with an existing renderer, // if yes -> throw an exception AssertUtils.notNull("to be added axis is null", axis); - axis.getBitState().addChangeListener(state); + axis.addListener(state); addAxisToChildren(axis); } } - - requestLayout(); + fireInvalidated(ChartBits.ChartLayout, ChartBits.ChartAxes); } private boolean addAxisToChildren(Axis axis) { @@ -865,25 +813,14 @@ private boolean removeAxisFromChildren(Axis axis) { return false; } - protected void dataSetInvalidated() { - // DataSet has notified and invalidate - if (DEBUG && LOGGER.isDebugEnabled()) { - LOGGER.debug("chart dataSetDataListener change notified"); - } - FXUtils.assertJavaFxThread(); - // updateAxisRange(); - // TODO: check why the following does not always forces a layoutChildren - requestLayout(); - } - protected void datasetsChanged(final ListChangeListener.Change change) { FXUtils.assertJavaFxThread(); while (change.next()) { for (final DataSet set : change.getRemoved()) { - set.removeListener(dataSetState); + set.removeListener(dataSetListener); } for (final DataSet set : change.getAddedSubList()) { - set.addListener(dataSetState); + set.addListener(dataSetListener); } } fireInvalidated(ChartBits.ChartLayout, ChartBits.ChartDataSets, ChartBits.ChartLegend); @@ -955,25 +892,24 @@ protected void registerShowingListener() { protected void rendererChanged(final ListChangeListener.Change change) { FXUtils.assertJavaFxThread(); while (change.next()) { + // TODO: how to work with renderer axes that are not in the SceneGraph? The length would never get set. + // handle added renderer - change.getAddedSubList().forEach(renderer -> { - // update legend and recalculateLayout on datasetChange + for (Renderer renderer : change.getAddedSubList()) { + for (DataSet dataset : renderer.getDatasets()) { + dataset.addListener(dataSetListener); + } renderer.getDatasets().addListener(datasetChangeListener); - // add listeners to all datasets already in the renderer - renderer.getDatasets().forEach(set -> set.addListener(dataSetState)); - - // TODO: how should it handle renderer axes? this can add automatically-generated axes that aren't wanted -// renderer.getAxes().addListener(axesChangeListenerLocal); -// renderer.getAxes().forEach(this::addAxisToChildren); - }); + } // handle removed renderer - change.getRemoved().forEach(renderer -> { + for (Renderer renderer : change.getRemoved()) { + for (DataSet dataset : renderer.getDatasets()) { + dataset.removeListener(dataSetListener); + } renderer.getDatasets().removeListener(datasetChangeListener); - renderer.getDatasets().forEach(set -> set.removeListener(dataSetState)); -// renderer.getAxes().removeListener(axesChangeListenerLocal); -// renderer.getAxes().forEach(this::removeAxisFromChildren); - }); + } + } // reset change to allow derived classes to add additional listeners to renderer changes change.reset(); @@ -1000,7 +936,8 @@ protected void updateLegend(final List dataSets, final List r } protected void updatePluginsArea() { - pluginsArea.getChildren().setAll(plugins.stream().map(pluginGroups::get).collect(Collectors.toList())); + var pluginChildren = plugins.stream().map(pluginGroups::get).collect(Collectors.toList()); + pluginsArea.getChildren().setAll(pluginChildren); requestLayout(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 7b4c81395..02eeacd2a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -120,7 +120,12 @@ public ObservableList 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; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MetaDataRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MetaDataRenderer.java index bd91c4dd9..194c3d5a4 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MetaDataRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MetaDataRenderer.java @@ -337,9 +337,7 @@ protected void updateInfoBoxLocation() { // remove old pane borderPane.getChildren().remove(messageBox); - for (final Side s : Side.values()) { - chart.getTitleLegendPane(s).getChildren().remove(messageBox); - } + chart.getTitleLegendPane().getChildren().remove(messageBox); if (isDrawOnCanvas()) { switch (side) { @@ -366,7 +364,7 @@ protected void updateInfoBoxLocation() { break; } } else { - chart.getTitleLegendPane(side).getChildren().add(messageBox); + chart.getTitleLegendPane().addSide(side, messageBox); } // chart.requestLayout(); } From b43199348c47010fe72de92daf45588d34318d8e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 27 Jul 2023 15:26:02 +0200 Subject: [PATCH 37/81] added global dataset locking between pre and post layout phase --- .../main/java/io/fair_acc/chartfx/Chart.java | 17 ++++++++-- .../java/io/fair_acc/chartfx/XYChart.java | 34 +++++++------------ .../fair_acc/chartfx/plugins/TableViewer.java | 7 ++-- .../fair_acc/chartfx/ui/utils/LayoutHook.java | 10 ++++++ 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index f1ffd8789..4f044ef2a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -601,6 +601,8 @@ public boolean isToolBarPinned() { } protected void runPreLayout() { + forEachDataSet(ds -> lockedDataSets.add(ds.lock().readLock())); + if (state.isDirty(ChartBits.ChartLegend, ChartBits.ChartDataSets, ChartBits.ChartRenderers)) { updateLegend(getDatasets(), getRenderers()); } @@ -610,6 +612,8 @@ protected void runPreLayout() { ProcessingProfiler.getTimeDiff(start, "updateAxisRange()"); } + private List lockedDataSets = new ArrayList<>(); + @Override public void layoutChildren() { // Size all nodes to full size. Account for margin and border insets. @@ -622,7 +626,11 @@ public void layoutChildren() { } // request re-layout of plugins - layoutPluginsChildren(); // TODO: what if the datasets were not locked properly? + // TODO: + // * what if the datasets were not locked beforehand? + // * layoutChildren only gets called on actual changes, so could we guarantee execution if we skip it? + // * maybe they need to call their own readLock? But that would also result in outdated axes. Is this even needed? + layoutPluginsChildren(); // Make sure things will get redrawn fireInvalidated(ChartBits.ChartCanvas); @@ -636,7 +644,12 @@ protected void runPostLayout() { } redrawCanvas(); state.clear(); - forEachDataSet(ds -> ds.getBitState().clear()); + for (var ds : lockedDataSets) { + ds.getBitState().clear(); // technically a 'write' + ds.lock().readUnLock(); + } + lockedDataSets.clear(); + // TODO: plugins etc., do locking ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 02eeacd2a..fa45ab975 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -10,6 +10,7 @@ import java.util.stream.Collectors; import io.fair_acc.chartfx.axes.spi.AxisRange; +import io.fair_acc.dataset.events.ChartBits; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; @@ -258,26 +259,17 @@ public void updateAxisRange() { return; } - // lock datasets to prevent writes while updating the axes - ObservableList 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 lockQueue = new ArrayDeque<>(dataSets); - recursiveLockGuard(lockQueue, () -> getAxes().forEach(chartAxis -> { - final List 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 queue, final Runnable runnable) { // NOPMD - if (queue.isEmpty()) { - runnable.run(); - } else { - queue.pop().lock().readLockGuard(() -> recursiveLockGuard(queue, runnable)); - } } /** @@ -416,7 +408,7 @@ protected static void updateNumericAxis(final Axis axis, final List dat // 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); @@ -431,7 +423,7 @@ protected static void updateNumericAxis(final Axis axis, final List dat dsRange.add(dataset.getAxisDescription(nDim).getMin()); dsRange.add(dataset.getAxisDescription(nDim).getMax()); } - })); + }); // Update the auto range final boolean changed; diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java index 45f38957b..b0d93acf5 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java @@ -313,9 +313,10 @@ public void runPreLayout() { // Update the datasets int i = 0, nRowsNew = 0; for (DataSet ds : columnsUpdated) { - var col = (DataSetTableColumns) cols.get(i++); - col.update(ds); - nRowsNew = Math.max(nRowsNew, ds.getDataCount()); + if (cols instanceof DataSetTableColumns) { + ((DataSetTableColumns) cols.get(i++)).update(ds); + nRowsNew = Math.max(nRowsNew, ds.getDataCount()); + } } if (nRows != nRowsNew) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java index 08e210f0a..f78185de6 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java @@ -45,6 +45,13 @@ private LayoutHook(Node node, Runnable preLayoutAction, Runnable postLayoutActio this.postLayoutAction = AssertUtils.notNull("preLayoutAction", postLayoutAction); } + /** + * @return true if the pre layout hook was executed this cycle. Meant to be called during the layout phase. + */ + public boolean hasRunPreLayout() { + return hasRunPreLayout; + } + public LayoutHook registerOnce() { // Scene has changed -> remove the old one first if (registeredScene != null && registeredScene != node.getScene()) { @@ -66,6 +73,7 @@ private void runPreLayoutAndAdd() { // We don't want to be in a position where the post layout listener // runs by itself, so we don't register until we made sure that the // pre-layout action ran before. + hasRunPreLayout = true; preLayoutAction.run(); registeredScene.addPostLayoutPulseListener(postLayoutAndRemove); } @@ -79,11 +87,13 @@ private void unregister() { registeredScene.removePreLayoutPulseListener(preLayoutAndAdd); registeredScene.removePostLayoutPulseListener(postLayoutAndRemove); registeredScene = null; + hasRunPreLayout = false; } final Node node; final Runnable preLayoutAction; final Runnable postLayoutAction; + boolean hasRunPreLayout = false; final Runnable preLayoutAndAdd = this::runPreLayoutAndAdd; final Runnable postLayoutAndRemove = this::runPostLayoutAndRemove; From c3ff7b31f066f16497b52b46612c1d7d554c38b9 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 27 Jul 2023 15:54:56 +0200 Subject: [PATCH 38/81] removed unnecessary locks from renderers --- .../chartfx/axes/spi/CategoryAxis.java | 27 +- .../renderer/spi/CachedDataPoints.java | 304 ++++++++---------- .../renderer/spi/ContourDataSetRenderer.java | 28 +- .../renderer/spi/ErrorDataSetRenderer.java | 11 +- .../renderer/spi/HistogramRenderer.java | 102 +++--- .../renderer/spi/LabelledMarkerRenderer.java | 42 ++- .../renderer/spi/MountainRangeRenderer.java | 42 ++- .../renderer/spi/ReducingLineRenderer.java | 130 ++++---- .../spi/financial/CandleStickRenderer.java | 235 +++++++------- .../spi/financial/FootprintRenderer.java | 153 +++++---- .../spi/financial/HighLowRenderer.java | 217 +++++++------ 11 files changed, 617 insertions(+), 674 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java index d0a555fbf..96e65bef7 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java @@ -168,23 +168,20 @@ public boolean updateCategories(final DataSet dataSet) { } final List newCategoryList = new ArrayList<>(); - final boolean result = dataSet.lock().readLockGuard(() -> { - boolean zeroDataLabels = true; - for (int i = 0; i < dataSet.getDataCount(); i++) { - final String dataLabel = dataSet.getDataLabel(i); - String sanitizedLabel; - if (dataLabel == null) { - sanitizedLabel = "unknown category"; - } else { - sanitizedLabel = dataLabel; - zeroDataLabels = false; - } - newCategoryList.add(sanitizedLabel); + boolean zeroDataLabels = true; + for (int i = 0; i < dataSet.getDataCount(); i++) { + final String dataLabel = dataSet.getDataLabel(i); + String sanitizedLabel; + if (dataLabel == null) { + sanitizedLabel = "unknown category"; + } else { + sanitizedLabel = dataLabel; + zeroDataLabels = false; } - return zeroDataLabels; - }); + newCategoryList.add(sanitizedLabel); + } - if (!result) { + if (!zeroDataLabels) { setCategories(newCategoryList); forceAxisCategories = false; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/CachedDataPoints.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/CachedDataPoints.java index 5b5fe68db..209143b01 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/CachedDataPoints.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/CachedDataPoints.java @@ -118,58 +118,52 @@ protected void computeBoundaryVariables(final Axis xAxis, final Axis yAxis) { private void computeErrorStyles(final DataSet dataSet, final int min, final int max) { // no error attached - dataSet.lock().readLockGuardOptimistic(() -> { - for (int index = min; index < max; index++) { - styles[index] = dataSet.getStyle(index); - } - }); + for (int index = min; index < max; index++) { + styles[index] = dataSet.getStyle(index); + } } private void computeFullPolar(final Axis yAxis, final DataSetError dataSet, final int min, final int max) { - dataSet.lock().readLockGuardOptimistic(() -> { - for (int index = min; index < max; index++) { - final double x = dataSet.get(DIM_X, index); - final double y = dataSet.get(DIM_Y, index); - // check if error should be surrounded by Math.abs(..) - // to ensure that they are always positive - final double phi = x * DEG_TO_RAD; - final double r = maxRadius * Math.abs(1 - (yAxis.getDisplayPosition(y) / yRange)); - xValues[index] = xZero + (r * Math.cos(phi)); - yValues[index] = yZero + (r * Math.sin(phi)); - - // ignore errors (for now) -> TODO: add proper transformation - errorXNeg[index] = 0.0; - errorXPos[index] = 0.0; - errorYNeg[index] = 0.0; - errorYPos[index] = 0.0; - - if (!Double.isFinite(yValues[index])) { - yValues[index] = yZero; - } - styles[index] = dataSet.getStyle(index); + for (int index = min; index < max; index++) { + final double x = dataSet.get(DIM_X, index); + final double y = dataSet.get(DIM_Y, index); + // check if error should be surrounded by Math.abs(..) + // to ensure that they are always positive + final double phi = x * DEG_TO_RAD; + final double r = maxRadius * Math.abs(1 - (yAxis.getDisplayPosition(y) / yRange)); + xValues[index] = xZero + (r * Math.cos(phi)); + yValues[index] = yZero + (r * Math.sin(phi)); + + // ignore errors (for now) -> TODO: add proper transformation + errorXNeg[index] = 0.0; + errorXPos[index] = 0.0; + errorYNeg[index] = 0.0; + errorYPos[index] = 0.0; + + if (!Double.isFinite(yValues[index])) { + yValues[index] = yZero; } - }); + styles[index] = dataSet.getStyle(index); + } } private void computeNoErrorPolar(final Axis yAxis, final DataSet dataSet, final int min, final int max) { // experimental transform euclidean to polar coordinates - dataSet.lock().readLockGuardOptimistic(() -> { - for (int index = min; index < max; index++) { - final double x = dataSet.get(DIM_X, index); - final double y = dataSet.get(DIM_Y, index); - // check if error should be surrounded by Math.abs(..) - // to ensure that they are always positive - final double phi = x * DEG_TO_RAD; - final double r = maxRadius * Math.abs(1 - (yAxis.getDisplayPosition(y) / yRange)); - xValues[index] = xZero + (r * Math.cos(phi)); - yValues[index] = yZero + (r * Math.sin(phi)); - - if (!Double.isFinite(yValues[index])) { - yValues[index] = yZero; - } - styles[index] = dataSet.getStyle(index); + for (int index = min; index < max; index++) { + final double x = dataSet.get(DIM_X, index); + final double y = dataSet.get(DIM_Y, index); + // check if error should be surrounded by Math.abs(..) + // to ensure that they are always positive + final double phi = x * DEG_TO_RAD; + final double r = maxRadius * Math.abs(1 - (yAxis.getDisplayPosition(y) / yRange)); + xValues[index] = xZero + (r * Math.cos(phi)); + yValues[index] = yZero + (r * Math.sin(phi)); + + if (!Double.isFinite(yValues[index])) { + yValues[index] = yZero; } - }); + styles[index] = dataSet.getStyle(index); + } } protected void computeScreenCoordinates(final Axis xAxis, final Axis yAxis, final DataSet dataSet, @@ -274,167 +268,153 @@ private void computeScreenCoordinatesPolar(final Axis yAxis, final DataSet dataS private void computeWithError(final Axis yAxis, final DataSet dataSet, final int dimIndex, final int min, final int max) { if (dataSet instanceof DataSetError) { - dataSet.lock().readLockGuardOptimistic(() -> { - final double[] values = dimIndex == DIM_X ? xValues : yValues; - final double[] valuesEN = dimIndex == DIM_X ? errorXNeg : errorYNeg; - final double[] valuesEP = dimIndex == DIM_X ? errorXPos : errorYPos; - final double minValue = dimIndex == DIM_X ? xMin : yMin; - final DataSetError ds = (DataSetError) dataSet; - for (int index = min; index < max; index++) { - final double value = dataSet.get(dimIndex, index); - - values[index] = yAxis.getDisplayPosition(value); - - if (!Double.isNaN(values[index])) { - valuesEN[index] = yAxis.getDisplayPosition(value - ds.getErrorNegative(dimIndex, index)); - valuesEP[index] = yAxis.getDisplayPosition(value + ds.getErrorPositive(dimIndex, index)); - continue; - } - values[index] = minValue; - valuesEN[index] = minValue; - valuesEP[index] = minValue; - } - }); - return; - } - - // default dataset - dataSet.lock().readLockGuardOptimistic(() -> { final double[] values = dimIndex == DIM_X ? xValues : yValues; final double[] valuesEN = dimIndex == DIM_X ? errorXNeg : errorYNeg; final double[] valuesEP = dimIndex == DIM_X ? errorXPos : errorYPos; final double minValue = dimIndex == DIM_X ? xMin : yMin; - + final DataSetError ds = (DataSetError) dataSet; for (int index = min; index < max; index++) { - values[index] = yAxis.getDisplayPosition(dataSet.get(dimIndex, index)); - if (Double.isFinite(values[index])) { - valuesEN[index] = values[index]; - valuesEP[index] = values[index]; - } else { - values[index] = minValue; - valuesEN[index] = minValue; - valuesEP[index] = minValue; - } - } - }); - } + final double value = dataSet.get(dimIndex, index); - private void computeWithErrorAllowingNaNs(final Axis yAxis, final DataSet dataSet, final int dimIndex, - final int min, final int max) { - if (dataSet instanceof DataSetError) { - dataSet.lock().readLockGuardOptimistic(() -> { - final double[] values = dimIndex == DIM_X ? xValues : yValues; - final double[] valuesEN = dimIndex == DIM_X ? errorXNeg : errorYNeg; - final double[] valuesEP = dimIndex == DIM_X ? errorXPos : errorYPos; - final DataSetError ds = (DataSetError) dataSet; - for (int index = min; index < max; index++) { - final double value = dataSet.get(dimIndex, index); - - if (!Double.isFinite(value)) { - values[index] = Double.NaN; - valuesEN[index] = Double.NaN; - valuesEP[index] = Double.NaN; - continue; - } - - values[index] = yAxis.getDisplayPosition(value); + values[index] = yAxis.getDisplayPosition(value); + + if (!Double.isNaN(values[index])) { valuesEN[index] = yAxis.getDisplayPosition(value - ds.getErrorNegative(dimIndex, index)); valuesEP[index] = yAxis.getDisplayPosition(value + ds.getErrorPositive(dimIndex, index)); + continue; } - }); + values[index] = minValue; + valuesEN[index] = minValue; + valuesEP[index] = minValue; + } return; } // default dataset - dataSet.lock().readLockGuardOptimistic(() -> { + final double[] values = dimIndex == DIM_X ? xValues : yValues; + final double[] valuesEN = dimIndex == DIM_X ? errorXNeg : errorYNeg; + final double[] valuesEP = dimIndex == DIM_X ? errorXPos : errorYPos; + final double minValue = dimIndex == DIM_X ? xMin : yMin; + + for (int index = min; index < max; index++) { + values[index] = yAxis.getDisplayPosition(dataSet.get(dimIndex, index)); + if (Double.isFinite(values[index])) { + valuesEN[index] = values[index]; + valuesEP[index] = values[index]; + } else { + values[index] = minValue; + valuesEN[index] = minValue; + valuesEP[index] = minValue; + } + } + } + + private void computeWithErrorAllowingNaNs(final Axis yAxis, final DataSet dataSet, final int dimIndex, + final int min, final int max) { + if (dataSet instanceof DataSetError) { final double[] values = dimIndex == DIM_X ? xValues : yValues; final double[] valuesEN = dimIndex == DIM_X ? errorXNeg : errorYNeg; final double[] valuesEP = dimIndex == DIM_X ? errorXPos : errorYPos; - + final DataSetError ds = (DataSetError) dataSet; for (int index = min; index < max; index++) { - values[index] = yAxis.getDisplayPosition(dataSet.get(dimIndex, index)); + final double value = dataSet.get(dimIndex, index); - if (Double.isFinite(values[index])) { - valuesEN[index] = values[index]; - valuesEP[index] = values[index]; - } else { + if (!Double.isFinite(value)) { values[index] = Double.NaN; valuesEN[index] = Double.NaN; valuesEP[index] = Double.NaN; + continue; } + + values[index] = yAxis.getDisplayPosition(value); + valuesEN[index] = yAxis.getDisplayPosition(value - ds.getErrorNegative(dimIndex, index)); + valuesEP[index] = yAxis.getDisplayPosition(value + ds.getErrorPositive(dimIndex, index)); } - }); + return; + } + + // default dataset + final double[] values = dimIndex == DIM_X ? xValues : yValues; + final double[] valuesEN = dimIndex == DIM_X ? errorXNeg : errorYNeg; + final double[] valuesEP = dimIndex == DIM_X ? errorXPos : errorYPos; + + for (int index = min; index < max; index++) { + values[index] = yAxis.getDisplayPosition(dataSet.get(dimIndex, index)); + + if (Double.isFinite(values[index])) { + valuesEN[index] = values[index]; + valuesEP[index] = values[index]; + } else { + values[index] = Double.NaN; + valuesEN[index] = Double.NaN; + valuesEP[index] = Double.NaN; + } + } } private void computeWithNoError(final Axis axis, final DataSet dataSet, final int dimIndex, final int min, final int max) { // no error attached - dataSet.lock().readLockGuardOptimistic(() -> { - final double[] values = dimIndex == DIM_X ? xValues : yValues; - final double minValue = dimIndex == DIM_X ? xMin : yMin; - for (int index = min; index < max; index++) { - final double value = dataSet.get(dimIndex, index); + final double[] values = dimIndex == DIM_X ? xValues : yValues; + final double minValue = dimIndex == DIM_X ? xMin : yMin; + for (int index = min; index < max; index++) { + final double value = dataSet.get(dimIndex, index); - values[index] = axis.getDisplayPosition(value); + values[index] = axis.getDisplayPosition(value); - if (Double.isNaN(values[index])) { - yValues[index] = minValue; - } + if (Double.isNaN(values[index])) { + yValues[index] = minValue; } + } - if ((dimIndex == DIM_Y) && (rendererErrorStyle != ErrorStyle.NONE)) { - System.arraycopy(values, min, errorYNeg, min, max - min); - System.arraycopy(values, min, errorYPos, min, max - min); - } - }); + if ((dimIndex == DIM_Y) && (rendererErrorStyle != ErrorStyle.NONE)) { + System.arraycopy(values, min, errorYNeg, min, max - min); + System.arraycopy(values, min, errorYPos, min, max - min); + } } private void computeWithNoErrorAllowingNaNs(final Axis axis, final DataSet dataSet, final int dimIndex, final int min, final int max) { // no error attached - dataSet.lock().readLockGuardOptimistic(() -> { - final double[] values = dimIndex == DIM_X ? xValues : yValues; - for (int index = min; index < max; index++) { - final double value = dataSet.get(dimIndex, index); + final double[] values = dimIndex == DIM_X ? xValues : yValues; + for (int index = min; index < max; index++) { + final double value = dataSet.get(dimIndex, index); - if (Double.isFinite(value)) { - values[index] = axis.getDisplayPosition(value); - } else { - values[index] = Double.NaN; - } + if (Double.isFinite(value)) { + values[index] = axis.getDisplayPosition(value); + } else { + values[index] = Double.NaN; } + } - if ((dimIndex == DIM_Y) && (rendererErrorStyle != ErrorStyle.NONE)) { - System.arraycopy(values, min, errorYNeg, min, max - min); - System.arraycopy(values, min, errorYPos, min, max - min); - } - }); + if ((dimIndex == DIM_Y) && (rendererErrorStyle != ErrorStyle.NONE)) { + System.arraycopy(values, min, errorYNeg, min, max - min); + System.arraycopy(values, min, errorYPos, min, max - min); + } } private void computeYonlyPolar(final Axis yAxis, final DataSet dataSet, final int min, final int max) { - dataSet.lock().readLockGuardOptimistic(() -> { - for (int index = min; index < max; index++) { - final double x = dataSet.get(DIM_X, index); - final double y = dataSet.get(DIM_Y, index); - // check if error should be surrounded by Math.abs(..) - // to ensure that they are always positive - final double phi = x * DEG_TO_RAD; - final double r = maxRadius * Math.abs(1 - (yAxis.getDisplayPosition(y) / yRange)); - xValues[index] = xZero + (r * Math.cos(phi)); - yValues[index] = yZero + (r * Math.sin(phi)); - - // ignore errors (for now) -> TODO: add proper transformation - errorXNeg[index] = 0.0; - errorXPos[index] = 0.0; - errorYNeg[index] = 0.0; - errorYPos[index] = 0.0; - - if (!Double.isFinite(yValues[index])) { - yValues[index] = yZero; - } - styles[index] = dataSet.getStyle(index); + for (int index = min; index < max; index++) { + final double x = dataSet.get(DIM_X, index); + final double y = dataSet.get(DIM_Y, index); + // check if error should be surrounded by Math.abs(..) + // to ensure that they are always positive + final double phi = x * DEG_TO_RAD; + final double r = maxRadius * Math.abs(1 - (yAxis.getDisplayPosition(y) / yRange)); + xValues[index] = xZero + (r * Math.cos(phi)); + yValues[index] = yZero + (r * Math.sin(phi)); + + // ignore errors (for now) -> TODO: add proper transformation + errorXNeg[index] = 0.0; + errorXPos[index] = 0.0; + errorYNeg[index] = 0.0; + errorYPos[index] = 0.0; + + if (!Double.isFinite(yValues[index])) { + yValues[index] = yZero; } - }); + styles[index] = dataSet.getStyle(index); + } } /** @@ -544,7 +524,7 @@ protected void setErrorType(final DataSet dataSet, final ErrorStyle errorStyle) final DataSetError ds = (DataSetError) dataSet; for (int dimIndex = 0; dimIndex < ds.getDimension(); dimIndex++) { final int tmpIndex = dimIndex; - errorType[dimIndex] = dataSet.lock().readLockGuardOptimistic(() -> ds.getErrorType(tmpIndex)); + errorType[dimIndex] = ds.getErrorType(tmpIndex); } } else if (errorStyle == ErrorStyle.NONE) { // special case where users does not want error bars @@ -568,7 +548,7 @@ protected void setErrorType(final DataSet dataSet, final ErrorStyle errorStyle) } protected void setStyleVariable(final DataSet dataSet, final int dsIndex) { - dataSet.lock().readLockGuardOptimistic(() -> defaultStyle = dataSet.getStyle()); + defaultStyle = dataSet.getStyle(); final Integer layoutOffset = StyleParser.getIntegerPropertyValue(defaultStyle, XYChartCss.DATASET_LAYOUT_OFFSET); final Integer dsIndexLocal = StyleParser.getIntegerPropertyValue(defaultStyle, XYChartCss.DATASET_INDEX); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java index 341fbe4bf..4f0a227b6 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java @@ -478,29 +478,19 @@ public List render(final GraphicsContext gc, final Chart chart, final i List drawnDataSet = new ArrayList<>(localDataSetList.size()); for (int dataSetIndex = localDataSetList.size() - 1; dataSetIndex >= 0; dataSetIndex--) { final DataSet dataSet = localDataSetList.get(dataSetIndex); - if (!dataSet.isVisible() || !(dataSet instanceof GridDataSet) || dataSet.getDimension() <= 2) { + if (!dataSet.isVisible() || !(dataSet instanceof GridDataSet) || dataSet.getDimension() <= 2 || dataSet.getDataCount() == 0) { continue; // DataSet not applicable to ContourChartRenderer } - final boolean result = dataSet.lock().readLockGuard(() -> { - long stop = ProcessingProfiler.getTimeDiff(mid, "dataSet.lock()"); + long stop = ProcessingProfiler.getTimeDiff(mid, "dataSet.lock()"); + localCache = new ContourDataSetCache(xyChart, this, dataSet); // NOPMD + ProcessingProfiler.getTimeDiff(stop, "updateCachedVariables"); - if (dataSet.getDataCount() == 0) { - return false; - } - - localCache = new ContourDataSetCache(xyChart, this, dataSet); // NOPMD - ProcessingProfiler.getTimeDiff(stop, "updateCachedVariables"); - return true; - }); - - if (result) { - layoutZAxis(getZAxis()); - // data reduction algorithm here - paintCanvas(gc); - drawnDataSet.add(dataSet); - localCache.releaseCachedVariables(); - } + layoutZAxis(getZAxis()); + // data reduction algorithm here + paintCanvas(gc); + drawnDataSet.add(dataSet); + localCache.releaseCachedVariables(); ProcessingProfiler.getTimeDiff(mid, "finished drawing"); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRenderer.java index 72852290c..8cc57ec3b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRenderer.java @@ -2,6 +2,7 @@ import java.security.InvalidParameterException; import java.util.*; +import java.util.function.Supplier; import javafx.collections.ObservableList; import javafx.geometry.Orientation; @@ -179,17 +180,17 @@ public List render(final GraphicsContext gc, final Chart chart, final i if (dataSetIndex == 0) { if (getFirstAxis(Orientation.HORIZONTAL) instanceof CategoryAxis) { final CategoryAxis axis = (CategoryAxis) getFirstAxis(Orientation.HORIZONTAL); - dataSet.lock().readLockGuard(() -> axis.updateCategories(dataSet)); + axis.updateCategories(dataSet); } if (getFirstAxis(Orientation.VERTICAL) instanceof CategoryAxis) { final CategoryAxis axis = (CategoryAxis) getFirstAxis(Orientation.VERTICAL); - dataSet.lock().readLockGuard(() -> axis.updateCategories(dataSet)); + axis.updateCategories(dataSet); } } // check for potentially reduced data range we are supposed to plot - final Optional cachedPoints = dataSet.lock().readLockGuard(() -> { + Supplier> cachedPoints = () -> { int indexMin; int indexMax; /* indexMax is excluded in the drawing */ if (isAssumeSortedData()) { @@ -230,9 +231,9 @@ public List render(final GraphicsContext gc, final Chart chart, final i stopStamp = ProcessingProfiler.getTimeDiff(stopStamp, "computeScreenCoordinates()"); } return Optional.of(localCachedPoints); - }); + }; - cachedPoints.ifPresent(value -> { + cachedPoints.get().ifPresent(value -> { // invoke data reduction algorithm value.reduce(rendererDataReducerProperty().get(), isReducePoints(), getMinRequiredReductionSize()); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/HistogramRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/HistogramRenderer.java index 52c9f33f5..b4497be4e 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/HistogramRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/HistogramRenderer.java @@ -133,31 +133,28 @@ public List render(final GraphicsContext gc, final Chart chart, final i // verify that allDataSets are sorted for (int i = 0; i < localDataSetList.size(); i++) { DataSet dataSet = localDataSetList.get(i); - final int index = i; - dataSet.lock().readLockGuardOptimistic(() -> { - if (!(dataSet instanceof Histogram) && isAutoSorting() && (!isDataSetSorted(dataSet, DIM_X) && !isDataSetSorted(dataSet, DIM_Y))) { - // replace DataSet with sorted variety - // do not need to do this for Histograms as they are always sorted by design - LimitedIndexedTreeDataSet newDataSet = new LimitedIndexedTreeDataSet(dataSet.getName(), Integer.MAX_VALUE); - newDataSet.setVisible(dataSet.isVisible()); - newDataSet.set(dataSet); - localDataSetList.set(index, newDataSet); - } + if (!(dataSet instanceof Histogram) && isAutoSorting() && (!isDataSetSorted(dataSet, DIM_X) && !isDataSetSorted(dataSet, DIM_Y))) { + // replace DataSet with sorted variety + // do not need to do this for Histograms as they are always sorted by design + LimitedIndexedTreeDataSet newDataSet = new LimitedIndexedTreeDataSet(dataSet.getName(), Integer.MAX_VALUE); + newDataSet.setVisible(dataSet.isVisible()); + newDataSet.set(dataSet); + localDataSetList.set(i, newDataSet); + } - if (index != 0) { - return; - } - // update categories for the first (index == '0') indexed data set - if (xAxis instanceof CategoryAxis) { - final CategoryAxis axis = (CategoryAxis) xAxis; - axis.updateCategories(dataSet); - } + if (i != 0) { + continue; + } + // update categories for the first (index == '0') indexed data set + if (xAxis instanceof CategoryAxis) { + final CategoryAxis axis = (CategoryAxis) xAxis; + axis.updateCategories(dataSet); + } - if (yAxis instanceof CategoryAxis) { - final CategoryAxis axis = (CategoryAxis) yAxis; - axis.updateCategories(dataSet); - } - }); + if (yAxis instanceof CategoryAxis) { + final CategoryAxis axis = (CategoryAxis) yAxis; + axis.updateCategories(dataSet); + } } drawHistograms(gc, localDataSetList, xAxis, yAxis, dataSetOffset); @@ -287,42 +284,29 @@ protected void drawBars(final GraphicsContext gc, final List dataSets, } protected void drawHistograms(final GraphicsContext gc, final List dataSets, final Axis xAxis, final Axis yAxis, final int dataSetOffset) { - final ArrayDeque lockQueue = new ArrayDeque<>(dataSets.size()); - try { - dataSets.forEach(ds -> { - lockQueue.push(ds); - ds.lock().readLock(); - }); - - switch (getPolyLineStyle()) { - case NONE: - return; - case AREA: - drawPolyLineLine(gc, dataSets, xAxis, yAxis, dataSetOffset, true); - break; - case ZERO_ORDER_HOLDER: - case STAIR_CASE: - drawPolyLineStairCase(gc, dataSets, xAxis, yAxis, dataSetOffset, false); - break; - case HISTOGRAM: - drawPolyLineHistogram(gc, dataSets, xAxis, yAxis, dataSetOffset, false); - break; - case HISTOGRAM_FILLED: - drawPolyLineHistogram(gc, dataSets, xAxis, yAxis, dataSetOffset, true); - break; - case BEZIER_CURVE: - drawPolyLineHistogramBezier(gc, dataSets, xAxis, yAxis, dataSetOffset, true); - break; - case NORMAL: - default: - drawPolyLineLine(gc, dataSets, xAxis, yAxis, dataSetOffset, false); - break; - } - } finally { - // unlock in reverse order - while (!lockQueue.isEmpty()) { - lockQueue.pop().lock().readUnLock(); - } + switch (getPolyLineStyle()) { + case NONE: + return; + case AREA: + drawPolyLineLine(gc, dataSets, xAxis, yAxis, dataSetOffset, true); + break; + case ZERO_ORDER_HOLDER: + case STAIR_CASE: + drawPolyLineStairCase(gc, dataSets, xAxis, yAxis, dataSetOffset, false); + break; + case HISTOGRAM: + drawPolyLineHistogram(gc, dataSets, xAxis, yAxis, dataSetOffset, false); + break; + case HISTOGRAM_FILLED: + drawPolyLineHistogram(gc, dataSets, xAxis, yAxis, dataSetOffset, true); + break; + case BEZIER_CURVE: + drawPolyLineHistogramBezier(gc, dataSets, xAxis, yAxis, dataSetOffset, true); + break; + case NORMAL: + default: + drawPolyLineLine(gc, dataSets, xAxis, yAxis, dataSetOffset, false); + break; } } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java index 451cfb06c..c7605e1ca 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRenderer.java @@ -256,28 +256,26 @@ public List render(final GraphicsContext gc, final Chart chart, final i if (!dataSet.isVisible()) { continue; } - dataSet.lock().readLockGuard(() -> { - // check for potentially reduced data range we are supposed to plot - final int indexMin = Math.max(0, dataSet.getIndex(DataSet.DIM_X, xMin)); - final int indexMax = Math.min(dataSet.getIndex(DataSet.DIM_X, xMax) + 1, - dataSet.getDataCount()); - - // return if zero length data set - if (indexMax - indexMin <= 0) { - return; - } - - drawnDataSet.add(dataSet); - if (isHorizontalMarker()) { - // draw horizontal marker - drawHorizontalLabelledMarker(gc, xyChart, dataSet, indexMin, indexMax); - } - - if (isVerticalMarker()) { - // draw vertical marker - drawVerticalLabelledMarker(gc, xyChart, dataSet, indexMin, indexMax); - } - }); + // check for potentially reduced data range we are supposed to plot + final int indexMin = Math.max(0, dataSet.getIndex(DataSet.DIM_X, xMin)); + final int indexMax = Math.min(dataSet.getIndex(DataSet.DIM_X, xMax) + 1, + dataSet.getDataCount()); + + // return if zero length data set + if (indexMax - indexMin <= 0) { + continue; + } + + drawnDataSet.add(dataSet); + if (isHorizontalMarker()) { + // draw horizontal marker + drawHorizontalLabelledMarker(gc, xyChart, dataSet, indexMin, indexMax); + } + + if (isVerticalMarker()) { + // draw vertical marker + drawVerticalLabelledMarker(gc, xyChart, dataSet, indexMin, indexMax); + } } // end of 'dataSetIndex' loop diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java index 2b9a577db..cfeafc5b1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/MountainRangeRenderer.java @@ -97,30 +97,28 @@ public List render(final GraphicsContext gc, final Chart chart, final i continue; } - dataSet.lock().readLockGuardOptimistic(() -> { - xWeakIndexMap.clear(); - yWeakIndexMap.clear(); - mountainRangeExtra = getMountainRangeOffset(); - - final double max = zRangeMax * (1.0 + mountainRangeExtra); - final boolean autoRange = yAxis.isAutoRanging(); - if (autoRange && (zRangeMin != yAxis.getMin() || max != yAxis.getMax())) { - yAxis.setAutoRanging(false); - yAxis.setMin(zRangeMin); - yAxis.setMax(max); - yAxis.setTickUnit(Math.abs(max - zRangeMin) / 10.0); - yAxis.forceRedraw(); - } - yAxis.setAutoRanging(autoRange); + xWeakIndexMap.clear(); + yWeakIndexMap.clear(); + mountainRangeExtra = getMountainRangeOffset(); + + final double max = zRangeMax * (1.0 + mountainRangeExtra); + final boolean autoRange = yAxis.isAutoRanging(); + if (autoRange && (zRangeMin != yAxis.getMin() || max != yAxis.getMax())) { + yAxis.setAutoRanging(false); + yAxis.setMin(zRangeMin); + yAxis.setMax(max); + yAxis.setTickUnit(Math.abs(max - zRangeMin) / 10.0); + yAxis.forceRedraw(); + } + yAxis.setAutoRanging(autoRange); - final int yCountMax = ((GridDataSet) dataSet).getShape(DIM_Y); - checkAndRecreateRenderer(yCountMax); + final int yCountMax = ((GridDataSet) dataSet).getShape(DIM_Y); + checkAndRecreateRenderer(yCountMax); - for (int index = yCountMax - 1; index >= 0; index--) { - renderers.get(index).getDatasets().setAll(new Demux3dTo2dDataSet((GridDataSet) dataSet, index, zRangeMin, max)); // NOPMD -- new necessary here - renderers.get(index).render(gc, chart, 0, empty); - } - }); + for (int index = yCountMax - 1; index >= 0; index--) { + renderers.get(index).getDatasets().setAll(new Demux3dTo2dDataSet((GridDataSet) dataSet, index, zRangeMin, max)); // NOPMD -- new necessary here + renderers.get(index).render(gc, chart, 0, empty); + } } ProcessingProfiler.getTimeDiff(start); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ReducingLineRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ReducingLineRenderer.java index a61be6e74..286860d01 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ReducingLineRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ReducingLineRenderer.java @@ -82,81 +82,79 @@ public List render(final GraphicsContext gc, final Chart chart, final i continue; } final int lindex = index; - ds.lock().readLockGuardOptimistic(() -> { - // update categories in case of category axes for the first - // (index == '0') indexed data set - if (lindex == 0) { - if (xyChart.getXAxis() instanceof CategoryAxis) { - final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); - axis.updateCategories(ds); - } + // update categories in case of category axes for the first + // (index == '0') indexed data set + if (lindex == 0) { + if (xyChart.getXAxis() instanceof CategoryAxis) { + final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); + axis.updateCategories(ds); + } - if (xyChart.getYAxis() instanceof CategoryAxis) { - final CategoryAxis axis = (CategoryAxis) xyChart.getYAxis(); - axis.updateCategories(ds); - } + if (xyChart.getYAxis() instanceof CategoryAxis) { + final CategoryAxis axis = (CategoryAxis) xyChart.getYAxis(); + axis.updateCategories(ds); } + } - gc.save(); - DefaultRenderColorScheme.setLineScheme(gc, ds.getStyle(), lindex); - DefaultRenderColorScheme.setGraphicsContextAttributes(gc, ds.getStyle()); - if (ds.getDataCount() > 0) { - final int indexMin = Math.max(0, ds.getIndex(DIM_X, xmin)); - final int indexMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); - final int n = Math.abs(indexMax - indexMin); - final int d = n / maxPoints; - if (d <= 1) { - int i = ds.getIndex(DIM_X, xmin); - if (i < 0) { - i = 0; - } - double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - double y0 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); - i++; - for (; i < Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); i++) { - final double x1 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - final double y1 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); + gc.save(); + DefaultRenderColorScheme.setLineScheme(gc, ds.getStyle(), lindex); + DefaultRenderColorScheme.setGraphicsContextAttributes(gc, ds.getStyle()); + if (ds.getDataCount() > 0) { + final int indexMin = Math.max(0, ds.getIndex(DIM_X, xmin)); + final int indexMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); + final int n = Math.abs(indexMax - indexMin); + final int d = n / maxPoints; + if (d <= 1) { + int i = ds.getIndex(DIM_X, xmin); + if (i < 0) { + i = 0; + } + double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + double y0 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); + i++; + for (; i < Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); i++) { + final double x1 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + final double y1 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); + gc.strokeLine(x0, y0, x1, y1); + x0 = x1; + y0 = y1; + } + } else { + int i = ds.getIndex(DIM_X, xmin); + if (i < 0) { + i = 0; + } + double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + double y0 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); + i++; + double x1 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + double y1 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); + double delta = Math.abs(y1 - y0); + i++; + int j = d - 2; + for (; i < Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); i++) { + if (j > 0) { + final double x2 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + final double y2 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); + if (Math.abs(y2 - y0) > delta) { + x1 = x2; + y1 = y2; + delta = Math.abs(y2 - y0); + } + j--; + } else { gc.strokeLine(x0, y0, x1, y1); x0 = x1; y0 = y1; - } - } else { - int i = ds.getIndex(DIM_X, xmin); - if (i < 0) { - i = 0; - } - double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - double y0 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); - i++; - double x1 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - double y1 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); - double delta = Math.abs(y1 - y0); - i++; - int j = d - 2; - for (; i < Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); i++) { - if (j > 0) { - final double x2 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - final double y2 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); - if (Math.abs(y2 - y0) > delta) { - x1 = x2; - y1 = y2; - delta = Math.abs(y2 - y0); - } - j--; - } else { - gc.strokeLine(x0, y0, x1, y1); - x0 = x1; - y0 = y1; - x1 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - y1 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); - delta = Math.abs(y1 - y0); - j = d - 1; - } + x1 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + y1 = yAxis.getDisplayPosition(ds.get(DIM_Y, i)); + delta = Math.abs(y1 - y0); + j = d - 1; } } } - gc.restore(); - }); + } + gc.restore(); index++; } ProcessingProfiler.getTimeDiff(start); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRenderer.java index 104c3f06e..fadbfe798 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/CandleStickRenderer.java @@ -142,128 +142,127 @@ public List render(final GraphicsContext gc, final Chart chart, final i continue; final int lindex = index; - ds.lock().readLockGuardOptimistic(() -> { - // update categories in case of category axes for the first (index == '0') indexed data set - if (lindex == 0 && xyChart.getXAxis() instanceof CategoryAxis) { - final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); - axis.updateCategories(ds); - } - AttributeModelAware attrs = null; - if (ds instanceof AttributeModelAware) { - attrs = (AttributeModelAware) ds; - } - IOhlcvItemAware itemAware = null; - if (ds instanceof IOhlcvItemAware) { - itemAware = (IOhlcvItemAware) ds; + // update categories in case of category axes for the first (index == '0') indexed data set + if (lindex == 0 && xyChart.getXAxis() instanceof CategoryAxis) { + final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); + axis.updateCategories(ds); + } + AttributeModelAware attrs = null; + if (ds instanceof AttributeModelAware) { + attrs = (AttributeModelAware) ds; + } + IOhlcvItemAware itemAware = null; + if (ds instanceof IOhlcvItemAware) { + itemAware = (IOhlcvItemAware) ds; + } + boolean isEpAvailable = !paintAfterEPS.isEmpty() || paintBarMarker != null; + + gc.save(); + // default styling level + String style = ds.getStyle(); + DefaultRenderColorScheme.setLineScheme(gc, style, lindex); + DefaultRenderColorScheme.setGraphicsContextAttributes(gc, style); + // financial styling level + var candleLongColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_LONG_COLOR, Color.GREEN); + var candleShortColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_SHORT_COLOR, Color.RED); + var candleLongWickColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_LONG_WICK_COLOR, Color.BLACK); + var candleShortWickColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_SHORT_WICK_COLOR, Color.BLACK); + var candleShadowColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_SHADOW_COLOR, null); + var candleVolumeLongColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_LONG_COLOR, Color.rgb(139, 199, 194, 0.2)); + var candleVolumeShortColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_SHORT_COLOR, Color.rgb(235, 160, 159, 0.2)); + double barWidthPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_CANDLESTICK_BAR_WIDTH_PERCENTAGE, 0.5d); + double shadowLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_LINE_WIDTH, 2.5d); + double shadowTransPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_TRANSPOSITION_PERCENT, 0.5d); + + if (ds.getDataCount() > 0) { + int iMin = ds.getIndex(DIM_X, xmin); + if (iMin < 0) + iMin = 0; + int iMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); + + double[] distances = null; + var minRequiredWidth = 0.0; + if (lindex == 0) { + distances = findAreaDistances(findAreaDistances, ds, xAxis, yAxis, xmin, xmax); + minRequiredWidth = distances[0]; } - boolean isEpAvailable = !paintAfterEPS.isEmpty() || paintBarMarker != null; - - gc.save(); - // default styling level - String style = ds.getStyle(); - DefaultRenderColorScheme.setLineScheme(gc, style, lindex); - DefaultRenderColorScheme.setGraphicsContextAttributes(gc, style); - // financial styling level - var candleLongColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_LONG_COLOR, Color.GREEN); - var candleShortColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_SHORT_COLOR, Color.RED); - var candleLongWickColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_LONG_WICK_COLOR, Color.BLACK); - var candleShortWickColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_SHORT_WICK_COLOR, Color.BLACK); - var candleShadowColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_SHADOW_COLOR, null); - var candleVolumeLongColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_LONG_COLOR, Color.rgb(139, 199, 194, 0.2)); - var candleVolumeShortColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_SHORT_COLOR, Color.rgb(235, 160, 159, 0.2)); - double barWidthPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_CANDLESTICK_BAR_WIDTH_PERCENTAGE, 0.5d); - double shadowLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_LINE_WIDTH, 2.5d); - double shadowTransPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_TRANSPOSITION_PERCENT, 0.5d); - - if (ds.getDataCount() > 0) { - int iMin = ds.getIndex(DIM_X, xmin); - if (iMin < 0) - iMin = 0; - int iMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); - - double[] distances = null; - var minRequiredWidth = 0.0; - if (lindex == 0) { - distances = findAreaDistances(findAreaDistances, ds, xAxis, yAxis, xmin, xmax); - minRequiredWidth = distances[0]; + double localBarWidth = minRequiredWidth * barWidthPercent; + double barWidthHalf = localBarWidth / 2.0; + + for (int i = iMin; i < iMax; i++) { + double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + double yOpen = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_OPEN, i)); + double yHigh = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_HIGH, i)); + double yLow = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_LOW, i)); + double yClose = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_CLOSE, i)); + + double yDiff = yOpen - yClose; + double yMin = yDiff > 0 ? yClose : yOpen; + + // prepare extension point data (if EPs available) + OhlcvRendererEpData data = null; + if (isEpAvailable) { + data = new OhlcvRendererEpData(); + data.gc = gc; + data.ds = ds; + data.attrs = attrs; + data.ohlcvItemAware = itemAware; + data.ohlcvItem = itemAware != null ? itemAware.getItem(i) : null; + data.index = i; + data.minIndex = iMin; + data.maxIndex = iMax; + data.barWidth = localBarWidth; + data.barWidthHalf = barWidthHalf; + data.xCenter = x0; + data.yOpen = yOpen; + data.yHigh = yHigh; + data.yLow = yLow; + data.yClose = yClose; + data.yDiff = yDiff; + data.yMin = yMin; + } + + // paint volume + if (paintVolume) { + assert distances != null; + paintVolume(gc, ds, i, candleVolumeLongColor, candleVolumeShortColor, yAxis, distances, localBarWidth, barWidthHalf, x0); + } + + // paint shadow + if (candleShadowColor != null) { + double lineWidth = gc.getLineWidth(); + paintCandleShadow(gc, + candleShadowColor, shadowLineWidth, shadowTransPercent, + localBarWidth, barWidthHalf, x0, yOpen, yClose, yLow, yHigh, yDiff, yMin); + gc.setLineWidth(lineWidth); + } + + // choose color of the bar + Paint barPaint = data == null ? null : getPaintBarColor(data); + + if (yDiff > 0) { + gc.setFill(Objects.requireNonNullElse(barPaint, candleLongColor)); + gc.setStroke(Objects.requireNonNullElse(barPaint, candleLongWickColor)); + } else { + yDiff = Math.abs(yDiff); + gc.setFill(Objects.requireNonNullElse(barPaint, candleShortColor)); + gc.setStroke(Objects.requireNonNullElse(barPaint, candleShortWickColor)); } - double localBarWidth = minRequiredWidth * barWidthPercent; - double barWidthHalf = localBarWidth / 2.0; - - for (int i = iMin; i < iMax; i++) { - double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - double yOpen = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_OPEN, i)); - double yHigh = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_HIGH, i)); - double yLow = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_LOW, i)); - double yClose = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_CLOSE, i)); - - double yDiff = yOpen - yClose; - double yMin = yDiff > 0 ? yClose : yOpen; - - // prepare extension point data (if EPs available) - OhlcvRendererEpData data = null; - if (isEpAvailable) { - data = new OhlcvRendererEpData(); - data.gc = gc; - data.ds = ds; - data.attrs = attrs; - data.ohlcvItemAware = itemAware; - data.ohlcvItem = itemAware != null ? itemAware.getItem(i) : null; - data.index = i; - data.minIndex = iMin; - data.maxIndex = iMax; - data.barWidth = localBarWidth; - data.barWidthHalf = barWidthHalf; - data.xCenter = x0; - data.yOpen = yOpen; - data.yHigh = yHigh; - data.yLow = yLow; - data.yClose = yClose; - data.yDiff = yDiff; - data.yMin = yMin; - } - - // paint volume - if (paintVolume) { - assert distances != null; - paintVolume(gc, ds, i, candleVolumeLongColor, candleVolumeShortColor, yAxis, distances, localBarWidth, barWidthHalf, x0); - } - - // paint shadow - if (candleShadowColor != null) { - double lineWidth = gc.getLineWidth(); - paintCandleShadow(gc, - candleShadowColor, shadowLineWidth, shadowTransPercent, - localBarWidth, barWidthHalf, x0, yOpen, yClose, yLow, yHigh, yDiff, yMin); - gc.setLineWidth(lineWidth); - } - - // choose color of the bar - Paint barPaint = data == null ? null : getPaintBarColor(data); - - if (yDiff > 0) { - gc.setFill(Objects.requireNonNullElse(barPaint, candleLongColor)); - gc.setStroke(Objects.requireNonNullElse(barPaint, candleLongWickColor)); - } else { - yDiff = Math.abs(yDiff); - gc.setFill(Objects.requireNonNullElse(barPaint, candleShortColor)); - gc.setStroke(Objects.requireNonNullElse(barPaint, candleShortWickColor)); - } - - // paint candle - gc.strokeLine(x0, yLow, x0, yDiff > 0 ? yOpen : yClose); - gc.strokeLine(x0, yHigh, x0, yDiff > 0 ? yClose : yOpen); - gc.fillRect(x0 - barWidthHalf, yMin, localBarWidth, yDiff); // open-close - gc.strokeRect(x0 - barWidthHalf, yMin, localBarWidth, yDiff); // open-close - - // extension point - paint after painting of candle - if (!paintAfterEPS.isEmpty()) { - paintAfter(data); - } + + // paint candle + gc.strokeLine(x0, yLow, x0, yDiff > 0 ? yOpen : yClose); + gc.strokeLine(x0, yHigh, x0, yDiff > 0 ? yClose : yOpen); + gc.fillRect(x0 - barWidthHalf, yMin, localBarWidth, yDiff); // open-close + gc.strokeRect(x0 - barWidthHalf, yMin, localBarWidth, yDiff); // open-close + + // extension point - paint after painting of candle + if (!paintAfterEPS.isEmpty()) { + paintAfter(data); } } - gc.restore(); - }); + } + gc.restore(); + // possibility to re-arrange y-axis by min/max of dataset (after paint) if (computeLocalRange()) { applyLocalYRange(ds, yAxis, xmin, xmax); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRenderer.java index 23daef35f..fbd3a1bf1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/FootprintRenderer.java @@ -186,89 +186,88 @@ public List render(final GraphicsContext gc, final Chart chart, final i continue; final int lindex = index; - ds.lock().readLockGuardOptimistic(() -> { - // update categories in case of category axes for the first (index == '0') indexed data set - if (lindex == 0 && xyChart.getXAxis() instanceof CategoryAxis) { - final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); - axis.updateCategories(ds); - } - attrs = null; - if (ds instanceof AttributeModelAware) { - attrs = (AttributeModelAware) ds; + // update categories in case of category axes for the first (index == '0') indexed data set + if (lindex == 0 && xyChart.getXAxis() instanceof CategoryAxis) { + final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); + axis.updateCategories(ds); + } + attrs = null; + if (ds instanceof AttributeModelAware) { + attrs = (AttributeModelAware) ds; + } + itemAware = (IOhlcvItemAware) ds; + isEpAvailable = !paintAfterEPS.isEmpty() || paintBarMarker != null; + + gc.save(); + // default styling level + String style = ds.getStyle(); + DefaultRenderColorScheme.setLineScheme(gc, style, lindex); + DefaultRenderColorScheme.setGraphicsContextAttributes(gc, style); + + // footprint settings + Font basicFontTemplate = footprintAttrs.getRequiredAttribute(BID_ASK_VOLUME_FONTS)[1]; + Font selectedFontTemplate = footprintAttrs.getRequiredAttribute(BID_ASK_VOLUME_FONTS)[2]; + + // financial styling level + pocColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_POC_COLOR, Color.rgb(255, 255, 0)); + footprintDefaultFontColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_DEFAULT_FONT_COLOR, Color.rgb(255, 255, 255, 0.58)); + footprintCrossLineColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_CROSS_LINE_COLOR, Color.GRAY); + footprintBoxLongColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_LONG_COLOR, Color.GREEN); + fooprintBoxShortColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_SHORT_COLOR, Color.RED); + footprintVolumeLongColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_VOLUME_LONG_COLOR, Color.rgb(139, 199, 194, 0.2)); + footprintVolumeShortColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_VOLUME_SHORT_COLOR, Color.rgb(235, 160, 159, 0.2)); + double barWidthPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_FOOTPRINT_BAR_WIDTH_PERCENTAGE, 0.5d); + double positionPaintMainRatio = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_FOOTPRINT_PAINT_MAIN_RATIO, 5.157d); + + if (ds.getDataCount() > 0) { + iMin = ds.getIndex(DIM_X, xmin); + if (iMin < 0) + iMin = 0; + iMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); + + distances = null; + double minRequiredWidth = 0.0; + if (lindex == 0) { + distances = findAreaDistances(findAreaDistances, ds, xAxis, yAxis, xmin, xmax); + minRequiredWidth = distances[0]; } - itemAware = (IOhlcvItemAware) ds; - isEpAvailable = !paintAfterEPS.isEmpty() || paintBarMarker != null; - - gc.save(); - // default styling level - String style = ds.getStyle(); - DefaultRenderColorScheme.setLineScheme(gc, style, lindex); - DefaultRenderColorScheme.setGraphicsContextAttributes(gc, style); - - // footprint settings - Font basicFontTemplate = footprintAttrs.getRequiredAttribute(BID_ASK_VOLUME_FONTS)[1]; - Font selectedFontTemplate = footprintAttrs.getRequiredAttribute(BID_ASK_VOLUME_FONTS)[2]; - - // financial styling level - pocColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_POC_COLOR, Color.rgb(255, 255, 0)); - footprintDefaultFontColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_DEFAULT_FONT_COLOR, Color.rgb(255, 255, 255, 0.58)); - footprintCrossLineColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_CROSS_LINE_COLOR, Color.GRAY); - footprintBoxLongColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_LONG_COLOR, Color.GREEN); - fooprintBoxShortColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_SHORT_COLOR, Color.RED); - footprintVolumeLongColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_VOLUME_LONG_COLOR, Color.rgb(139, 199, 194, 0.2)); - footprintVolumeShortColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_VOLUME_SHORT_COLOR, Color.rgb(235, 160, 159, 0.2)); - double barWidthPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_FOOTPRINT_BAR_WIDTH_PERCENTAGE, 0.5d); - double positionPaintMainRatio = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_FOOTPRINT_PAINT_MAIN_RATIO, 5.157d); - - if (ds.getDataCount() > 0) { - iMin = ds.getIndex(DIM_X, xmin); - if (iMin < 0) - iMin = 0; - iMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); - - distances = null; - double minRequiredWidth = 0.0; - if (lindex == 0) { - distances = findAreaDistances(findAreaDistances, ds, xAxis, yAxis, xmin, xmax); - minRequiredWidth = distances[0]; + localBarWidth = minRequiredWidth * barWidthPercent; + barWidthHalf = localBarWidth / 2.0; + ratio = Math.pow(localBarWidth, 0.25) * positionPaintMainRatio; + + // calculate ratio depended attributes + basicFont = getFontWithRatio(basicFontTemplate, ratio); + selectedFont = getFontWithRatio(selectedFontTemplate, ratio); + fontGap = getFontGap(5.0, ratio); + basicGap = getFontGap(1.0, ratio); + + FontMetrics metricsBasicFont = getFontMetrics(basicFont); + heightText = metricsBasicFont.getLeading() + metricsBasicFont.getAscent(); + + for (int i = iMin; i < iMax; i++) { + double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + // get all additional information for footprints + IOhlcvItem ohlcvItem = itemAware.getItem(i); + IOhlcvItem lastOhlcvItem = itemAware.getLastItem(); + boolean isLastBar = lastOhlcvItem == null || lastOhlcvItem.getTimeStamp().equals(ohlcvItem.getTimeStamp()); + if (!footprintRenderedApi.isFootprintAvailable(ohlcvItem)) { + continue; } - localBarWidth = minRequiredWidth * barWidthPercent; - barWidthHalf = localBarWidth / 2.0; - ratio = Math.pow(localBarWidth, 0.25) * positionPaintMainRatio; - - // calculate ratio depended attributes - basicFont = getFontWithRatio(basicFontTemplate, ratio); - selectedFont = getFontWithRatio(selectedFontTemplate, ratio); - fontGap = getFontGap(5.0, ratio); - basicGap = getFontGap(1.0, ratio); - - FontMetrics metricsBasicFont = getFontMetrics(basicFont); - heightText = metricsBasicFont.getLeading() + metricsBasicFont.getAscent(); - - for (int i = iMin; i < iMax; i++) { - double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - // get all additional information for footprints - IOhlcvItem ohlcvItem = itemAware.getItem(i); - IOhlcvItem lastOhlcvItem = itemAware.getLastItem(); - boolean isLastBar = lastOhlcvItem == null || lastOhlcvItem.getTimeStamp().equals(ohlcvItem.getTimeStamp()); - if (!footprintRenderedApi.isFootprintAvailable(ohlcvItem)) { - continue; - } - synchronized (footprintRenderedApi.getLock(ohlcvItem)) { - drawFootprintItem(gc, yAxis, ds, i, x0, ohlcvItem, isEpAvailable, isLastBar, paintVolume); - - if (isLastBar && paintPullbackColumn) { - IOhlcvItem pullbackColumn = footprintRenderedApi.getPullbackColumn(ohlcvItem); - if (pullbackColumn != null) { - x0 = x0 + localBarWidth + barWidthHalf; - drawFootprintItem(gc, yAxis, ds, i, x0, pullbackColumn, false, true, false); - } + synchronized (footprintRenderedApi.getLock(ohlcvItem)) { + drawFootprintItem(gc, yAxis, ds, i, x0, ohlcvItem, isEpAvailable, isLastBar, paintVolume); + + if (isLastBar && paintPullbackColumn) { + IOhlcvItem pullbackColumn = footprintRenderedApi.getPullbackColumn(ohlcvItem); + if (pullbackColumn != null) { + x0 = x0 + localBarWidth + barWidthHalf; + drawFootprintItem(gc, yAxis, ds, i, x0, pullbackColumn, false, true, false); } } } } - gc.restore(); - }); + } + gc.restore(); + // possibility to re-arrange y-axis by min/max of dataset (after paint) if (computeLocalRange()) { applyLocalYRange(ds, yAxis, xmin, xmax); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRenderer.java index d2be687bd..2480d3076 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/financial/HighLowRenderer.java @@ -140,119 +140,118 @@ public List render(final GraphicsContext gc, final Chart chart, final i continue; final int lindex = index; - ds.lock().readLockGuardOptimistic(() -> { - // update categories in case of category axes for the first (index == '0') indexed data set - if (lindex == 0 && xyChart.getXAxis() instanceof CategoryAxis) { - final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); - axis.updateCategories(ds); - } - AttributeModelAware attrs = null; - if (ds instanceof AttributeModelAware) { - attrs = (AttributeModelAware) ds; - } - IOhlcvItemAware itemAware = null; - if (ds instanceof IOhlcvItemAware) { - itemAware = (IOhlcvItemAware) ds; + // update categories in case of category axes for the first (index == '0') indexed data set + if (lindex == 0 && xyChart.getXAxis() instanceof CategoryAxis) { + final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); + axis.updateCategories(ds); + } + AttributeModelAware attrs = null; + if (ds instanceof AttributeModelAware) { + attrs = (AttributeModelAware) ds; + } + IOhlcvItemAware itemAware = null; + if (ds instanceof IOhlcvItemAware) { + itemAware = (IOhlcvItemAware) ds; + } + boolean isEpAvailable = !paintAfterEPS.isEmpty() || paintBarMarker != null; + + gc.save(); + // default styling level + String style = ds.getStyle(); + DefaultRenderColorScheme.setLineScheme(gc, style, lindex); + DefaultRenderColorScheme.setGraphicsContextAttributes(gc, style); + // financial styling level + Color longBodyColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_BODY_LONG_COLOR, Color.GREEN); + Color shortBodyColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_BODY_SHORT_COLOR, Color.RED); + Color longTickColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_TICK_LONG_COLOR, Color.GREEN); + Color shortTickColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_TICK_SHORT_COLOR, Color.RED); + Color hiLowShadowColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_SHADOW_COLOR, null); + Color candleVolumeLongColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_LONG_COLOR, Color.rgb(139, 199, 194, 0.2)); + Color candleVolumeShortColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_SHORT_COLOR, Color.rgb(235, 160, 159, 0.2)); + double bodyLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_HILOW_BODY_LINEWIDTH, 1.2d); + double tickLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_HILOW_TICK_LINEWIDTH, 1.2d); + double barWidthPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_HILOW_BAR_WIDTH_PERCENTAGE, 0.6d); + double shadowLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_LINE_WIDTH, 2.5d); + double shadowTransPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_TRANSPOSITION_PERCENT, 0.5d); + + if (ds.getDataCount() > 0) { + int iMin = ds.getIndex(DIM_X, xmin); + if (iMin < 0) + iMin = 0; + int iMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); + + double[] distances = null; + double minRequiredWidth = 0.0; + if (lindex == 0) { + distances = findAreaDistances(findAreaDistances, ds, xAxis, yAxis, xmin, xmax); + minRequiredWidth = distances[0]; } - boolean isEpAvailable = !paintAfterEPS.isEmpty() || paintBarMarker != null; - - gc.save(); - // default styling level - String style = ds.getStyle(); - DefaultRenderColorScheme.setLineScheme(gc, style, lindex); - DefaultRenderColorScheme.setGraphicsContextAttributes(gc, style); - // financial styling level - Color longBodyColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_BODY_LONG_COLOR, Color.GREEN); - Color shortBodyColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_BODY_SHORT_COLOR, Color.RED); - Color longTickColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_TICK_LONG_COLOR, Color.GREEN); - Color shortTickColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_TICK_SHORT_COLOR, Color.RED); - Color hiLowShadowColor = StyleParser.getColorPropertyValue(style, DATASET_HILOW_SHADOW_COLOR, null); - Color candleVolumeLongColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_LONG_COLOR, Color.rgb(139, 199, 194, 0.2)); - Color candleVolumeShortColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_VOLUME_SHORT_COLOR, Color.rgb(235, 160, 159, 0.2)); - double bodyLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_HILOW_BODY_LINEWIDTH, 1.2d); - double tickLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_HILOW_TICK_LINEWIDTH, 1.2d); - double barWidthPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_HILOW_BAR_WIDTH_PERCENTAGE, 0.6d); - double shadowLineWidth = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_LINE_WIDTH, 2.5d); - double shadowTransPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_SHADOW_TRANSPOSITION_PERCENT, 0.5d); - - if (ds.getDataCount() > 0) { - int iMin = ds.getIndex(DIM_X, xmin); - if (iMin < 0) - iMin = 0; - int iMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); - - double[] distances = null; - double minRequiredWidth = 0.0; - if (lindex == 0) { - distances = findAreaDistances(findAreaDistances, ds, xAxis, yAxis, xmin, xmax); - minRequiredWidth = distances[0]; + double localBarWidth = minRequiredWidth * barWidthPercent; + double barWidthHalf = localBarWidth / 2.0; + + for (int i = iMin; i < iMax; i++) { + double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); + double yOpen = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_OPEN, i)); + double yHigh = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_HIGH, i)); + double yLow = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_LOW, i)); + double yClose = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_CLOSE, i)); + + // prepare extension point data (if EPs available) + OhlcvRendererEpData data = null; + if (isEpAvailable) { + data = new OhlcvRendererEpData(); + data.gc = gc; + data.ds = ds; + data.attrs = attrs; + data.ohlcvItemAware = itemAware; + data.ohlcvItem = itemAware != null ? itemAware.getItem(i) : null; + data.index = i; + data.minIndex = iMin; + data.maxIndex = iMax; + data.barWidth = localBarWidth; + data.barWidthHalf = barWidthHalf; + data.xCenter = x0; + data.yOpen = yOpen; + data.yHigh = yHigh; + data.yLow = yLow; + data.yClose = yClose; + } + + // paint volume + if (paintVolume) { + assert distances != null; + paintVolume(gc, ds, i, candleVolumeLongColor, candleVolumeShortColor, yAxis, distances, localBarWidth, barWidthHalf, x0); } - double localBarWidth = minRequiredWidth * barWidthPercent; - double barWidthHalf = localBarWidth / 2.0; - - for (int i = iMin; i < iMax; i++) { - double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); - double yOpen = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_OPEN, i)); - double yHigh = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_HIGH, i)); - double yLow = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_LOW, i)); - double yClose = yAxis.getDisplayPosition(ds.get(OhlcvDataSet.DIM_Y_CLOSE, i)); - - // prepare extension point data (if EPs available) - OhlcvRendererEpData data = null; - if (isEpAvailable) { - data = new OhlcvRendererEpData(); - data.gc = gc; - data.ds = ds; - data.attrs = attrs; - data.ohlcvItemAware = itemAware; - data.ohlcvItem = itemAware != null ? itemAware.getItem(i) : null; - data.index = i; - data.minIndex = iMin; - data.maxIndex = iMax; - data.barWidth = localBarWidth; - data.barWidthHalf = barWidthHalf; - data.xCenter = x0; - data.yOpen = yOpen; - data.yHigh = yHigh; - data.yLow = yLow; - data.yClose = yClose; - } - - // paint volume - if (paintVolume) { - assert distances != null; - paintVolume(gc, ds, i, candleVolumeLongColor, candleVolumeShortColor, yAxis, distances, localBarWidth, barWidthHalf, x0); - } - - // paint shadow - if (hiLowShadowColor != null) { - double lineWidth = gc.getLineWidth(); - paintHiLowShadow(gc, hiLowShadowColor, shadowLineWidth, shadowTransPercent, barWidthHalf, x0, yOpen, yClose, yLow, yHigh); - gc.setLineWidth(lineWidth); - } - - // choose color of the bar - Paint barPaint = data == null ? null : getPaintBarColor(data); - - // the ohlc body - gc.setStroke(Objects.requireNonNullElse(barPaint, yOpen > yClose ? longBodyColor : shortBodyColor)); - gc.setLineWidth(bodyLineWidth); - gc.strokeLine(x0, yLow, x0, yHigh); - - // paint open/close tick - gc.setStroke(Objects.requireNonNullElse(barPaint, yOpen > yClose ? longTickColor : shortTickColor)); - gc.setLineWidth(tickLineWidth); - gc.strokeLine(x0 - barWidthHalf, yOpen, x0, yOpen); - gc.strokeLine(x0, yClose, x0 + barWidthHalf, yClose); - - // extension point - paint after painting of bar - if (!paintAfterEPS.isEmpty()) { - paintAfter(data); - } + + // paint shadow + if (hiLowShadowColor != null) { + double lineWidth = gc.getLineWidth(); + paintHiLowShadow(gc, hiLowShadowColor, shadowLineWidth, shadowTransPercent, barWidthHalf, x0, yOpen, yClose, yLow, yHigh); + gc.setLineWidth(lineWidth); + } + + // choose color of the bar + Paint barPaint = data == null ? null : getPaintBarColor(data); + + // the ohlc body + gc.setStroke(Objects.requireNonNullElse(barPaint, yOpen > yClose ? longBodyColor : shortBodyColor)); + gc.setLineWidth(bodyLineWidth); + gc.strokeLine(x0, yLow, x0, yHigh); + + // paint open/close tick + gc.setStroke(Objects.requireNonNullElse(barPaint, yOpen > yClose ? longTickColor : shortTickColor)); + gc.setLineWidth(tickLineWidth); + gc.strokeLine(x0 - barWidthHalf, yOpen, x0, yOpen); + gc.strokeLine(x0, yClose, x0 + barWidthHalf, yClose); + + // extension point - paint after painting of bar + if (!paintAfterEPS.isEmpty()) { + paintAfter(data); } } - gc.restore(); - }); + } + gc.restore(); + // possibility to re-arrange y-axis by min/max of dataset (after paint) if (computeLocalRange()) { applyLocalYRange(ds, yAxis, xmin, xmax); From f3e90dfeb68331bf27c63d80064712e47266b82c Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 27 Jul 2023 17:40:06 +0200 Subject: [PATCH 39/81] removed some unnecessary redraws --- .../main/java/io/fair_acc/chartfx/Chart.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 4f044ef2a..6ad3da6dc 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -78,7 +78,14 @@ public abstract class Chart extends Region implements EventSource { protected final BitState state = BitState.initDirty(this, BitState.ALL_BITS) .addChangeListener(ChartBits.KnownMask, (src, bits) -> layoutHooks.registerOnce()) .addChangeListener(ChartBits.ChartLayout, (src, bits) -> super.requestLayout()); - protected final StateListener dataSetListener = FXUtils.runOnFxThread(state); // datasets can be updated from different threads + + // DataSets are the only part that can potentially get updated from different threads, so we use a separate + // state object that can handle multithreaded updates. The state always represents the current aggregate state + // of all datasets, but the JavaFX change listener may not forward the dirty bits to the chart until the next frame. + // This creates a race condition where delta bits that are already cleared in the datasets may end up dirtying the + // chart and trigger an unnecessary redraw. To avoid this issue we ignore the delta and pass the current state. + protected final BitState dataSetState = BitState.initDirty(this, BitState.ALL_BITS) + .addChangeListener(FXUtils.runOnFxThread((src, deltaBits) -> state.setDirty(src.getBits()))); private static final Logger LOGGER = LoggerFactory.getLogger(Chart.class); private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); @@ -104,9 +111,6 @@ public abstract class Chart extends Region implements EventSource { */ private final StyleableBooleanProperty legendVisible = CSS.createBooleanProperty(this, "legendVisible", true, state.onAction(ChartBits.ChartLegend)); - // isCanvasChangeRequested is a recursion guard to update canvas only once - protected boolean isCanvasChangeRequested; - // layoutOngoing is a recursion guard to update canvas only once protected final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>()); private final Map pluginGroups = new ConcurrentHashMap<>(); private final ObservableList plugins = FXCollections.observableList(new LinkedList<>()); @@ -347,14 +351,6 @@ public Chart(Axis... axes) { // recursions w.r.t. repainting getCanvasForeground().resize(width, height); } - - if (!isCanvasChangeRequested) { - isCanvasChangeRequested = true; - Platform.runLater(() -> { - this.layoutChildren(); - isCanvasChangeRequested = false; - }); - } }; canvas.widthProperty().addListener(canvasSizeChangeListener); canvas.heightProperty().addListener(canvasSizeChangeListener); @@ -643,6 +639,7 @@ protected void runPostLayout() { axis.drawAxis(); } redrawCanvas(); + dataSetState.clear(); state.clear(); for (var ds : lockedDataSets) { ds.getBitState().clear(); // technically a 'write' @@ -830,10 +827,10 @@ protected void datasetsChanged(final ListChangeListener.Change Date: Thu, 27 Jul 2023 18:19:08 +0200 Subject: [PATCH 40/81] moved showing property to a utility method --- .../main/java/io/fair_acc/chartfx/Chart.java | 57 +++---------------- .../io/fair_acc/chartfx/utils/FXUtils.java | 37 ++++++++++-- 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 6ad3da6dc..54ee4b1e8 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -93,9 +93,11 @@ public abstract class Chart extends Region implements EventSource { private static final int DEFAULT_TRIGGER_DISTANCE = 50; protected static final boolean DEBUG = Boolean.getBoolean("chartfx.debug"); // for more verbose debugging - protected BooleanBinding showingBinding; protected final BooleanProperty showing = new SimpleBooleanProperty(this, "showing", false); - protected final ChangeListener showingListener = (ch2, o, n) -> showing.set(n); + { + showing.bind(FXUtils.getShowingBinding(this)); + } + /** * When true any data changes will be animated. */ @@ -165,39 +167,12 @@ public abstract class Chart extends Region implements EventSource { protected final ListChangeListener axesChangeListener = this::axesChanged; protected final ListChangeListener datasetChangeListener = this::datasetsChanged; protected final ListChangeListener pluginsChangedListener = this::pluginsChanged; - protected final ChangeListener windowPropertyListener = (ch1, oldWindow, newWindow) -> { - if (oldWindow != null) { - oldWindow.showingProperty().removeListener(showingListener); - } - if (newWindow == null) { - showing.set(false); - return; - } - newWindow.showingProperty().addListener(showingListener); - }; - private final ChangeListener scenePropertyListener = (ch, oldScene, newScene) -> { - if (oldScene == newScene) { - return; - } - if (oldScene != null) { - // remove listener - oldScene.windowProperty().removeListener(windowPropertyListener); - } - - if (newScene == null) { - showing.set(false); - return; - } - layoutHooks.registerOnce(); - // add listener - newScene.windowProperty().addListener(windowPropertyListener); - }; { getDatasets().addListener(datasetChangeListener); getAxes().addListener(axesChangeListener); - // update listener to propagate axes changes to chart changes getAxes().addListener(axesChangeListenerLocal); + showing.addListener((obs, old, showing) -> layoutHooks.registerOnce()); } protected final Label titleLabel = new Label(); @@ -302,7 +277,7 @@ protected void invalidated() { break; } return (newVal); - }, this::requestLayout); + }); /** * Creates a new default Chart instance. @@ -399,8 +374,6 @@ public Chart(Axis... axes) { titleLabel.getStyleClass().add("chart-title"); getStyleClass().add("chart"); axesAndCanvasPane.getStyleClass().add("chart-content"); - - registerShowingListener(); // NOPMD - unlikely but allowed override } @Override @@ -883,22 +856,6 @@ protected void pluginsChanged(final ListChangeListener.Change { - if (Boolean.TRUE.equals(n)) { - // requestLayout(); - - // alt implementation in case of start-up issues - final KeyFrame kf1 = new KeyFrame(Duration.millis(20), e -> requestLayout()); - - final Timeline timeline = new Timeline(kf1); - Platform.runLater(timeline::play); - } - }); - } - protected void rendererChanged(final ListChangeListener.Change change) { FXUtils.assertJavaFxThread(); while (change.next()) { @@ -948,7 +905,7 @@ protected void updateLegend(final List dataSets, final List r protected void updatePluginsArea() { var pluginChildren = plugins.stream().map(pluginGroups::get).collect(Collectors.toList()); pluginsArea.getChildren().setAll(pluginChildren); - requestLayout(); + fireInvalidated(ChartBits.ChartPlugins); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java index 78e00a492..4e04c54e5 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/utils/FXUtils.java @@ -1,9 +1,6 @@ package io.fair_acc.chartfx.utils; -import java.util.List; -import java.util.Objects; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; @@ -14,9 +11,14 @@ import io.fair_acc.dataset.events.StateListener; import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableBooleanValue; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.stage.Window; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -295,4 +297,31 @@ public static StateListener runOnFxThread(StateListener listener) { }; } + public static ObservableBooleanValue getShowingBinding(Node node) { + BooleanProperty showing = new SimpleBooleanProperty(); + Runnable update = () -> showing.set(Optional.ofNullable(node.getScene()) + .flatMap(scene -> Optional.ofNullable(scene.getWindow())) + .map(Window::isShowing) + .orElse(false)); + update.run(); // initial value + + ChangeListener onShowingChange = (obs, old, value) -> { + update.run(); + }; + + ChangeListener onWindowChange = (obs, old, value) -> { + if(old != null) old.showingProperty().removeListener(onShowingChange); + if(value != null) value.showingProperty().addListener(onShowingChange); + update.run(); + }; + + node.sceneProperty().addListener((obs, old, value) -> { + if(old != null) old.windowProperty().removeListener(onWindowChange); + if(value != null) value.windowProperty().addListener(onWindowChange); + update.run(); + }); + + return showing; + } + } From 418a85b3dd7bf52802f3a15e8ff7320f7bb31642 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 27 Jul 2023 23:19:26 +0200 Subject: [PATCH 41/81] fixed category axis sample --- .../main/java/io/fair_acc/chartfx/Chart.java | 6 +++++- .../java/io/fair_acc/chartfx/XYChart.java | 1 - .../chartfx/axes/spi/AbstractAxis.java | 11 +---------- .../chartfx/axes/spi/CategoryAxis.java | 19 ++++++------------- .../fair_acc/chartfx/ui/utils/LayoutHook.java | 9 +++++---- 5 files changed, 17 insertions(+), 29 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 54ee4b1e8..5c27a74c0 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -172,7 +172,11 @@ public abstract class Chart extends Region implements EventSource { getDatasets().addListener(datasetChangeListener); getAxes().addListener(axesChangeListener); getAxes().addListener(axesChangeListenerLocal); - showing.addListener((obs, old, showing) -> layoutHooks.registerOnce()); + showing.addListener((obs, old, showing) -> { + if (showing) { + layoutHooks.registerOnce(); + } + }); } protected final Label titleLabel = new Label(); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index fa45ab975..7a3e00b4b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -436,7 +436,6 @@ protected static void updateNumericAxis(final Axis axis, final List dat // Trigger a redraw if (changed && (axis.isAutoRanging() || axis.isAutoGrowRanging())) { axis.invalidateRange(); - axis.requestAxisLayout(); } // TODO: is this used for anything? can it be removed? diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 161d9bdfc..c1cc64bc1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -251,15 +251,6 @@ protected void updateAxisRange(double length) { setLength(length); AxisRange range = getRange(); - // Avoid NaNs getting into the system (TODO: happened due to bad interactions w/ event system. Still needed?) - if (!Double.isFinite(range.getMin()) || !Double.isFinite(range.getMax())) { - set(Double.NaN, Double.NaN); - setScale(0.1); - setTickUnit(0.1); - updateCachedVariables(); - return; - } - // Set a real finite range set(range.getMin(), range.getMax()); setScale(range.scale = calculateNewScale(length, getMin(), getMax())); @@ -626,7 +617,7 @@ protected void updateMajorTickMarks(AxisRange range) { var oldTickValues = getTickMarkValues(); if(newTickValues.size() < 2) { return; // TODO: why did the previous code only update when there are > 2 ticks? - }else if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickLabelText)) { + } else if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickLabelText)) { return; // no need to redo labels, just reposition the ticks } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java index 96e65bef7..9e6b1ab00 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java @@ -4,6 +4,8 @@ import java.util.Collections; import java.util.List; +import io.fair_acc.chartfx.utils.PropUtil; +import io.fair_acc.dataset.events.ChartBits; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; import javafx.collections.FXCollections; @@ -64,19 +66,12 @@ public CategoryAxis(final ObservableList categories) { public CategoryAxis(final String axisLabel) { super(axisLabel); this.setOverlapPolicy(AxisLabelOverlapPolicy.SHIFT_ALT); - minProperty().addListener((ch, old, val) -> { - final double range = Math.abs(val.doubleValue() - CategoryAxis.this.getMax()); - final double rangeInt = (int) range; - final double scale = 0.5 / rangeInt; + PropUtil.runOnChange(() -> { + final double range = Math.abs(getMax() - getMin()); + final double scale = 0.5 / ((int) range); autoRangePaddingProperty().set(scale); - }); + }, minProperty(), maxProperty()); - maxProperty().addListener((ch, old, val) -> { - final double range = Math.abs(CategoryAxis.this.getMin() - val.doubleValue()); - final double rangeInt = (int) range; - final double scale = 0.5 / rangeInt; - autoRangePaddingProperty().set(scale); - }); } /** @@ -152,8 +147,6 @@ public String toString(Number object) { } }); categories.set(categoryList); - - requestAxisLayout(); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java index f78185de6..8d20668df 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java @@ -53,6 +53,11 @@ public boolean hasRunPreLayout() { } public LayoutHook registerOnce() { + // Called before proper initialization + if (preLayoutAction == null || postLayoutAndRemove == null) { + return this; + } + // Scene has changed -> remove the old one first if (registeredScene != null && registeredScene != node.getScene()) { unregister(); @@ -66,10 +71,6 @@ public LayoutHook registerOnce() { } private void runPreLayoutAndAdd() { - // Called before proper initialization - if (preLayoutAction == null) { - return; - } // We don't want to be in a position where the post layout listener // runs by itself, so we don't register until we made sure that the // pre-layout action ran before. From 125263b36d15a9938701e1be010e838e27622dd7 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 27 Jul 2023 23:59:33 +0200 Subject: [PATCH 42/81] changed renderer-axis assignment to happen before pre layout --- .../java/io/fair_acc/chartfx/XYChart.java | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 7a3e00b4b..98086d739 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -59,8 +59,6 @@ public class XYChart extends Chart { private final ObjectProperty polarStepSize = new SimpleObjectProperty<>(PolarTickStep.THIRTY); private final GridRenderer gridRenderer = new GridRenderer(); protected final ChangeListener gridLineVisibilitychange = (ob, o, n) -> requestLayout(); - private long lastCanvasUpdate; - private boolean callCanvasUpdateLater; /** * Construct a new XYChart with the given axes. @@ -345,6 +343,15 @@ protected List 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()) { @@ -352,46 +359,31 @@ protected void redrawCanvas() { } 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 drawnDataSets = renderer.render(gc, this, dataSetOffset, getDatasets()); dataSetOffset += drawnDataSets == null ? 0 : drawnDataSets.size(); } + // Top grid if (gridRenderer.isDrawOnTop()) { gridRenderer.render(gc, this, 0, null); } + if (DEBUG && LOGGER.isDebugEnabled()) { LOGGER.debug(" xychart redrawCanvas() - done"); } From 26037cdc63ab78d587210374e661c05364c83a2f Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 28 Jul 2023 00:02:02 +0200 Subject: [PATCH 43/81] added more layout hook checks and allow multiple occurences of a single listener --- .../fair_acc/chartfx/ui/utils/LayoutHook.java | 22 ++++++++++++++----- .../io/fair_acc/dataset/events/BitState.java | 10 ++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java index 8d20668df..46efe452c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java @@ -53,19 +53,26 @@ public boolean hasRunPreLayout() { } public LayoutHook registerOnce() { - // Called before proper initialization + // Potentially called before proper initialization if (preLayoutAction == null || postLayoutAndRemove == null) { return this; } + // Already registered or null, so nothing to do + var scene = node.getScene(); + if (scene == registeredScene) { + return this; + } + // Scene has changed -> remove the old one first - if (registeredScene != null && registeredScene != node.getScene()) { + if (registeredScene != null) { unregister(); } - // Register only if we haven't already registered - if (registeredScene == null && node.getScene() != null) { - registeredScene = node.getScene(); - registeredScene.addPreLayoutPulseListener(preLayoutAndAdd); + + // Register only if the scene is valid + if (scene != null) { + scene.addPreLayoutPulseListener(preLayoutAndAdd); + registeredScene = scene; } return this; } @@ -85,6 +92,9 @@ private void runPostLayoutAndRemove() { } private void unregister() { + if (registeredScene == null) { + return; + } registeredScene.removePreLayoutPulseListener(preLayoutAndAdd); registeredScene.removePostLayoutPulseListener(postLayoutAndRemove); registeredScene = null; diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 31841ee92..7d90b0865 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -181,6 +181,9 @@ public boolean removeInvalidateListener(StateListener listener) { return removeListener(invalidateListeners, listener); } + /** + * @return true if the last occurrence of the listener was removed + */ private static boolean removeListener(List list, StateListener listener) { if (list == null) { return false; @@ -189,13 +192,10 @@ private static boolean removeListener(List list, StateListener l for (int i = list.size() - 1; i >= 0; i--) { if (isEqual(list.get(i), listener)) { list.remove(i); - removed++; + return true; } } - if (removed > 1) { - throw new IllegalStateException("Can't remove targets that have been added more than once"); - } - return removed == 1; + return false; } private void notifyListeners(List list, int delta) { From 09875b8d2690b436e46cb8ec1b41e8d305512a45 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 28 Jul 2023 13:03:51 +0200 Subject: [PATCH 44/81] added architecture documentation --- docs/Architecture.md | 145 +++++++++++++++++++++++++++++++++++++++++++ docs/ThreadSafety.md | 2 +- 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 docs/Architecture.md diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 000000000..db9129583 --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,145 @@ +# ChartFX Architecture + +Below are some architectural considerations aimed at maintainers + +* Rendering Phases +* Concurrency +* Event System + +## Rendering Phases + +The JavaFX application thread (`FXAT`) is operating in on-demand pulses that work through phases: + +1) **Animations**: execution of animations and user actions added to [Platform::runLater](https://docs.oracle.com/javase/8/javafx/api/javafx/application/Platform.html#runLater-java.lang.Runnable-). This is where most of the user code lives. +2) **Pre-Layout Listener**: user actions added to [Scene::addPreLayoutPulseListener](https://docs.oracle.com/javase%2F9%2Fdocs%2Fapi%2F%2F/javafx/scene/Scene.html#addPreLayoutPulseListener-java.lang.Runnable-) +3) **CSS**: application of node styling +4) **Layout**: 'dirty' nodes are laid out and sizes get determined +5) **Post-Layout Listener**: user actions added to [Scene::addPostLayoutPulseListener](https://docs.oracle.com/javase%2F9%2Fdocs%2Fapi%2F%2F/javafx/scene/Scene.html#addPostLayoutPulseListener-java.lang.Runnable-) +6) **Bounds**: update of the bounds for all nodes +7) **Draw**: Node data gets copied to the Rendering thread where it gets rendered to screen. Note that JavaFX uses [retained mode](https://en.wikipedia.org/wiki/Retained_mode), so the application thread might specify a `line(x,y,width,length)`, and the rendering thread will later determine how the primitive maps to individual pixels on the screen. + +In order to guarantee a deterministic chart within a single pulse, ChartFX operates in the following way: + +* **Animations**: user code + * general user code / setup + * modification of state (datasets, plugins etc) +* **Pre-Layout Listener**: computes everything needed for the layout + * lock all datasets + * determine dataset range + * update legend + * update axis ranges + * update labels +* **Layout**: determines the size and placement of parts, e.g., + * axis ranges + * axis mappings from values to pixels + * axis tick marks + * axis label placement/visibility +* **Post-Layout Listener**: draws content according to the placement determined in the layout + * draw axes + * draw canvas + * draw plugins + * unlock locked datasets + +Users generally do not need to worry about the phases as most user code is handled in the first phase, but maintainers and low-level users should be careful not to change the SceneGraph in methods that get called by the post layout listener. + +## Concurrency + +All SceneGraph components (axes, plugins, charts) may only be modified on the FXAT. DataSets are the only part that may be modified concurrently from a background thread, and only while getting a `DataSet::lock()` write lock. + +All used datasets get locked and unlocked for the entire time from pre-layout to the drawing phases. The FXAT currently uses a reentrant (datasets can be added to multiple charts) read-lock (for compatibility), but it is allowed to do write operations such as clearing the event state described below. + +Parallel processing from the FXAT while the datasets are locked (e.g. parallel point reduction) does not require any additional locking of the datasets. The FXAT waits for operations to finish, so it does not make any progress and therefore can't run into race conditions. + +## Event System + +The event system is based on [bit masks](https://en.wikipedia.org/wiki/Mask_(computing)) where each bit corresponds to a specific part of the chart. Setting a bit dirties the state and registers the draw handlers. Setting the same bit multiple times generally has no additional effect. The state bits get cleared once the drawing is finished. Individual steps may be skipped if the state indicates that nothing has changed. + +Listeners can aggregate state from multiple sources and filter bits that they are interested in. Similar to JavaFX properties, each element can subscribe to others via change-listeners (called if specified bits change from 0 to 1) or invalidation-listeners (called on every event). However, it is generally recommended to only use change-listeners. + +Example for an axis that needs to trigger a layout on a property change + +```Java +// create an axis state object that knows only about axis events +var state = BitState.initDirty(this, ChartBits.AxisMask); + +// changes to the axis padding need to recompute the layout and redraw the canvas +// (the set method has the same signature as a JavaFX listener, but does not require a dependency) +axisPadding.addListener(state.onPropChange(ChartBits.AxisLayout, ChartBits.AxisCanvas)::set); + +// trigger JavaFX layouts +state.addChangeListener(ChartBits.AxisLayout, (src, bits) -> requestLayout()); +``` + +Example for a chart that aggregates the state from one or more axes + +```Java +// create a chart state object that knows about all events +var state = BitState.initDirty(this, ChartBits.AxisMask); + +// merge changes coming from a relevant axis +axis.getBitState().addChangeListener(state); + +// trigger a redraw if any axis needs to be drawn +state.addChangeListener(ChartBits.AxisCanvas, (src, bits) -> drawAxesInNextCycle()); + +// remove an axis that is no longer part of this chart +axis.getBitState().removeChangeListener(state); +``` + +Example for skipping the draw step if none of the axes has changed + +```Java +redrawAxes() { + if (state.isClean(ChartBits.AxisCanvas) { + return; // all content is still good + } + for (var axis : getAxes) { + axis.redraw(); + } +} +``` + +Updates use bitwise operations and batch updates automatically, so the process is very efficient. For example 10 data sets changing 100 values will get merged into 10 dataset events that are then merged into a single chart event. + +State bits may only be modified from the FXAT, or in the dataset case from a concurrent thread that holds a write lock. Since dataset updates may come from other threads, the Chart internally keeps a second thread-safe (using CAS operations) accumulation state for datasets, e.g., + +```Java +var state = BitState.initDirty(this); +var dataSetState = BitState.initDirtyMultiThreaded(this, ChartBits.DataSetMask) + .addChangeListener((src, bits) -> { + if(Platform.isFxApplicationThread()) { + // Forward immediately as is + state.accept(src, bits); + } else { + // May be deferred until the bits are already outdated, so set actual state + Platform.runLater(() -> state.accept(src, src.getBits())); + } + }); +``` + +In some cases it can be difficult to find what triggered events, so there are additional debugging tools that provide stack trace information + +```Java +// Print every time something needs the canvas content to be updated +state.addInvalidateListener(ChartBits.AxisCanvas, ChartBits.printerWithStackTrace()); +``` + +For example, the screenshot below shows a redraw request triggered by a tick unit change `[15] setTickUnit`. IntelliJ parses the output similar to an exception stack trace and provides clickable links to the lines. + +![image](https://github.com/fair-acc/chart-fx/assets/5491587/b0666f90-d990-4b4e-a1a3-a263a8fab63b) + +At the low-level the bit masks use `int` and `IntSupplier`, so users could also create custom elements with custom events, e.g., + +```Java +enum CustomEvents implements IntSupplier { + SomeBit; + int getAsInt() { + return bit; + } + final static int OFFSET = ChartEvents.values().length; + final int bit = 1 << (OFFSET + ordinal()); +} + +state.setDirty(CustomEvents.SomeBit); +``` + diff --git a/docs/ThreadSafety.md b/docs/ThreadSafety.md index 4f41a5180..b0bb11317 100644 --- a/docs/ThreadSafety.md +++ b/docs/ThreadSafety.md @@ -1,4 +1,4 @@ -# Thread-Safety and Concurrency in ChartFX +# Thread-Safety and Concurrency in ChartFX (outdated) Large data sets are typically -- or better 'should be' -- primarily updated in separate data-acquisition or processing threads in order not to block or overload JavaFX's main rendering thread. The `[DataSet](chartfx-dataset/src/main/java/io/fair_acc/dataset/DataSet.java)` interface thus deploys for this purpose a Read-Write-Lock pair (interface: `[DataSetLock](master/chartfx-dataset/src/main/java/io/fair_acc/dataset/locks/DataSetLock.java)`) that facilitates the thread-safety of the UI and non-UI parts of the ChartFx library. From 2bc955cfe4b2a028203bb4f0639c5f727b68050f Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 28 Jul 2023 15:14:27 +0200 Subject: [PATCH 45/81] removed some unnecessary atomic / concurrent objects --- chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java | 3 +-- .../io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java | 1 - .../src/main/java/io/fair_acc/chartfx/plugins/Zoomer.java | 6 +++--- .../io/fair_acc/dataset/spi/DefaultAxisDescription.java | 1 - .../fair_acc/dataset/utils/CachedDaemonThreadFactory.java | 6 +++--- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 5c27a74c0..4fb521559 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -2,7 +2,6 @@ import java.security.InvalidParameterException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -114,7 +113,7 @@ public abstract class Chart extends Region implements EventSource { private final StyleableBooleanProperty legendVisible = CSS.createBooleanProperty(this, "legendVisible", true, state.onAction(ChartBits.ChartLegend)); protected final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>()); - private final Map pluginGroups = new ConcurrentHashMap<>(); + private final Map pluginGroups = new HashMap<>(); private final ObservableList plugins = FXCollections.observableList(new LinkedList<>()); private final ObservableList datasets = FXCollections.observableArrayList(); protected final ObservableList allDataSets = FXCollections.observableArrayList(); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 7d401147a..0d1b9c578 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -4,7 +4,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; import io.fair_acc.chartfx.ui.css.PathStyle; import io.fair_acc.chartfx.ui.css.StyleUtil; diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/Zoomer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/Zoomer.java index 4512f8a12..bcc0f46c1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/Zoomer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/Zoomer.java @@ -1,10 +1,10 @@ package io.fair_acc.chartfx.plugins; import java.util.ArrayDeque; +import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import javafx.animation.KeyFrame; @@ -749,7 +749,7 @@ private void clearZoomStackIfAxisAutoRangingIsEnabled() { } private Map getZoomDataWindows() { - ConcurrentHashMap axisStateMap = new ConcurrentHashMap<>(); + Map axisStateMap = new HashMap<>(); if (getChart() == null) { return axisStateMap; } @@ -979,7 +979,7 @@ private void pushCurrentZoomWindows() { if (getChart() == null) { return; } - ConcurrentHashMap axisStateMap = new ConcurrentHashMap<>(); + Map axisStateMap = new HashMap<>(); for (Axis axis : getChart().getAxes()) { switch (getAxisMode()) { case X: diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java index 25b512cb4..de765665e 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DefaultAxisDescription.java @@ -4,7 +4,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; import io.fair_acc.dataset.AxisDescription; import io.fair_acc.dataset.events.BitState; diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/CachedDaemonThreadFactory.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/CachedDaemonThreadFactory.java index 6037c7392..484fa84d6 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/CachedDaemonThreadFactory.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/utils/CachedDaemonThreadFactory.java @@ -10,7 +10,7 @@ public final class CachedDaemonThreadFactory implements ThreadFactory { private static final ThreadFactory DEFAULT_FACTORY = Executors.defaultThreadFactory(); private static final CachedDaemonThreadFactory SELF = new CachedDaemonThreadFactory(); private static final ExecutorService COMMON_POOL = Executors.newFixedThreadPool(2 * MAX_THREADS, SELF); - private static final AtomicInteger TREAD_COUNTER = new AtomicInteger(); + private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); private CachedDaemonThreadFactory() { // helper class @@ -19,8 +19,8 @@ private CachedDaemonThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = DEFAULT_FACTORY.newThread(r); - TREAD_COUNTER.incrementAndGet(); - thread.setName("daemonised_chartfx_thread_#" + TREAD_COUNTER.intValue()); + THREAD_COUNTER.incrementAndGet(); + thread.setName("daemonised_chartfx_thread_#" + THREAD_COUNTER.intValue()); thread.setDaemon(true); return thread; } From c9ac1d3e90da6418ac1b35a64408a7059c0ab9db Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 29 Jul 2023 21:30:46 +0200 Subject: [PATCH 46/81] changed contour renderer to add the extra color bar before the layout --- .../src/main/java/io/fair_acc/chartfx/Chart.java | 13 +++++++++++++ .../io/fair_acc/chartfx/plugins/ChartPlugin.java | 14 ++++++++++++++ .../io/fair_acc/chartfx/renderer/Renderer.java | 7 +++++++ .../renderer/spi/ContourDataSetRenderer.java | 6 +++++- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 4fb521559..2ff957305 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -582,6 +582,15 @@ protected void runPreLayout() { final long start = ProcessingProfiler.getTimeStamp(); updateAxisRange(); // Update data ranges etc. to trigger anything that might need a layout ProcessingProfiler.getTimeDiff(start, "updateAxisRange()"); + + for (Renderer renderer : renderers) { + renderer.runPreLayout(); + } + + for (ChartPlugin plugin : plugins) { + plugin.runPreLayout(); + } + } private List lockedDataSets = new ArrayList<>(); @@ -615,6 +624,10 @@ protected void runPostLayout() { axis.drawAxis(); } redrawCanvas(); + for (ChartPlugin plugin : plugins) { + plugin.runPostLayout(); + } + dataSetState.clear(); state.clear(); for (var ds : lockedDataSets) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/ChartPlugin.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/ChartPlugin.java index c2fddec93..ed238b136 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/ChartPlugin.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/ChartPlugin.java @@ -186,6 +186,13 @@ public final boolean isAddButtonsToToolBar() { return addButtonsToToolBarProperty().get(); } + /** + * Optional method that allows plug-in make layout changes after axes and dataset limits are known + */ + public void runPreLayout() { // #NOPMD + // empty by default + } + /** * Optional method that allows the plug-in to react in case the size of the chart that it belongs to has changed. */ @@ -193,6 +200,13 @@ public void layoutChildren() { // #NOPMD // empty by default } + /** + * Optional method that allows plug-in render something after axes and charts have been rendered + */ + public void runPostLayout() { // #NOPMD + // empty by default + } + /** * Registers event handlers that should be added to the {@code XYChartPane} node when the plugin is added to the * pane and are removed once the plugin is removed from the pane. diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java index b9804eab8..7fe049617 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java @@ -36,6 +36,13 @@ public interface Renderer { ObservableList getDatasetsCopy(); + /** + * Optional method that allows the renderer make layout changes after axes and dataset limits are known + */ + default void runPreLayout() { // #NOPMD + // empty by default + } + /** * * @param gc the Canvas' GraphicsContext the renderer should draw upon diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java index 4f0a227b6..cfdd5a38b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRenderer.java @@ -452,6 +452,11 @@ private void paintCanvas(final GraphicsContext gc) { } } + @Override + public void runPreLayout() { + layoutZAxis(getZAxis()); + } + @Override public List render(final GraphicsContext gc, final Chart chart, final int dataSetOffset, final ObservableList datasets) { @@ -486,7 +491,6 @@ public List render(final GraphicsContext gc, final Chart chart, final i localCache = new ContourDataSetCache(xyChart, this, dataSet); // NOPMD ProcessingProfiler.getTimeDiff(stop, "updateCachedVariables"); - layoutZAxis(getZAxis()); // data reduction algorithm here paintCanvas(gc); drawnDataSet.add(dataSet); From b53658f778e5b5832dccce731334bcf813201168 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 31 Jul 2023 16:41:18 +0200 Subject: [PATCH 47/81] split data updates into added/removed again --- .../io/fair_acc/dataset/events/BitState.java | 7 ++++++- .../io/fair_acc/dataset/events/ChartBits.java | 13 +++++++++++-- .../fair_acc/dataset/spi/AbstractHistogram.java | 1 - .../fair_acc/dataset/spi/AveragingDataSet.java | 4 +--- .../dataset/spi/CircularDoubleErrorDataSet.java | 9 +++------ .../dataset/spi/DimReductionDataSet.java | 2 +- .../io/fair_acc/dataset/spi/DoubleDataSet.java | 15 ++++++--------- .../dataset/spi/DoubleErrorDataSet.java | 17 +++++++---------- .../fair_acc/dataset/spi/DoubleGridDataSet.java | 1 - .../dataset/spi/FifoDoubleErrorDataSet.java | 11 ++++------- .../io/fair_acc/dataset/spi/FloatDataSet.java | 15 ++++++--------- .../fair_acc/dataset/spi/FragmentedDataSet.java | 6 ++---- .../java/io/fair_acc/dataset/spi/Histogram.java | 6 ++---- .../io/fair_acc/dataset/spi/Histogram2.java | 1 - .../dataset/spi/LabelledMarkerDataSet.java | 9 +++------ .../dataset/spi/LimitedIndexedTreeDataSet.java | 13 +++++-------- .../dataset/spi/MultiDimDoubleDataSet.java | 15 ++++++--------- .../io/fair_acc/dataset/spi/RollingDataSet.java | 4 +--- .../io/fair_acc/dataset/spi/WrappedDataSet.java | 2 -- .../dataset/spi/DimReductionDataSetTests.java | 1 - .../sample/chart/utils/TestDataSetSource.java | 5 ++--- .../sample/dataset/legacy/DoubleDataSet.java | 11 ++++------- .../dataset/legacy/DoubleErrorDataSet.java | 11 ++++------- .../service/SimpleOhlcvReplayDataSet.java | 1 - .../service/order/PositionFinancialDataSet.java | 2 +- .../fair_acc/sample/math/TSpectrumSample.java | 1 - 26 files changed, 75 insertions(+), 108 deletions(-) diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 7d90b0865..02105557e 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -43,6 +43,11 @@ public static int mask(IntSupplier[] bits){ return mask; } + public static IntSupplier maskSupplier(IntSupplier bit0, IntSupplier... more) { + int mask = mask(bit0, more); + return () -> mask; + } + public static int mask(IntSupplier bit0, IntSupplier... more) { int mask = bit0.getAsInt(); for (var bit : more) { @@ -296,7 +301,7 @@ public static StringBuilder appendStackTrace(StringBuilder builder, int from, in // Default to hide stack trace lines that are inside the printer. Keep updated. private static final int DEFAULT_MIN_STACK_TRACE = 6; - private static final int DEFAULT_MAX_STACK_TRACE = 20; + private static final int DEFAULT_MAX_STACK_TRACE = 25; private static class FilteredListener implements StateListener { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java index 7d790f534..6630aea9a 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/ChartBits.java @@ -23,7 +23,8 @@ public enum ChartBits implements IntSupplier { ChartPlugins, ChartPluginState, DataSetVisibility, - DataSetData, + DataSetDataAdded, + DataSetDataRemoved, DataSetRange, DataSetName, DataSetMetaData, @@ -34,7 +35,9 @@ public enum ChartBits implements IntSupplier { private static final ChartBits[] AllBits = ChartBits.values(); public static final int KnownMask = BitState.mask(AllBits); public static final int AxisMask = BitState.mask(AxisLayout, AxisCanvas, AxisRange, AxisTickLabelText, AxisLabelText); - public static final int DataSetMask = BitState.mask(ChartDataSets, DataSetVisibility, DataSetData, DataSetRange, DataSetMetaData, DataSetPermutation); + + public static final IntSupplier DataSetData = BitState.maskSupplier(DataSetDataAdded, DataSetDataRemoved); // any data update + public static final int DataSetMask = BitState.mask(DataSetData, ChartDataSets, DataSetVisibility, DataSetRange, DataSetMetaData, DataSetPermutation); public static StateListener printer() { return PRINTER; @@ -58,4 +61,10 @@ public boolean isSet(int mask) { final int bit = 1 << ordinal(); + static { + if (ChartBits.AllBits.length > 32) { + throw new AssertionError("The int32 based event system can only support 32 different types"); + } + } + } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java index c2ee88ce3..11fc689fc 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AbstractHistogram.java @@ -4,7 +4,6 @@ import java.util.Arrays; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java index 42ee0cdcc..baef48ab8 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/AveragingDataSet.java @@ -3,8 +3,6 @@ import java.util.ArrayDeque; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.events.ChartBits; @@ -69,7 +67,7 @@ public void add(DataSet ds) { } dataset.recomputeLimits(DIM_X); dataset.recomputeLimits(DIM_Y); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); } /** diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java index 893950acf..9917415ec 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSet.java @@ -1,9 +1,6 @@ package io.fair_acc.dataset.spi; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.utils.CircularBuffer; @@ -95,7 +92,7 @@ public CircularDoubleErrorDataSet add(final double x, final double y, final doub getAxisDescription(DIM_Y).clear(); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -157,7 +154,7 @@ public CircularDoubleErrorDataSet add(final double[] xVals, final double[] yVals getAxisDescription(DIM_Y).clear(); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -227,7 +224,7 @@ public CircularDoubleErrorDataSet reset() { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java index f0e1d1af9..4ce9ecdc9 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DimReductionDataSet.java @@ -116,7 +116,7 @@ public void handle(UpdateEvent event) { } })); - this.fireInvalidated(ChartBits.DataSetData); + this.fireInvalidated(ChartBits.DataSetDataAdded); } public void setMaxValue(final double val) { diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java index d23e1819f..b28eb937e 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleDataSet.java @@ -1,9 +1,6 @@ package io.fair_acc.dataset.spi; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; @@ -113,7 +110,7 @@ public DoubleDataSet add(final double x, final double y, final String label) { getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -140,7 +137,7 @@ public DoubleDataSet add(final double[] xValuesNew, final double[] yValuesNew) { getAxisDescription(DIM_Y).add(yValuesNew); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -188,7 +185,7 @@ public DoubleDataSet add(final int index, final double x, final double y, final getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -215,7 +212,7 @@ public DoubleDataSet add(final int index, final double[] x, final double[] y) { getDataLabelMap().shiftKeys(indexAt, xValues.size()); getDataStyleMap().shiftKeys(indexAt, xValues.size()); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -234,7 +231,7 @@ public DoubleDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -307,7 +304,7 @@ public DoubleDataSet remove(final int fromIndex, final int toIndex) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java index 1dc25d4b6..15238b7d5 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleErrorDataSet.java @@ -1,9 +1,6 @@ package io.fair_acc.dataset.spi; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; @@ -138,7 +135,7 @@ public DoubleErrorDataSet add(final double x, final double y, final double yErro getAxisDescription(DIM_Y).add(y - yErrorNeg); getAxisDescription(DIM_Y).add(y + yErrorPos); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -171,7 +168,7 @@ public DoubleErrorDataSet add(final double[] xValuesNew, final double[] yValuesN getAxisDescription(DIM_X).add(xValuesNew); getAxisDescription(DIM_Y).add(yValuesNew); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -241,7 +238,7 @@ public DoubleErrorDataSet add(final int index, final double x, final double y, f getAxisDescription(DIM_Y).add(y - yErrorNeg); getAxisDescription(DIM_Y).add(y + yErrorPos); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -279,7 +276,7 @@ public DoubleErrorDataSet add(final int index, final double[] x, final double[] getDataStyleMap().shiftKeys(indexAt, xValues.size()); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -300,7 +297,7 @@ public DoubleErrorDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -389,7 +386,7 @@ public DoubleErrorDataSet remove(final int fromIndex, final int toIndex) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -627,7 +624,7 @@ public DoubleErrorDataSet trim() { yErrorsPos.trim(0); yErrorsNeg.trim(0); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java index 0032c4d1b..23c3ac3f2 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/DoubleGridDataSet.java @@ -3,7 +3,6 @@ import java.util.Arrays; import java.util.stream.IntStream; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet3D; import io.fair_acc.dataset.GridDataSet; diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java index 2262fff39..b6408ed20 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FifoDoubleErrorDataSet.java @@ -4,9 +4,6 @@ import java.util.List; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.DoublePointError; import io.fair_acc.dataset.utils.AssertUtils; @@ -107,7 +104,7 @@ public FifoDoubleErrorDataSet add(final double x, final double y, final double y // remove old fields if necessary expire(x); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return this; } @@ -151,7 +148,7 @@ public FifoDoubleErrorDataSet add(final double[] xValues, final double[] yValues this.add(xValues[i], yValues[i], yErrorsNeg[i], yErrorsPos[i]); } }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return this; } @@ -181,7 +178,7 @@ public int expire(final double now) { return toRemoveList.size(); }); if (dataPointsToRemove != 0) { - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); } return dataPointsToRemove; } @@ -235,7 +232,7 @@ public String getStyle(final int index) { */ public void reset() { data.clear(); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); } /** diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java index 6ac2f456b..1ab9e7346 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FloatDataSet.java @@ -1,9 +1,6 @@ package io.fair_acc.dataset.spi; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.MathUtils; import io.fair_acc.dataset.utils.AssertUtils; @@ -115,7 +112,7 @@ public FloatDataSet add(final float x, final float y, final String label) { getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -145,7 +142,7 @@ public FloatDataSet add(final float[] xValuesNew, final float[] yValuesNew) { getAxisDescription(DIM_Y).add(v); } }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -193,7 +190,7 @@ public FloatDataSet add(final int index, final float x, final float y, final Str getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -223,7 +220,7 @@ public FloatDataSet add(final int index, final float[] x, final float[] y) { getDataLabelMap().shiftKeys(indexAt, xValues.size()); getDataStyleMap().shiftKeys(indexAt, xValues.size()); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -242,7 +239,7 @@ public FloatDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -311,7 +308,7 @@ public FloatDataSet remove(final int fromIndex, final int toIndex) { // -> fireInvalidated calls computeLimits for autoNotification this.getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java index 9dafbbb88..6a6a6ea12 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/FragmentedDataSet.java @@ -4,8 +4,6 @@ import java.util.Collection; import java.util.Comparator; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet2D; import io.fair_acc.dataset.events.ChartBits; @@ -39,7 +37,7 @@ public void add(final DataSet set) { getAxisDescription(DIM_Y).add(set.getAxisDescription(DIM_Y).getMax()); getAxisDescription(DIM_Y).add(set.getAxisDescription(DIM_Y).getMin()); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); } /** @@ -64,7 +62,7 @@ public void clear() { lock().writeLockGuard(() -> { dataCount = 0; list.clear(); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); }); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java index c8913b142..296d5d781 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram.java @@ -7,8 +7,6 @@ import io.fair_acc.dataset.DataSet2D; import io.fair_acc.dataset.DataSetMetaData; import io.fair_acc.dataset.Histogram1D; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; @@ -100,7 +98,7 @@ public int fill(final double x, final double w) { addBinContent(bin, w); return bin; }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return retVal; } @@ -111,7 +109,7 @@ public void fillN(double[] x, double[] w, int stepSize) { this.fill(x[i], w[i]); } }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); } @Override diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java index 41fbc8313..e4d561af0 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/Histogram2.java @@ -8,7 +8,6 @@ import io.fair_acc.dataset.DataSetMetaData; import io.fair_acc.dataset.Histogram1D; import io.fair_acc.dataset.Histogram2D; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java index 539bc404a..63c2e36f0 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LabelledMarkerDataSet.java @@ -13,9 +13,6 @@ import java.util.List; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.utils.DoublePoint; import io.fair_acc.dataset.utils.AssertUtils; @@ -52,7 +49,7 @@ public LabelledMarkerDataSet add(LabelledMarker marker) { dataLabels.add(marker.getLabel()); dataStyles.add(marker.getStyle()); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -67,7 +64,7 @@ public LabelledMarkerDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -150,7 +147,7 @@ public LabelledMarkerDataSet remove(final int fromIndex, final int toIndex) { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java index acad86a22..047a0a776 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/LimitedIndexedTreeDataSet.java @@ -5,9 +5,6 @@ import java.util.NoSuchElementException; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.utils.trees.IndexedNavigableSet; @@ -99,7 +96,7 @@ public LimitedIndexedTreeDataSet add(final double x, final double y, final doubl getAxisDescription(DIM_Y).add(y + ey); expire(); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -149,7 +146,7 @@ public LimitedIndexedTreeDataSet add(final double[] xValues, final double[] yVal } expire(); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -193,7 +190,7 @@ public LimitedIndexedTreeDataSet clearData() { data.clear(); getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -341,7 +338,7 @@ public LimitedIndexedTreeDataSet remove(final int fromIndex, final int toIndex) getAxisDescription(DIM_X).setMax(Double.NaN); getAxisDescription(DIM_Y).setMax(Double.NaN); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -367,7 +364,7 @@ public LimitedIndexedTreeDataSet remove(final int[] indices) { // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java index 89032e9d0..1e0e18245 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSet.java @@ -1,9 +1,6 @@ package io.fair_acc.dataset.spi; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; @@ -130,7 +127,7 @@ public MultiDimDoubleDataSet add(final double[] newValues, final String label) { addDataLabel(this.values[0].size() - 1, label); } }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -154,7 +151,7 @@ public MultiDimDoubleDataSet add(final double[][] valuesNew) { } }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -192,7 +189,7 @@ public MultiDimDoubleDataSet add(final int index, final double[] newValues, fina getDataLabelMap().addValueAndShiftKeys(indexAt, this.values[0].size(), label); getDataStyleMap().shiftKeys(indexAt, this.values[0].size()); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -220,7 +217,7 @@ public MultiDimDoubleDataSet add(final int index, final double[][] newValues) { getDataLabelMap().shiftKeys(indexAt, this.values[0].size()); getDataStyleMap().shiftKeys(indexAt, this.values[0].size()); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -240,7 +237,7 @@ public MultiDimDoubleDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -314,7 +311,7 @@ public MultiDimDoubleDataSet remove(final int fromIndex, final int toIndex) { // -> fireInvalidated calls computeLimits for autoNotification getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java index 1e9e06b2f..33c1c3dd7 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/RollingDataSet.java @@ -1,8 +1,6 @@ package io.fair_acc.dataset.spi; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.events.ChartBits; @@ -35,7 +33,7 @@ public void add(final DataSet set) { lastLength = set.getAxisDescription(DIM_X).getMax(); // invalidate ranges getAxisDescriptions().forEach(AxisDescription::clear); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); } /** diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java index bddeccf1b..3746bd1ed 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/WrappedDataSet.java @@ -4,8 +4,6 @@ import java.util.List; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.EventListener; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.events.ChartBits; diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java index 321e76603..1a34c405c 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java @@ -9,7 +9,6 @@ import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.GridDataSet; import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.UpdateEvent; import io.fair_acc.dataset.events.ChartBits; import org.awaitility.Awaitility; import org.junit.jupiter.api.Assertions; diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java index 1b216657f..c5d3ecae9 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/utils/TestDataSetSource.java @@ -19,7 +19,6 @@ import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.GridDataSet; -import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.spi.AbstractDataSet; import io.fair_acc.dataset.utils.ByteArrayCache; import io.fair_acc.dataset.utils.DoubleCircularBuffer; @@ -110,7 +109,7 @@ protected void openLineIn() { public void fillTestData() { lock().writeLockGuard( () -> synth.decode(history[2].elements(), frameSize, updatePeriod, samplingRate, N_SYNTHESISER_BITS)); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); } @Override @@ -166,7 +165,7 @@ public void pause() { public void reset() { synth.reset(); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); } public void setFrameCount(int frameCount) { diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java index 62a594315..93d238d2d 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleDataSet.java @@ -3,9 +3,6 @@ import java.util.Arrays; import io.fair_acc.dataset.AxisDescription; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.DataSet; @@ -166,7 +163,7 @@ public DoubleDataSet add(final double[] xValuesNew, final double[] yValuesNew) { recomputeLimits(DIM_X); recomputeLimits(DIM_Y); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -238,7 +235,7 @@ public DoubleDataSet add(final int index, final double x, final double y, final getAxisDescription(DIM_X).add(x); getAxisDescription(DIM_Y).add(y); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -257,7 +254,7 @@ public DoubleDataSet clearData() { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -335,7 +332,7 @@ public DoubleDataSet remove(final int fromIndex, final int toIndex) { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java index de1c5899b..abe2f69ac 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/dataset/legacy/DoubleErrorDataSet.java @@ -3,9 +3,6 @@ import java.util.Arrays; import io.fair_acc.dataset.*; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.utils.AssertUtils; import io.fair_acc.dataset.spi.AbstractErrorDataSet; @@ -163,7 +160,7 @@ public DoubleErrorDataSet add(final double x, final double y, final double yErro getAxisDescription(DIM_Y).add(y - yErrorNeg); getAxisDescription(DIM_Y).add(y + yErrorPos); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -219,7 +216,7 @@ public DoubleErrorDataSet add(final double[] xValues, final double[] yValues, fi recomputeLimits(DIM_X); recomputeLimits(DIM_Y); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); return getThis(); } @@ -246,7 +243,7 @@ public DoubleErrorDataSet clearData() { getAxisDescription(DIM_X).clear(); getAxisDescription(DIM_Y).clear(); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } @@ -332,7 +329,7 @@ public DoubleErrorDataSet remove(final int fromIndex, final int toIndex) { getAxisDescriptions().forEach(AxisDescription::clear); }); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataRemoved); return getThis(); } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java index 96eaf9721..12a3212bb 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/SimpleOhlcvReplayDataSet.java @@ -18,7 +18,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fair_acc.dataset.event.AddedDataEvent; import io.fair_acc.dataset.spi.financial.OhlcvDataSet; import io.fair_acc.dataset.spi.financial.api.attrs.AttributeModelAware; import io.fair_acc.dataset.spi.financial.api.ohlcv.IOhlcvItem; diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java index 86c034666..b7b471bcb 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/service/order/PositionFinancialDataSet.java @@ -180,7 +180,7 @@ public void orderFilled(OrderEvent event) { // update internal memories Order order = event.getOrder(); includePosition(order.isExitOrder() ? order.getExitOfPosition() : order.getEntryOfPosition(), false); - fireInvalidated(ChartBits.DataSetData); + fireInvalidated(ChartBits.DataSetDataAdded); } @Override diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java index 359a01350..a0015a502 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/math/TSpectrumSample.java @@ -35,7 +35,6 @@ import io.fair_acc.chartfx.renderer.spi.ErrorDataSetRenderer; import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSet2D; -import io.fair_acc.dataset.event.UpdatedDataEvent; import io.fair_acc.dataset.spi.DoubleDataSet; import io.fair_acc.dataset.spi.utils.DoublePoint; import io.fair_acc.math.ArrayMath; From e6684c45efaca7f4082992092fb7b7b417969ce8 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 31 Jul 2023 17:12:11 +0200 Subject: [PATCH 48/81] migrated math data set and tests --- .../java/io/fair_acc/math/MathDataSet.java | 25 ++++++----- .../io/fair_acc/math/MathDataSetTests.java | 45 +++++-------------- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java b/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java index 28d592b91..c21db6cf2 100644 --- a/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java +++ b/chartfx-math/src/main/java/io/fair_acc/math/MathDataSet.java @@ -9,12 +9,8 @@ import io.fair_acc.dataset.DataSet; import io.fair_acc.dataset.DataSetError; -import io.fair_acc.dataset.event.AddedDataEvent; -import io.fair_acc.dataset.event.EventRateLimiter; import io.fair_acc.dataset.event.EventRateLimiter.UpdateStrategy; -import io.fair_acc.dataset.event.RemovedDataEvent; -import io.fair_acc.dataset.event.UpdateEvent; -import io.fair_acc.dataset.event.UpdatedDataEvent; +import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.spi.DoubleErrorDataSet; @@ -29,6 +25,7 @@ public class MathDataSet extends DoubleErrorDataSet { private static final long serialVersionUID = -4978160822533565009L; private static final long DEFAULT_UPDATE_LIMIT = 40; private final transient List sourceDataSets; + private final transient BitState dataSourceState = BitState.initDirtyMultiThreaded(this, ChartBits.DataSetMask); private final transient DataSetFunction dataSetFunction; private final transient DataSetsFunction dataSetsFunction; private final transient DataSetValueFunction dataSetValueFunction; @@ -132,15 +129,19 @@ protected MathDataSet(final String transformName, DataSetFunction dataSetFunctio // the 'DataSetFunction' interface } - // TODO: when should the updates happen? Should this be multithreaded? on invalidate or change? - getBitState().addInvalidateListener(ChartBits.DataSetData, (obj, bits) -> update()); + // TODO: the updates currently get computed on the change listener thread. When should this happen concurrently? + // TODO: maybe trigger the update from the chart preLayout? + dataSourceState.addChangeListener((obj, bits) -> update()); // exceptionally call handler during DataSet creation registerListener(); // NOPMD + + // call handler for initial constructor update + update(); } public final void deregisterListener() { - sourceDataSets.forEach(srcDataSet -> srcDataSet.removeListener(this)); + sourceDataSets.forEach(srcDataSet -> srcDataSet.getBitState().removeInvalidateListener(dataSourceState)); } public final List getSourceDataSets() { @@ -148,7 +149,7 @@ public final List getSourceDataSets() { } public final void registerListener() { - sourceDataSets.forEach(srcDataSet -> srcDataSet.addListener(this)); + sourceDataSets.forEach(srcDataSet -> srcDataSet.getBitState().addInvalidateListener(dataSourceState)); } private void handleDataSetValueFunctionInterface() { @@ -183,6 +184,10 @@ private void handleDataSetValueFunctionInterface() { protected void update() { this.lock().writeLockGuard(() -> { + if (dataSourceState.isClean()) { + return; + } + dataSourceState.clear(); if (dataSetFunction != null) { set(dataSetFunction.transform(sourceDataSets.get(0))); } else if (dataSetsFunction != null) { @@ -195,8 +200,8 @@ protected void update() { } this.setName(getCompositeDataSetName(transformName, sourceDataSets.toArray(new DataSet[0]))); + // Note: the data bit is already invalidated at the storing data set level }); - fireInvalidated(ChartBits.DataSetData); } protected static String getCompositeDataSetName(final String transformName, final DataSet... sources) { diff --git a/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java b/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java index 2619e706a..c961f13f7 100644 --- a/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java +++ b/chartfx-math/src/test/java/io/fair_acc/math/MathDataSetTests.java @@ -7,6 +7,7 @@ import java.util.concurrent.atomic.AtomicInteger; +import io.fair_acc.dataset.events.ChartBits; import org.junit.jupiter.api.Test; import io.fair_acc.dataset.DataSet; @@ -138,65 +139,43 @@ public void testNotifies() { System.arraycopy(input, 0, output, 0, length); }, -1, null, rawDataSetRef); assertArrayEquals(rawDataSetRef.getValues(DataSet.DIM_Y), identityDataSet.getValues(DataSet.DIM_Y)); - identityDataSet.addListener(evt -> counter2.incrementAndGet()); + identityDataSet.getBitState().addInvalidateListener(ChartBits.DataSetData, + (src, bits) -> counter2.incrementAndGet()); // has been initialised once during construction assertEquals(1, counter1.get()); assertEquals(0, counter2.get()); counter1.set(0); - // null does not invoke update - rawDataSetRef.invokeListener(null, false); - assertEquals(0, counter1.get()); - assertEquals(0, counter2.get()); - - // null does not invoke update - rawDataSetRef.invokeListener(new UpdateEvent(rawDataSetRef, "wrong event"), false); - assertEquals(0, counter1.get()); - assertEquals(0, counter2.get()); - - // null does not invoke update - rawDataSetRef.invokeListener(new UpdateEvent(identityDataSet, "wrong reference", false)); + // wrong event does not invoke update + rawDataSetRef.fireInvalidated(ChartBits.ChartLegend); assertEquals(0, counter1.get()); assertEquals(0, counter2.get()); // AddedDataEvent does invoke update - rawDataSetRef.invokeListener(new AddedDataEvent(rawDataSetRef, "OK reference", false)); + rawDataSetRef.fireInvalidated(ChartBits.DataSetDataAdded); assertEquals(1, counter1.get()); assertEquals(1, counter2.get()); // RemovedDataEvent does invoke update - rawDataSetRef.invokeListener(new RemovedDataEvent(rawDataSetRef, "OK reference", false)); + rawDataSetRef.fireInvalidated(ChartBits.DataSetDataRemoved); assertEquals(2, counter1.get()); assertEquals(2, counter2.get()); - rawDataSetRef.invokeListener(new RemovedDataEvent(identityDataSet, "wrong reference", false)); + + rawDataSetRef.fireInvalidated(ChartBits.DataSetDataRemoved); assertEquals(3, counter1.get()); assertEquals(3, counter2.get()); // UpdatedDataEvent does invoke update - rawDataSetRef.invokeListener(new UpdatedDataEvent(rawDataSetRef, "OK reference", false)); + rawDataSetRef.fireInvalidated(ChartBits.DataSetData); assertEquals(4, counter1.get()); assertEquals(4, counter2.get()); - assertEquals(1, rawDataSetRef.getBitState().size()); - identityDataSet.deregisterListener(); - assertEquals(0, rawDataSetRef.getBitState().size()); - rawDataSetRef.invokeListener(new UpdatedDataEvent(rawDataSetRef, "OK reference", false)); - assertEquals(4, counter1.get()); - assertEquals(4, counter2.get()); - - identityDataSet.registerListener(); - rawDataSetRef.invokeListener(new UpdatedDataEvent(rawDataSetRef, "OK reference", false)); + rawDataSetRef.fireInvalidated(ChartBits.DataSetDataAdded); assertEquals(5, counter1.get()); assertEquals(5, counter2.get()); + assertEquals(5, counter2.get()); - assertEquals(1, identityDataSet.getSourceDataSets().size()); - identityDataSet.getSourceDataSets().clear(); - assertEquals(0, identityDataSet.getSourceDataSets().size()); - - rawDataSetRef.invokeListener(new UpdatedDataEvent(rawDataSetRef, "OK reference", false)); - assertEquals(5, counter1.get()); - assertEquals(6, counter2.get()); } protected static DoubleDataSet generateSineWaveData(final int nData) { From ed2ccd3358a5d84c59dd78d360f6cdd82867d691 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 1 Aug 2023 13:04:28 +0200 Subject: [PATCH 49/81] fixed axis scale --- .../main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index c1cc64bc1..dc81fa16e 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -360,11 +360,8 @@ protected double calculateNewScale(final double length, final double lowerBound, final double scale = (range == 0) ? length : length / range; if (scale == 0) { return -1; // covers inf range input - } else if (getSide().isVertical()) { - return isInvertedAxis ? scale : -scale; - } else { - return isInvertedAxis ? -scale : scale; } + return getSide().isVertical() ? -scale : scale; } protected void clearAxisCanvas(final GraphicsContext gc, final double width, final double height) { From 9576d23e0c03761fc153c20fee1e5c1f41727a3e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 1 Aug 2023 13:04:45 +0200 Subject: [PATCH 50/81] fixed some unit tests --- .../chartfx/axes/spi/AbstractAxisParameterTests.java | 5 ----- .../renderer/spi/financial/utils/FinancialTestUtils.java | 4 ---- .../java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java | 1 - 3 files changed, 10 deletions(-) diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java index adbd84f66..089772e36 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameterTests.java @@ -352,11 +352,6 @@ public AxisRange getRange() { return null; } - @Override - public void fireInvalidated() { - // deliberately not implemented - } - @Override public void forceRedraw() { // deliberately not implemented diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/utils/FinancialTestUtils.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/utils/FinancialTestUtils.java index 7784511e9..92ae796a8 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/utils/FinancialTestUtils.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/financial/utils/FinancialTestUtils.java @@ -93,7 +93,6 @@ public static OhlcvItem parseOhlcvItem(String ohlcvItemString) throws ParseExcep public static void generateCosData(final DefaultErrorDataSet dataSet) { final long startTime = ProcessingProfiler.getTimeStamp(); - dataSet.autoNotification().set(false); dataSet.clearData(); final double now = System.currentTimeMillis() / 1000.0 + 1; // N.B. '+1' for (int n = 0; n < N_SAMPLES; n++) { @@ -104,9 +103,6 @@ public static void generateCosData(final DefaultErrorDataSet dataSet) { final double ey = 10; dataSet.add(t, y, ex, ey); } - dataSet.autoNotification().set(true); - - Platform.runLater(() -> dataSet.fireInvalidated(null)); ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java index 8ac6a7a80..d2d86a3c3 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/viewer/DataViewWindowTests.java @@ -56,7 +56,6 @@ public void testSetterGetter() throws InterruptedException, ExecutionException { // could not check for identity since ButtonBase slightly modifies the Graphics (css-related) assertEquals(content, field.getContent()); assertTrue(field.getChildren().contains(content), "content in children list"); - assertNotNull(field.autoNotification()); assertNotNull(field.getBitState()); assertTrue(field.isDetachableWindow()); From 913d6a6b141548866de2f9efe3d036f57b6c097c Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 1 Aug 2023 14:43:07 +0200 Subject: [PATCH 51/81] removed explicit layout requests from histogram renderer --- ...AbstractErrorDataSetRendererParameter.java | 21 +++++++++++++++ .../renderer/spi/HistogramRenderer.java | 14 +++++++--- .../chart/HistogramRendererBarSample.java | 27 ++++--------------- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java index 628534372..9e9c0010b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/AbstractErrorDataSetRendererParameter.java @@ -2,6 +2,7 @@ import java.util.Objects; +import io.fair_acc.chartfx.utils.PropUtil; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; @@ -62,6 +63,26 @@ public abstract class AbstractErrorDataSetRendererParameter render(final GraphicsContext gc, final Chart chart, final i return localDataSetList; } - public void requestLayout() { + public void invalidateCanvas() { final Chart chart = getChart(); if (chart == null) { return; } - chart.requestLayout(); + chart.fireInvalidated(ChartBits.ChartCanvas); } public BooleanProperty roundedCornerProperty() { @@ -692,7 +698,7 @@ public void handle(final long now) { // scheme 2 final Double val = scaling.put(dataSet.getName(), Math.min(scaling.computeIfAbsent(dataSet.getName(), ds -> 0.0) + 0.05, dataSet.getDataCount() + 1.0)); if (val != null && val < dataSet.getDataCount() + 1.0) { - HistogramRenderer.this.requestLayout(); + invalidateCanvas(); } } } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramRendererBarSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramRendererBarSample.java index 23e3b1ab6..4eeaac3f2 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramRendererBarSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramRendererBarSample.java @@ -242,51 +242,34 @@ private void init() { if (n != LineStyle.NONE) { drawBars.setSelected(false); } - histogramRenderer.requestLayout(); }); drawBars.selectedProperty().addListener((ch, old, selected) -> { histogramRenderer.setDrawBars(selected); if (Boolean.TRUE.equals(selected)) { lineStyle.getSelectionModel().select(LineStyle.NONE); } - histogramRenderer.requestLayout(); }); final CheckBox dynBarWidthEnable = new CheckBox(); dynBarWidthEnable.setSelected(histogramRenderer.isDynamicBarWidth()); - dynBarWidthEnable.selectedProperty().addListener((ch, old, selected) -> { - histogramRenderer.setDynamicBarWidth(selected); - histogramRenderer.requestLayout(); - }); + dynBarWidthEnable.selectedProperty().addListener((ch, old, selected) -> histogramRenderer.setDynamicBarWidth(selected)); this.addToParameterPane(" Dyn. Bar Width: ", dynBarWidthEnable); final Spinner dynBarWidth = new Spinner<>(0, 100, histogramRenderer.getBarWidthPercentage(), 10); - dynBarWidth.valueProperty().addListener((ch, old, value) -> { - histogramRenderer.setBarWidthPercentage(value.intValue()); - histogramRenderer.requestLayout(); - }); + dynBarWidth.valueProperty().addListener((ch, old, value) -> histogramRenderer.setBarWidthPercentage(value.intValue())); this.addToParameterPane(" Dyn. Bar Width: ", dynBarWidth); final Spinner barWidth = new Spinner<>(0, 100, histogramRenderer.getBarWidth()); - barWidth.valueProperty().addListener((ch, old, value) -> { - histogramRenderer.setBarWidth(value.intValue()); - histogramRenderer.requestLayout(); - }); + barWidth.valueProperty().addListener((ch, old, value) -> histogramRenderer.setBarWidth(value.intValue())); this.addToParameterPane(" Abs. Bar Width: ", barWidth); final CheckBox shiftBar = new CheckBox(); shiftBar.setSelected(histogramRenderer.isShiftBar()); - shiftBar.selectedProperty().addListener((ch, old, selected) -> { - histogramRenderer.setShiftBar(selected); - histogramRenderer.requestLayout(); - }); + shiftBar.selectedProperty().addListener((ch, old, selected) -> histogramRenderer.setShiftBar(selected)); this.addToParameterPane(" Shift Bar (mult. data sets): ", shiftBar); final Spinner shiftBarOffset = new Spinner<>(0, 100, histogramRenderer.getShiftBarOffset()); - shiftBarOffset.valueProperty().addListener((ch, old, value) -> { - histogramRenderer.setshiftBarOffset(value.intValue()); - histogramRenderer.requestLayout(); - }); + shiftBarOffset.valueProperty().addListener((ch, old, value) -> histogramRenderer.setshiftBarOffset(value.intValue())); this.addToParameterPane(" Shift Bar Offset (mult. DS): ", shiftBarOffset); } From 070d738824209756db23177ff7378974731feff9 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 1 Aug 2023 14:47:31 +0200 Subject: [PATCH 52/81] bugfix and added compatibility API --- .../src/main/java/io/fair_acc/chartfx/Chart.java | 2 +- .../java/io/fair_acc/dataset/events/BitState.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 2ff957305..0e79be7a2 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -83,7 +83,7 @@ public abstract class Chart extends Region implements EventSource { // of all datasets, but the JavaFX change listener may not forward the dirty bits to the chart until the next frame. // This creates a race condition where delta bits that are already cleared in the datasets may end up dirtying the // chart and trigger an unnecessary redraw. To avoid this issue we ignore the delta and pass the current state. - protected final BitState dataSetState = BitState.initDirty(this, BitState.ALL_BITS) + protected final BitState dataSetState = BitState.initDirtyMultiThreaded(this, BitState.ALL_BITS) .addChangeListener(FXUtils.runOnFxThread((src, deltaBits) -> state.setDirty(src.getBits()))); private static final Logger LOGGER = LoggerFactory.getLogger(Chart.class); diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java index 02105557e..4e4746236 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/events/BitState.java @@ -1,6 +1,7 @@ package io.fair_acc.dataset.events; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -193,7 +194,6 @@ private static boolean removeListener(List list, StateListener l if (list == null) { return false; } - int removed = 0; for (int i = list.size() - 1; i >= 0; i--) { if (isEqual(list.get(i), listener)) { list.remove(i); @@ -431,6 +431,16 @@ protected BitState(Object source, int filter) { this.filter = filter; } + @Deprecated // for backwards compatibility + public List getChangeListeners() { + return changeListeners == null ? Collections.emptyList() : changeListeners; + } + + @Deprecated // for backwards compatibility + public List getInvalidationListeners() { + return invalidateListeners == null ? Collections.emptyList() : invalidateListeners; + } + @Override public String toString() { return "bits(" + String.valueOf(source) + ")"; From da8f71a467d52ff1e8761958092195c2b3d86cda Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 1 Aug 2023 15:20:20 +0200 Subject: [PATCH 53/81] added even offset and changed to use the same style for all tick marks --- .../chartfx/axes/spi/AbstractAxis.java | 20 +++++++++++++++++-- .../fair_acc/chartfx/axes/spi/TickMark.java | 8 +------- .../io/fair_acc/chartfx/ui/css/TextStyle.java | 6 +++++- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index dc81fa16e..0454fd5de 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -770,9 +770,17 @@ protected void drawTickLabels(final GraphicsContext gc, final double axisWidth, final double evenCoord = getCanvasCoordinate(axisWidth, axisHeight, evenLabelsOffset); final double oddCoord = getCanvasCoordinate(axisWidth, axisHeight, oddLabelsOffset); + // use the same style for all tick marks + gc.save(); + getTickLabelStyle().copyStyleTo(gc); + // draw the labels final boolean isHorizontal = getSide().isHorizontal(); - boolean isEven = false; // TODO: check why the flag/counter used to be based on the first tick value + boolean isEven = false; + if (shiftLabels) { + // We don't want the first label to flip rows when shifting, so we base it off the tick value + isEven = ((int) tickMarks.get(0).getValue()) % 2 == 0; + } for (TickMark tickMark : tickMarks) { isEven = !isEven; @@ -791,6 +799,8 @@ protected void drawTickLabels(final GraphicsContext gc, final double axisWidth, } + gc.restore(); + } protected void drawTickMarks(final GraphicsContext gc, final double axisLength, final double axisWidth, @@ -1063,6 +1073,9 @@ protected static void drawAxisLabel(final GraphicsContext gc, final double x, fi gc.save(); gc.translate(x, y); + if (label.getRotate() != 0) { + gc.rotate(label.getRotate()); + } label.copyStyleTo(gc); gc.fillText(label.getText(), 0, 0); gc.restore(); @@ -1076,7 +1089,10 @@ protected static void drawTickMarkLabel(final GraphicsContext gc, final double x gc.save(); gc.translate(x, y); // translate before applying any rotation - tickMark.getStyle().copyStyleTo(gc); + + if (tickMark.getRotation() != 0) { + gc.rotate(tickMark.getRotation()); + } if (scaleFont != 1.0) { gc.scale(scaleFont, scaleFont); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/TickMark.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/TickMark.java index a1fcda1e6..fe58f52cb 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/TickMark.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/TickMark.java @@ -139,16 +139,10 @@ public void setVisible(boolean visible) { this.tickPosition = tickPosition; } - @Deprecated // for testing purposes - double getRotation() { + public double getRotation() { return style.getRotate(); } - @Deprecated // for testing purposes - void setRotation(double rotation) { - style.setRotate(rotation); - } - @Deprecated void setValue(double v) { this.tickValue = v; diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java index b0a7f7d5e..c1774c696 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java @@ -30,6 +30,10 @@ public TextStyle(String... styles) { rotateProperty().addListener(onChange); } + /** + * Copies all style parameters except for rotate + * @param gc target context + */ public void copyStyleTo(GraphicsContext gc) { gc.setFont(getFont()); gc.setFill(getFill()); @@ -38,7 +42,7 @@ public void copyStyleTo(GraphicsContext gc) { gc.setTextBaseline(getTextOrigin()); gc.setLineWidth(getStrokeWidth()); gc.setGlobalAlpha(getOpacity()); - gc.rotate(getRotate()); + // gc.rotate(getRotate()); // could cause issues with translation } public long getChangeCounter() { From 4afc0fc30897301c096491f3b37e2843c4dc20c2 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Tue, 1 Aug 2023 16:21:35 +0200 Subject: [PATCH 54/81] fixed a bug where the layout could be called before the JavaFX would execute the layout hook --- .../main/java/io/fair_acc/chartfx/Chart.java | 44 ++++++++++++------- .../chartfx/axes/spi/AbstractAxis.java | 2 + .../axes/spi/AbstractAxisParameter.java | 1 + .../fair_acc/chartfx/ui/utils/LayoutHook.java | 32 ++++++++++++-- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 0e79be7a2..fc3225baa 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -12,14 +12,10 @@ import io.fair_acc.dataset.event.EventSource; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; -import io.fair_acc.dataset.events.StateListener; -import io.fair_acc.dataset.locks.DataSetLock; import javafx.animation.Animation; import javafx.animation.KeyFrame; -import javafx.animation.Timeline; import javafx.application.Platform; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; @@ -30,13 +26,11 @@ import javafx.scene.CacheHint; import javafx.scene.Group; import javafx.scene.Node; -import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.control.Control; import javafx.scene.control.Label; import javafx.scene.layout.*; import javafx.scene.paint.Paint; -import javafx.stage.Window; import javafx.util.Duration; import org.slf4j.Logger; @@ -171,10 +165,13 @@ public abstract class Chart extends Region implements EventSource { getDatasets().addListener(datasetChangeListener); getAxes().addListener(axesChangeListener); getAxes().addListener(axesChangeListenerLocal); - showing.addListener((obs, old, showing) -> { - if (showing) { - layoutHooks.registerOnce(); - } + sceneProperty().addListener((observable, oldValue, newValue) -> { + // Render for the new scene. Note that the scene reference gets + // set in the CSS phase, so by the time we can register it would + // already be too late. Waiting for the layout phase wouldn't + // let us change the scene graph, so the only option we have is + // run the pre layout hook manually during CSS. + layoutHooks.runPreLayoutAndAdd(); }); } @@ -607,11 +604,17 @@ public void layoutChildren() { } // request re-layout of plugins - // TODO: - // * what if the datasets were not locked beforehand? - // * layoutChildren only gets called on actual changes, so could we guarantee execution if we skip it? - // * maybe they need to call their own readLock? But that would also result in outdated axes. Is this even needed? - layoutPluginsChildren(); + if(layoutHooks.hasRunPreLayout()) { + // datasets are locked, so we can add plugins + layoutPluginsChildren(); + } else { + // There are some rare corner cases, e.g., computing + // the pref size of the scene, that call for a layout + // without calling the hooks. The plugins may rely + // on datasets being locked, so we try again next + // pulse to be safe. + Platform.runLater(this::requestLayout); + } // Make sure things will get redrawn fireInvalidated(ChartBits.ChartCanvas); @@ -624,10 +627,17 @@ protected void runPostLayout() { axis.drawAxis(); } redrawCanvas(); - for (ChartPlugin plugin : plugins) { + for (var renderer : getRenderers()) { + if (renderer instanceof EventSource) { + ((EventSource) renderer).getBitState().clear(); + } + } + for (var plugin : plugins) { plugin.runPostLayout(); + if (plugin instanceof EventSource) { + ((EventSource) plugin).getBitState().clear(); + } } - dataSetState.clear(); state.clear(); for (var ds : lockedDataSets) { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 0454fd5de..96b8d3d5b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -268,6 +268,8 @@ protected void updateAxisRange(double length) { updateMinorTickMarks(); updateTickMarkPositions(getTickMarks()); updateTickMarkPositions(getMinorTickMarks()); + + state.clear(ChartBits.AxisRange, ChartBits.AxisTickLabelText); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 0d1b9c578..caa88e194 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -1233,6 +1233,7 @@ protected void updateAxisLabel() { prefix = (prefix == null) ? "" : prefix; getAxisLabel().setText(getName() + " [" + prefix + unit + "]"); } + state.clear(ChartBits.AxisLabelText); } protected void updateScaleAndUnitPrefix() { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java index 46efe452c..cb36f5441 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java @@ -52,6 +52,10 @@ public boolean hasRunPreLayout() { return hasRunPreLayout; } + public boolean isRegistered() { + return registeredScene != null; + } + public LayoutHook registerOnce() { // Potentially called before proper initialization if (preLayoutAction == null || postLayoutAndRemove == null) { @@ -65,7 +69,7 @@ public LayoutHook registerOnce() { } // Scene has changed -> remove the old one first - if (registeredScene != null) { + if (isRegistered()) { unregister(); } @@ -77,7 +81,29 @@ public LayoutHook registerOnce() { return this; } - private void runPreLayoutAndAdd() { + /** + * Registers the post-layout hook and executes the pre-layout hook if + * it has not already been executed during this pulse. + *

+ * Generally this should be called by the pulse by registering + * registerOnce(), but when registering during the CSS or layout + * this method may be called manually to get it executed within + * the same pulse. + */ + public void runPreLayoutAndAdd() { + // Already ran + if (hasRunPreLayout()) { + return; + } + + // Needs a registration + if (!isRegistered()) { + if (node.getScene() == null) { + return; + } + registerOnce(); + } + // We don't want to be in a position where the post layout listener // runs by itself, so we don't register until we made sure that the // pre-layout action ran before. @@ -92,7 +118,7 @@ private void runPostLayoutAndRemove() { } private void unregister() { - if (registeredScene == null) { + if (!isRegistered()) { return; } registeredScene.removePreLayoutPulseListener(preLayoutAndAdd); From 3fa339a2b0ccd773a49ba2cae0dbaf00a2159b0d Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 00:31:36 +0200 Subject: [PATCH 55/81] fixed aliasing issue in center axes --- chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java | 2 +- .../main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java | 4 ++-- .../src/main/resources/io/fair_acc/chartfx/chart.css | 4 ++++ .../java/io/fair_acc/sample/chart/ChartAnatomySample.java | 5 +++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index fc3225baa..dbcf31ea5 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -169,7 +169,7 @@ public abstract class Chart extends Region implements EventSource { // Render for the new scene. Note that the scene reference gets // set in the CSS phase, so by the time we can register it would // already be too late. Waiting for the layout phase wouldn't - // let us change the scene graph, so the only option we have is + // let us change the scene graph, so the best option we have is // run the pre layout hook manually during CSS. layoutHooks.runPreLayoutAndAdd(); }); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java index 534ac52c9..b60d0a905 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java @@ -265,10 +265,10 @@ protected void layoutChildren() { xOffsetRight += prefSize; break; case CENTER_HOR: - resizeRelocate(child, xContent, yContent + contentHeight / 2 - prefSize / 2, contentWidth, prefSize); + resizeRelocate(child, xContent, snapSizeY(yContent + contentHeight / 2 - prefSize / 2), contentWidth, prefSize); break; case CENTER_VER: - resizeRelocate(child, xContent + contentWidth / 2 - prefSize / 2, yContent, prefSize, contentHeight); + resizeRelocate(child, snapSizeX(xContent + contentWidth / 2 - prefSize / 2), yContent, prefSize, contentHeight); break; } } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 5f700a063..721e51fa8 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -12,6 +12,10 @@ -fx-axis-label-alignment: center; } +.chart-content { + -fx-padding: 0px; +} + .chart-crosshair-path { -fx-stroke-width: 1; } diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java index 9630e0a6a..ff41e1c0d 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java @@ -82,8 +82,9 @@ protected void updateLegend(final List dataSets, final List r }; VBox.setVgrow(chart, Priority.ALWAYS); chart.getAxes().addAll( - xAxis1, xAxis2, xAxis3, - yAxis1, yAxis2, yAxis3, yAxis4); + xAxis1, xAxis2, // horizontal + yAxis1, yAxis2, yAxis3, // vertical + xAxis3, yAxis4); // center chart.setTitle(" Hello World Chart "); // chart.setToolBarSide(Side.LEFT); From 7bd741ddeefb4d24906e11d1c01a53fdcdac25b6 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 01:07:55 +0200 Subject: [PATCH 56/81] moved padding and min/pref size to css --- .../main/java/io/fair_acc/chartfx/Chart.java | 8 +-- .../chartfx/axes/spi/AbstractAxis.java | 4 +- .../fair_acc/chartfx/ui/layout/ChartPane.java | 54 +++++++++++++++++++ .../resources/io/fair_acc/chartfx/chart.css | 12 ++++- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index dbcf31ea5..a99d41cdd 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -5,6 +5,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import io.fair_acc.chartfx.ui.css.StyleUtil; import io.fair_acc.chartfx.ui.layout.ChartPane; import io.fair_acc.chartfx.ui.layout.PlotAreaPane; import io.fair_acc.chartfx.ui.*; @@ -148,7 +149,7 @@ public abstract class Chart extends Region implements EventSource { // > canvas foreground // > plugins // > plot background/foreground - plotArea.setContent(new PlotAreaPane(getCanvas(), getCanvasForeground(), pluginsArea)); + plotArea.setContent(StyleUtil.addStyles(new PlotAreaPane(getCanvas(), getCanvasForeground(), pluginsArea), "chart-plot-area")); axesAndCanvasPane.addCenter(getPlotBackground(), getPlotArea(), getPlotForeground()); titleLegendPane.addCenter(axesAndCanvasPane); measurementPane.addCenter(titleLegendPane); @@ -297,11 +298,6 @@ public Chart(Axis... axes) { } menuPane.setTriggerDistance(Chart.DEFAULT_TRIGGER_DISTANCE); - setMinSize(0, 0); - setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE); - setMaxSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE); - setPadding(Insets.EMPTY); - plotBackground.toBack(); plotForeGround.toFront(); plotForeGround.setMouseTransparent(true); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 96b8d3d5b..1e917841a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -381,7 +381,7 @@ protected double computePrefHeight(final double width) { final var side = getSide(); if ((side == null) || side.isVertical()) { // default axis size for uninitalised axis - return 150; + return Math.max(getAxisLabel().getBoundsInParent().getHeight(), 150); } return computePrefSize(width); } @@ -397,7 +397,7 @@ protected double computePrefWidth(final double height) { final var side = getSide(); if ((side == null) || side.isHorizontal()) { // default axis size for uninitalised axis - return 150; + return Math.max(getAxisLabel().getBoundsInParent().getWidth(), 150); } return computePrefSize(height); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java index b60d0a905..e90b3b0ce 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java @@ -133,6 +133,60 @@ private double estimateHeightOfAllHorizontalAxes() { return sum; } + @Override + protected double computeMinWidth(double height) { + double verticalSum = 0; + double maxHorizontalWidth = 0; + for (Node child : getChildren()) { + if (!child.isManaged()) continue; + Object location = getLocation(child); + if (location == null) { + maxHorizontalWidth = Math.max(maxHorizontalWidth, child.minWidth(height)); + } else if (location instanceof Side) { + switch ((Side) location) { + case CENTER_HOR: + case CENTER_VER: + case TOP: + case BOTTOM: + maxHorizontalWidth = Math.max(maxHorizontalWidth, child.minWidth(height)); + break; + case LEFT: + case RIGHT: + verticalSum += child.minWidth(height); + break; + } + } + } + return verticalSum + maxHorizontalWidth; + } + + @Override + protected double computeMinHeight(double width) { + double horizontalSum = 0; + double maxVerticalHeight = 0; + for (Node child : getChildren()) { + if (!child.isManaged()) continue; + Object location = getLocation(child); + if (location == null) { + maxVerticalHeight = Math.max(maxVerticalHeight, child.minHeight(width)); + } else if (location instanceof Side) { + switch ((Side) location) { + case CENTER_HOR: + case CENTER_VER: + case LEFT: + case RIGHT: + maxVerticalHeight = Math.max(maxVerticalHeight, child.minHeight(width)); + break; + case TOP: + case BOTTOM: + horizontalSum += child.minHeight(width); + break; + } + } + } + return horizontalSum + maxVerticalHeight; + } + @Override protected void layoutChildren() { // Account for margin and border insets diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 721e51fa8..06dc19798 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -12,7 +12,17 @@ -fx-axis-label-alignment: center; } -.chart-content { +.chart, .chart-content, .chart-plot-area { + /* pref size = computed */ + -fx-pref-height: -1; + -fx-pref-width: -1; + + /* min/max = reasonable */ + -fx-min-height: 100px; + -fx-min-width: 100px; + -fx-max-width: 4096px; + -fx-max-height: 4096px; + -fx-padding: 0px; } From 8c77d79fa6a171cf7d38ac0fcc79548f009e04ef Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 01:10:35 +0200 Subject: [PATCH 57/81] removed unnecessary parent --- .../io/fair_acc/sample/chart/ChartAnatomySample.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java index ff41e1c0d..8bb6a71b8 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ChartAnatomySample.java @@ -36,9 +36,6 @@ public class ChartAnatomySample extends Application { @Override public void start(final Stage primaryStage) { - final VBox root = new VBox(); - root.setAlignment(Pos.CENTER); - final DefaultNumericAxis xAxis1 = new DefaultNumericAxis("x-Axis1", 0, 100, 1); final DefaultNumericAxis xAxis2 = new DefaultNumericAxis("x-Axis2", 0, 100, 1); final DefaultNumericAxis xAxis3 = new DefaultNumericAxis("x-Axis3", -50, +50, 10); @@ -80,7 +77,7 @@ protected void updateLegend(final List dataSets, final List r // TODO Auto-generated method stub } }; - VBox.setVgrow(chart, Priority.ALWAYS); + chart.getAxes().addAll( xAxis1, xAxis2, // horizontal yAxis1, yAxis2, yAxis3, // vertical @@ -132,9 +129,7 @@ protected void updateLegend(final List dataSets, final List r chart.getCanvas().addEventHandler(MouseEvent.MOUSE_CLICKED, mevt -> LOGGER.atInfo().log("clicked on canvas - alt implementation")); - root.getChildren().add(chart); - - final Scene scene = new Scene(root, 1000, 600); + final Scene scene = new Scene(chart, 1000, 600); primaryStage.setTitle(this.getClass().getSimpleName()); primaryStage.setScene(scene); primaryStage.setOnCloseRequest(evt -> Platform.exit()); From 4100feaa16a3505b3f8325641aaf45c5bff7f48e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 01:53:18 +0200 Subject: [PATCH 58/81] bugfix when disabling a category range --- .../src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java | 1 + 1 file changed, 1 insertion(+) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java index 9e6b1ab00..b84711e4c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java @@ -68,6 +68,7 @@ public CategoryAxis(final String axisLabel) { this.setOverlapPolicy(AxisLabelOverlapPolicy.SHIFT_ALT); PropUtil.runOnChange(() -> { final double range = Math.abs(getMax() - getMin()); + if (!Double.isFinite(range)) return; final double scale = 0.5 / ((int) range); autoRangePaddingProperty().set(scale); }, minProperty(), maxProperty()); From f17c15b43168ca85953d55932778b072928a473f Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 13:46:51 +0200 Subject: [PATCH 59/81] fixed an issue when unregistering a scene --- .../main/java/io/fair_acc/chartfx/Chart.java | 8 ------ .../fair_acc/chartfx/ui/utils/LayoutHook.java | 27 ++++++++++++++----- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index a99d41cdd..cba8253e5 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -166,14 +166,6 @@ public abstract class Chart extends Region implements EventSource { getDatasets().addListener(datasetChangeListener); getAxes().addListener(axesChangeListener); getAxes().addListener(axesChangeListenerLocal); - sceneProperty().addListener((observable, oldValue, newValue) -> { - // Render for the new scene. Note that the scene reference gets - // set in the CSS phase, so by the time we can register it would - // already be too late. Waiting for the layout phase wouldn't - // let us change the scene graph, so the best option we have is - // run the pre layout hook manually during CSS. - layoutHooks.runPreLayoutAndAdd(); - }); } protected final Label titleLabel = new Label(); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java index cb36f5441..1af4bd64b 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java @@ -1,6 +1,8 @@ package io.fair_acc.chartfx.ui.utils; import io.fair_acc.dataset.utils.AssertUtils; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.scene.Node; import javafx.scene.Scene; @@ -43,6 +45,24 @@ private LayoutHook(Node node, Runnable preLayoutAction, Runnable postLayoutActio this.node = node; this.preLayoutAction = AssertUtils.notNull("preLayoutAction", preLayoutAction); this.postLayoutAction = AssertUtils.notNull("preLayoutAction", postLayoutAction); + node.sceneProperty().addListener((observable, oldValue, newValue) -> { + // Make sure we leave the old scene in a consistent state + if (isRegistered()) { + if (hasRunPreLayout) { + postLayoutAction.run(); + } + unregister(); + } + + // Register when the scene changes. Note that the scene reference gets + // set in the CSS phase, so by the time we can register it would + // already be too late. Waiting for the layout phase wouldn't + // let us change the scene graph, so the best option we have is + // run the pre layout hook manually during CSS. + if (newValue != null) { + runPreLayoutAndAdd(); + } + }); } /** @@ -57,11 +77,6 @@ public boolean isRegistered() { } public LayoutHook registerOnce() { - // Potentially called before proper initialization - if (preLayoutAction == null || postLayoutAndRemove == null) { - return this; - } - // Already registered or null, so nothing to do var scene = node.getScene(); if (scene == registeredScene) { @@ -90,7 +105,7 @@ public LayoutHook registerOnce() { * this method may be called manually to get it executed within * the same pulse. */ - public void runPreLayoutAndAdd() { + private void runPreLayoutAndAdd() { // Already ran if (hasRunPreLayout()) { return; From f7fa0ed0d925de42bd056ff7310d0ff6909d4ed6 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 13:50:09 +0200 Subject: [PATCH 60/81] removed pref size from css --- .../src/main/resources/io/fair_acc/chartfx/chart.css | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 06dc19798..bd3bc896b 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -13,16 +13,13 @@ } .chart, .chart-content, .chart-plot-area { - /* pref size = computed */ - -fx-pref-height: -1; - -fx-pref-width: -1; - - /* min/max = reasonable */ + /* use a reasonable min and full-screen max */ -fx-min-height: 100px; -fx-min-width: 100px; -fx-max-width: 4096px; -fx-max-height: 4096px; + /* no padding by default */ -fx-padding: 0px; } From 0b1dd2b79548bbb522daee8ad13a6f87f6ce8313 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 14:15:34 +0200 Subject: [PATCH 61/81] added support for renderer axes that are not part of the chart --- .../main/java/io/fair_acc/chartfx/Chart.java | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index cba8253e5..2b8e30b2c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -578,8 +578,6 @@ protected void runPreLayout() { } - private List lockedDataSets = new ArrayList<>(); - @Override public void layoutChildren() { // Size all nodes to full size. Account for margin and border insets. @@ -609,6 +607,10 @@ public void layoutChildren() { } protected void runPostLayout() { + // Make sure that renderer axes that are not part of + // the chart still produce an accurate axis transform. + updateStandaloneRendererAxes(); + // Update the actual Canvas content final long start = ProcessingProfiler.getTimeStamp(); for (Axis axis : axesList) { @@ -638,6 +640,29 @@ protected void runPostLayout() { ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); } + /** + * Update axes that are not part of the SceneGraph and that would + * otherwise not have a size. We could alternatively add renderer + * axes to the chart, but that would not match previous behavior, + * and it is probably a good idea to require it to be explicit. + */ + private void updateStandaloneRendererAxes() { + for (Renderer renderer : getRenderers()) { + for (Axis axis : renderer.getAxes()) { + if (axis instanceof AbstractAxis) { + var axisNode = (AbstractAxis) axis; + if (axisNode.getParent() == null) { + if (axis.getSide().isHorizontal()) { + axisNode.prefWidth(plotArea.getHeight()); + } else { + axisNode.prefHeight(plotArea.getWidth()); + } + } + } + } + } + } + private void forEachDataSet(Consumer action) { for (DataSet dataset : datasets) { action.accept(dataset); @@ -649,6 +674,8 @@ private void forEachDataSet(Consumer action) { } } + private final List lockedDataSets = new ArrayList<>(); + public final ObjectProperty legendProperty() { return legend; } @@ -775,7 +802,7 @@ protected void axesChangedLocal(final ListChangeListener.Change // remove axis invalidation listener AssertUtils.notNull("to be removed axis is null", axis); axis.removeListener(state); - removeAxisFromChildren(axis); // TODO: don't remove if it is contained in getAxes() + removeAxisFromChildren(axis); } for (final Axis axis : change.getAddedSubList()) { // check if axis is associated with an existing renderer, From 8c80597f40e2f2587aeca9198cc39945693ec23f Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 14:32:50 +0200 Subject: [PATCH 62/81] removed a now unnecessary requirement for minimum tick marks --- .../main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 1e917841a..fdb189f46 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -614,9 +614,7 @@ List computeTickMarks(AxisRange range, boolean major) { protected void updateMajorTickMarks(AxisRange range) { var newTickValues = calculateMajorTickValues(range); var oldTickValues = getTickMarkValues(); - if(newTickValues.size() < 2) { - return; // TODO: why did the previous code only update when there are > 2 ticks? - } else if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickLabelText)) { + if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickLabelText)) { return; // no need to redo labels, just reposition the ticks } From 9dae77d39b6e497a8f96ec56730fb9ee8b615e21 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 15:17:49 +0200 Subject: [PATCH 63/81] changed tick values from boxed lists to primitive lists --- .../chartfx/axes/AxisLabelFormatter.java | 5 +- .../chartfx/axes/spi/AbstractAxis.java | 50 +++++++++++-------- .../axes/spi/AbstractAxisParameter.java | 16 ++---- .../chartfx/axes/spi/CategoryAxis.java | 6 +-- .../chartfx/axes/spi/DefaultNumericAxis.java | 27 +++++----- .../fair_acc/chartfx/axes/spi/LinearAxis.java | 14 ++---- .../chartfx/axes/spi/LogarithmicAxis.java | 14 +++--- .../chartfx/axes/spi/NumericAxis.java | 14 +++--- .../chartfx/axes/spi/OscilloscopeAxis.java | 18 +++---- .../axes/spi/format/AbstractFormatter.java | 11 ++-- .../axes/spi/format/DefaultFormatter.java | 9 ++-- .../chartfx/axes/spi/AbstractAxisTests.java | 8 ++- .../axes/spi/OscilloscopeAxisTests.java | 2 +- .../dataset/spi/fastutil/DoubleArrayList.java | 14 ++++++ .../sample/chart/TimeAxisNonLinearSample.java | 10 ++-- 15 files changed, 112 insertions(+), 106 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/AxisLabelFormatter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/AxisLabelFormatter.java index caffeda45..37134876a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/AxisLabelFormatter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/AxisLabelFormatter.java @@ -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; /** @@ -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 newMajorTickMarks, double unitScaling); + void updateFormatter(DoubleArrayList newMajorTickMarks, double unitScaling); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index fdb189f46..d8a055dc9 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -7,10 +7,8 @@ import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.chartfx.utils.PropUtil; -import io.fair_acc.dataset.event.AxisNameChangeEvent; -import io.fair_acc.dataset.event.AxisRangeChangeEvent; import io.fair_acc.dataset.events.ChartBits; -import javafx.application.Platform; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableList; @@ -31,7 +29,6 @@ import io.fair_acc.chartfx.axes.spi.format.DefaultTimeFormatter; import io.fair_acc.chartfx.ui.ResizableCanvas; import io.fair_acc.chartfx.ui.geometry.Side; -import io.fair_acc.dataset.event.AxisChangeEvent; /** * @author rstein @@ -336,18 +333,17 @@ protected AxisRange autoRange(final double length) { /** * Calculate a list of all the data values for each tick mark in range * - * @param range A range object returned from autoRange() - * @return A list of tick marks that fit along the axis if it was the given length + * @param range A range object returned from autoRange() + * @param tickValues An empty list where the ticks shall be stored */ - protected abstract List calculateMajorTickValues(AxisRange range); + protected abstract void calculateMajorTickValues(AxisRange range, DoubleArrayList tickValues); /** * Calculate a list of the data values for every minor tick mark * - * @return List of data values where to draw minor tick marks + * @param tickValues An empty list where the ticks shall be stored */ - - protected abstract List calculateMinorTickValues(); + protected abstract void calculateMinorTickValues(DoubleArrayList tickValues); /** * Calculate a new scale for this axis. This should not effect any state(properties) of this axis. @@ -611,8 +607,15 @@ List computeTickMarks(AxisRange range, boolean major) { } } + private final transient DoubleArrayList newTickValues = new DoubleArrayList(); + protected void updateMajorTickMarks(AxisRange range) { - var newTickValues = calculateMajorTickValues(range); + // Compute new tick marks + newTickValues.clear(); + newTickValues.ensureCapacity(getMaxMajorTickLabelCount()); + calculateMajorTickValues(range, newTickValues); + + // Check if anything changed var oldTickValues = getTickMarkValues(); if (newTickValues.equals(oldTickValues) && state.isClean(ChartBits.AxisTickLabelText)) { return; // no need to redo labels, just reposition the ticks @@ -626,28 +629,35 @@ protected void updateMajorTickMarks(AxisRange range) { // Update the existing mark objects List marks = FXUtils.sizedList(getTickMarks(), newTickValues.size(), () -> new TickMark(getTickLabelStyle())); int i = 0; - for (var tick : newTickValues) { - String label = isTickLabelsVisible() ? getTickMarkLabel(tick) : ""; - marks.get(i++).setValue(tick, label); + for (var mark : marks) { + var tick = newTickValues.getDouble(i++); + mark.setValue(tick, isTickLabelsVisible() ? getTickMarkLabel(tick) : ""); } oldTickValues.setAll(newTickValues); tickMarksUpdated(); - } protected void updateMinorTickMarks() { - var newTickValues = calculateMinorTickValues(); - if (newTickValues.equals(getMinorTickMarkValues())) { + // Compute new tick marks + newTickValues.clear(); + newTickValues.ensureCapacity(getMaxMajorTickLabelCount() * getMinorTickCount()); + calculateMinorTickValues(newTickValues); + + // Check if anything changed + var oldTickValues = getMinorTickMarkValues(); + if (newTickValues.equals(oldTickValues)) { return; } - getMinorTickMarkValues().setAll(newTickValues); + // Update List marks = FXUtils.sizedList(getMinorTickMarks(), newTickValues.size(), () -> new TickMark(getTickLabelStyle())); int i = 0; - for (var tick : newTickValues) { - marks.get(i++).setValue(tick, ""); + for (var mark : marks) { + mark.setValue(newTickValues.getDouble(i++), ""); } + + oldTickValues.setAll(newTickValues); tickMarksUpdated(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index caa88e194..398ce3a94 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -1,7 +1,5 @@ package io.fair_acc.chartfx.axes.spi; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -12,6 +10,7 @@ import io.fair_acc.chartfx.utils.PropUtil; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -29,10 +28,6 @@ import io.fair_acc.chartfx.axes.AxisLabelOverlapPolicy; import io.fair_acc.chartfx.ui.css.CssPropertyFactory; import io.fair_acc.chartfx.ui.geometry.Side; -import io.fair_acc.dataset.event.AxisChangeEvent; -import io.fair_acc.dataset.event.EventListener; -import io.fair_acc.dataset.event.UpdateEvent; -import io.fair_acc.dataset.utils.NoDuplicatesList; /** * Class containing the properties, getters and setters for the AbstractNumericAxis class @@ -203,9 +198,8 @@ public AbstractAxisParameter() { getChildren().addAll(axisLabel, tickLabelStyle, majorTickStyle, minorTickStyle); } - // TODO: replace with primitive DoubleArrayList and specialized TickMarkList - protected final transient ObservableList majorTickMarkValues = FXCollections.observableArrayList(new NoDuplicatesList<>()); - protected final transient ObservableList minorTickMarkValues = FXCollections.observableArrayList(new NoDuplicatesList<>()); + protected final transient DoubleArrayList majorTickMarkValues = new DoubleArrayList(); + protected final transient DoubleArrayList minorTickMarkValues = new DoubleArrayList(); protected final transient ObservableList majorTickMarks = FXCollections.observableArrayList(); protected final transient ObservableList minorTickMarks = FXCollections.observableArrayList(); @@ -646,7 +640,7 @@ public ObservableList getMinorTickMarks() { /** * @return observable list containing of each minor TickMark values on this axis */ - public ObservableList getMinorTickMarkValues() { + public DoubleArrayList getMinorTickMarkValues() { return minorTickMarkValues; } @@ -731,7 +725,7 @@ public ObservableList getTickMarks() { /** * @return observable list containing of each major TickMark values on this axis */ - public ObservableList getTickMarkValues() { + public DoubleArrayList getTickMarkValues() { return majorTickMarkValues; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java index b84711e4c..c82d5e50d 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/CategoryAxis.java @@ -5,7 +5,7 @@ import java.util.List; import io.fair_acc.chartfx.utils.PropUtil; -import io.fair_acc.dataset.events.ChartBits; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; import javafx.collections.FXCollections; @@ -207,8 +207,8 @@ protected AxisRange autoRange(final double minValue, final double maxValue, fina } @Override - protected List calculateMinorTickValues() { - return Collections.emptyList(); + protected void calculateMinorTickValues(DoubleArrayList tickValues) { + // categories have no minor ticks } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java index ff9e4162b..7df48e883 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxis.java @@ -6,6 +6,7 @@ import java.util.List; import io.fair_acc.chartfx.utils.PropUtil; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.scene.chart.NumberAxis; @@ -406,24 +407,23 @@ protected AxisRange autoRange(final double minValue, final double maxValue, fina } @Override - protected List calculateMajorTickValues(final AxisRange axisRange) { - final List tickValues = new ArrayList<>(getMaxMajorTickLabelCount()); + protected void calculateMajorTickValues(final AxisRange axisRange, DoubleArrayList tickValues) { if (isLogAxis) { if (axisRange.getLowerBound() >= axisRange.getUpperBound()) { - return Arrays.asList(axisRange.getLowerBound()); // NOPMD NOSONAR -- cannot use singletonList since list needs to remain modifiable + tickValues.add(axisRange.getLowerBound()); + return; } double exp = Math.ceil(axisTransform.forward(axisRange.getLowerBound())); - for (double tickValue = axisTransform.backward(exp); tickValue <= axisRange - .getUpperBound(); + for (double tickValue = axisTransform.backward(exp); tickValue <= axisRange.getUpperBound(); tickValue = axisTransform.backward(++exp)) { tickValues.add(tickValue); } - - return tickValues; + return; } if (axisRange.getLowerBound() == axisRange.getUpperBound() || axisRange.getTickUnit() <= 0) { - return Arrays.asList(axisRange.getLowerBound()); // NOPMD NOSONAR -- cannot use singletonList since list needs to remain modifiable + tickValues.add(axisRange.getLowerBound()); + return; } final double firstTick = DefaultNumericAxis.computeFistMajorTick(axisRange.getLowerBound(), @@ -432,8 +432,9 @@ protected List calculateMajorTickValues(final AxisRange axisRange) { if (LOGGER.isDebugEnabled()) { LOGGER.atDebug().log("major ticks numerically not resolvable"); } - return tickValues; + return; } + final int maxTickCount = getMaxMajorTickLabelCount(); for (double major = firstTick; (major <= axisRange.getUpperBound() && tickValues.size() <= maxTickCount); major += axisRange.getTickUnit()) { if (tickValues.size() > getMaxMajorTickLabelCount()) { @@ -441,16 +442,14 @@ protected List calculateMajorTickValues(final AxisRange axisRange) { } tickValues.add(major); } - return tickValues; } @Override - protected List calculateMinorTickValues() { + protected void calculateMinorTickValues(DoubleArrayList newMinorTickMarks) { if (getMinorTickCount() <= 0 || getTickUnit() <= 0) { - return Collections.emptyList(); + return; } - final List newMinorTickMarks = new ArrayList<>(); final double lowerBound = getMin(); final double upperBound = getMax(); final double majorUnit = getTickUnit(); @@ -496,7 +495,7 @@ protected List calculateMinorTickValues() { majorTickCount++; } } - return newMinorTickMarks; + return; } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LinearAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LinearAxis.java index cd8c12358..79589b332 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LinearAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LinearAxis.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.*; import javafx.css.CssMetaData; import javafx.css.Styleable; @@ -428,27 +429,24 @@ protected AxisRange autoRange(final double minValue, final double maxValue, fina } @Override - protected List calculateMajorTickValues(final AxisRange range) { + protected void calculateMajorTickValues(final AxisRange range, DoubleArrayList tickValues) { if (range == null) { throw new InvalidParameterException("range is null"); } - final List tickValues = new ArrayList<>(); - if (range.getLowerBound() == range.getUpperBound() || range.getTickUnit() <= 0) { - return Collections.singletonList(range.getLowerBound()); + tickValues.add(range.getLowerBound()); + return; } final double firstTick = LinearAxis.computeFistMajorTick(range.getLowerBound(), range.getTickUnit()); for (double major = firstTick; major <= range.getUpperBound(); major += range.getTickUnit()) { tickValues.add(major); } - return tickValues; } @Override - protected List calculateMinorTickValues() { - final List minorTickMarks = new ArrayList<>(); + protected void calculateMinorTickValues(DoubleArrayList minorTickMarks) { final double lowerBound = getMin(); final double upperBound = getMax(); final double majorUnit = getTickUnit(); @@ -463,8 +461,6 @@ protected List calculateMinorTickValues() { } } } - - return minorTickMarks; } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LogarithmicAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LogarithmicAxis.java index b4e88411f..16a1714e8 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LogarithmicAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/LogarithmicAxis.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; @@ -330,32 +331,30 @@ protected AxisRange autoRange(final double minValue, final double maxValue, fina } @Override - protected List calculateMajorTickValues(final AxisRange range) { + protected void calculateMajorTickValues(final AxisRange range, DoubleArrayList tickValues) { if (range == null) { throw new InvalidParameterException("range is null"); } - final List tickValues = new ArrayList<>(); if (range.getLowerBound() >= range.getUpperBound()) { - return Collections.singletonList(range.getLowerBound()); + tickValues.add(range.getLowerBound()); + return; } double exp = Math.ceil(log(range.getLowerBound())); for (double tickValue = pow(exp); tickValue <= range.getUpperBound(); tickValue = pow(++exp)) { tickValues.add(tickValue); } - return tickValues; } // -------------- STYLESHEET HANDLING // ------------------------------------------------------------------------------ @Override - protected List calculateMinorTickValues() { + protected void calculateMinorTickValues(DoubleArrayList minorTickMarks) { if (getMinorTickCount() <= 0) { - return Collections.emptyList(); + return; } - final List minorTickMarks = new ArrayList<>(); final double lowerBound = getMin(); final double upperBound = getMax(); @@ -370,7 +369,6 @@ protected List calculateMinorTickValues() { } } - return minorTickMarks; } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/NumericAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/NumericAxis.java index f9956500d..7d44259b8 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/NumericAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/NumericAxis.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.List; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.*; import javafx.css.CssMetaData; import javafx.css.Styleable; @@ -461,25 +462,23 @@ protected AxisRange autoRange(final double minValue, final double maxValue, fina } @Override - protected List calculateMajorTickValues(final AxisRange range) { + protected void calculateMajorTickValues(final AxisRange range, DoubleArrayList tickValues) { if (range.getLowerBound() == range.getUpperBound() || range.getTickUnit() <= 0) { - return Collections.singletonList(range.getLowerBound()); + tickValues.add(range.getLowerBound()); + return; } - final List tickValues = new ArrayList<>(); final double firstTick = NumericAxis.computeFistMajorTick(range.getLowerBound(), range.getTickUnit()); for (double major = firstTick; major <= range.getUpperBound(); major += range.getTickUnit()) { tickValues.add(major); } - return tickValues; } @Override - protected List calculateMinorTickValues() { + protected void calculateMinorTickValues(DoubleArrayList minorTickMarks) { if (getMinorTickCount() == 0 || getTickUnit() == 0) { - return Collections.emptyList(); + return; } - final List minorTickMarks = new ArrayList<>(); final double lowerBound = getMin(); final double upperBound = getMax(); final double majorUnit = getTickUnit(); @@ -495,7 +494,6 @@ protected List calculateMinorTickValues() { } } } - return minorTickMarks; } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java index 2eef93f3d..9d8208693 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxis.java @@ -2,6 +2,7 @@ import java.util.*; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.DoubleProperty; import javafx.css.CssMetaData; import javafx.css.Styleable; @@ -237,10 +238,10 @@ public void setTickUnitSupplier(TickUnitSupplier tickUnitSupplier) { } @Override - protected List calculateMajorTickValues(AxisRange axisRange) { - final List tickValues = new ArrayList<>(); + protected void calculateMajorTickValues(AxisRange axisRange, DoubleArrayList tickValues) { if (axisRange.getMin() == axisRange.getMax() || axisRange.getTickUnit() <= 0) { - return Collections.singletonList(axisRange.getMin()); + tickValues.add(axisRange.getMin()); + return; } final double firstTick = Math.ceil(axisRange.getMin() / axisRange.getTickUnit()) * axisRange.getTickUnit(); @@ -248,22 +249,21 @@ protected List calculateMajorTickValues(AxisRange axisRange) { if (LOGGER.isDebugEnabled()) { LOGGER.atDebug().log("major ticks numerically not resolvable"); } - return tickValues; + return; } + final int maxTickCount = getMaxMajorTickLabelCount(); for (double major = firstTick; (major <= axisRange.getMax() && tickValues.size() <= maxTickCount); major += axisRange.getTickUnit()) { tickValues.add(major); } - return tickValues; } @Override - protected List calculateMinorTickValues() { + protected void calculateMinorTickValues(DoubleArrayList newMinorTickMarks) { if (getMinorTickCount() <= 0 || getTickUnit() <= 0) { - return Collections.emptyList(); + return; } - final List newMinorTickMarks = new ArrayList<>(); final double lowerBound = getMin(); final double upperBound = getMax(); final double majorUnit = getTickUnit(); @@ -291,8 +291,6 @@ protected List calculateMinorTickValues() { } majorTickCount++; } - - return newMinorTickMarks; } @Override diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/AbstractFormatter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/AbstractFormatter.java index dd84632cd..a4b87f827 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/AbstractFormatter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/AbstractFormatter.java @@ -5,6 +5,7 @@ import io.fair_acc.chartfx.axes.Axis; import io.fair_acc.chartfx.axes.AxisLabelFormatter; import io.fair_acc.chartfx.axes.TickUnitSupplier; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; @@ -19,7 +20,7 @@ public abstract class AbstractFormatter extends StringConverter implemen private final ObjectProperty tickUnitSupplier = new SimpleObjectProperty<>(this, "tickUnitSupplier", AbstractFormatter.DEFAULT_TICK_UNIT_SUPPLIER); protected FormatterLabelCache labelCache = new FormatterLabelCache(); - protected List majorTickMarksCopy; + protected final DoubleArrayList majorTickMarks = new DoubleArrayList(); protected double unitScaling; protected double rangeMin; protected double rangeMax; @@ -107,14 +108,14 @@ public ObjectProperty tickUnitSupplierProperty() { } @Override - public void updateFormatter(final List newMajorTickMarks, final double unitScaling) { - majorTickMarksCopy = newMajorTickMarks; + public void updateFormatter(final DoubleArrayList newMajorTickMarks, final double unitScaling) { + this.majorTickMarks.setAll(newMajorTickMarks);; this.unitScaling = unitScaling; this.rangeMin = +Double.MAX_VALUE; this.rangeMax = -Double.MAX_VALUE; - for (Number num : majorTickMarksCopy) { - double val = num.doubleValue(); + for (int i = 0; i < majorTickMarks.size(); i++) { + final double val = majorTickMarks.getDouble(i); if (Double.isFinite(val)) { this.rangeMin = Math.min(this.rangeMin, val); this.rangeMax = Math.max(this.rangeMax, val); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/DefaultFormatter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/DefaultFormatter.java index 9565be8a7..ad714e4cd 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/DefaultFormatter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/format/DefaultFormatter.java @@ -4,6 +4,7 @@ import io.fair_acc.chartfx.axes.TickUnitSupplier; import io.fair_acc.chartfx.utils.NumberFormatterImpl; import io.fair_acc.chartfx.utils.Schubfach; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.util.StringConverter; import java.util.ArrayList; @@ -96,10 +97,10 @@ public Number fromString(final String string) { @Override protected void rangeUpdated() { - if (majorTickMarksCopy != null && majorTickMarksCopy.size() > 0) { + if (majorTickMarks.size() > 0) { final boolean prevForm = formatter.isExponentialForm(); final int prevDecimals = formatter.getDecimalPlaces(); - configureFormatter(getRange(), majorTickMarksCopy); + configureFormatter(getRange(), majorTickMarks); // Clear the cache if the formatting changed if (formatter.isExponentialForm() != prevForm || formatter.getDecimalPlaces() != prevDecimals) { @@ -119,7 +120,7 @@ protected boolean shouldUseExponentialForm(double range, int minExp, int maxExp) return minExp < -3 || maxExp > 4; } - void configureFormatter(double range, List tickMarks) { + void configureFormatter(double range, DoubleArrayList tickMarks) { // Prepare enough cacheable objects final int n = tickMarks.size(); while (decompositions.size() < n) { @@ -128,7 +129,7 @@ void configureFormatter(double range, List tickMarks) { // Decompose the double values into significand and exponents for (int i = 0; i < n; i++) { - double value = tickMarks.get(i) / unitScaling; + double value = tickMarks.getDouble(i) / unitScaling; if (Math.abs(value) < 1E-14 && range > 1E-12) { // treat rounding errors around zero as zero // TODO: diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java index bd61acdff..afc33bfd3 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java @@ -8,11 +8,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import io.fair_acc.chartfx.ui.utils.JavaFXInterceptorUtils; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.geometry.VPos; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; @@ -36,7 +35,6 @@ import io.fair_acc.chartfx.axes.spi.transforms.DefaultAxisTransform; import io.fair_acc.chartfx.legend.spi.DefaultLegend; import io.fair_acc.chartfx.ui.geometry.Side; -import io.fair_acc.chartfx.utils.FXUtils; @ExtendWith(ApplicationExtension.class) @ExtendWith(JavaFXInterceptorUtils.SelectiveJavaFxInterceptor.class) @@ -392,7 +390,7 @@ public boolean isLogAxis() { } @Override - protected List calculateMajorTickValues(final AxisRange axisRange) { + protected void calculateMajorTickValues(final AxisRange axisRange, DoubleArrayList tickValues) { final List majorTicks = new ArrayList<>(); final double range = Math.abs(axisRange.getMax() - axisRange.getMin()); final double min = Math.min(getMin(), getMax()); @@ -403,7 +401,7 @@ protected List calculateMajorTickValues(final AxisRange axisRange) { } @Override - protected List calculateMinorTickValues() { + protected void calculateMinorTickValues(DoubleArrayList tickValues) { final List minorTicks = new ArrayList<>(); final double range = Math.abs(getMax() - getMin()); final double min = Math.min(getMin(), getMax()); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java index 36ab08b65..2e450bac4 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java @@ -177,7 +177,7 @@ public void setterGetterTests() { assertNotNull(axis.getAxisTransform()); // TODO: make proper sanity checks - assertNotNull(axis.calculateMajorTickValues(axis.getRange())); + assertNotNull(axis.calculateMajorTickValues(axis.getRange(), )); assertNotNull(axis.calculateMinorTickValues()); } } diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/fastutil/DoubleArrayList.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/fastutil/DoubleArrayList.java index 4f55e16fc..3441f87e4 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/fastutil/DoubleArrayList.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/spi/fastutil/DoubleArrayList.java @@ -60,6 +60,20 @@ public class DoubleArrayList implements RandomAccess, Cloneable, java.io.Seriali */ protected int size; + /** + * Manually added method that sets the contents of this list to + * be the same as the source. + * @param source desired content + */ + public void setAll(DoubleArrayList source) { + final int size = source.size(); + clear(); + ensureCapacity(size); + for (int i = 0; i < size; i++) { + add(source.getDouble(i)); + } + } + public void forEach(DoubleConsumer consumer) { for (int i = 0; i < size; i++) { consumer.accept(a[i]); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java index a57d4118c..d2cf86299 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisNonLinearSample.java @@ -5,6 +5,7 @@ import java.util.Timer; import java.util.TimerTask; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import javafx.application.Application; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; @@ -192,7 +193,7 @@ public DoubleProperty weightProperty() { } @Override - protected List calculateMajorTickValues(final AxisRange axisRange) { + protected void calculateMajorTickValues(final AxisRange axisRange, DoubleArrayList tickValues) { // TODO: // axisLength used to be passed in, but the ticks should be computed before the length is available. // this example is the only one that depends on the length, but there is already a dependency on @@ -200,10 +201,10 @@ protected List calculateMajorTickValues(final AxisRange axisRange) { // probably be refactored to work on the range directly. final double axisLength = getWidth(); final int nTicks = getMaxMajorTickLabelCount(); - final List tickValues = new ArrayList<>(nTicks); + tickValues.ensureCapacity(nTicks); final double nTicksHalf1 = nTicks * getThreshold(); - final var lower = new ArrayList((int) nTicksHalf1); + final var lower = new DoubleArrayList((int) nTicksHalf1); final double min = getValueForDisplay(0.01 * axisLength); lower.add(min); tickValues.add(min); @@ -216,7 +217,7 @@ protected List calculateMajorTickValues(final AxisRange axisRange) { } final double nTicksHalf2 = nTicks * (1.0 - getThreshold()); - final var upper = new ArrayList((int) nTicksHalf2); + final var upper = new DoubleArrayList((int) nTicksHalf2); final double atThreshold = getValueForDisplay(getThreshold() * axisLength); tickValues.add(atThreshold); // fixed limit at threshold boundary upper.add(atThreshold); @@ -231,7 +232,6 @@ protected List calculateMajorTickValues(final AxisRange axisRange) { } upperFormat.updateFormatter(upper, 1.0); - return tickValues; } @Override From 75e7bf111923896215a247272cb3781c6665f414 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 15:32:50 +0200 Subject: [PATCH 64/81] added support for stroked text --- .../io/fair_acc/chartfx/axes/spi/AbstractAxis.java | 14 ++++++++++---- .../java/io/fair_acc/chartfx/ui/css/TextStyle.java | 9 +++++++-- .../main/resources/io/fair_acc/chartfx/chart.css | 6 +++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index d8a055dc9..8db302abe 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -1,6 +1,7 @@ package io.fair_acc.chartfx.axes.spi; import java.util.List; +import java.util.Objects; import io.fair_acc.chartfx.axes.AxisLabelOverlapPolicy; import io.fair_acc.chartfx.ui.css.PathStyle; @@ -19,6 +20,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javafx.util.StringConverter; @@ -1088,6 +1090,11 @@ protected static void drawAxisLabel(final GraphicsContext gc, final double x, fi } label.copyStyleTo(gc); gc.fillText(label.getText(), 0, 0); + + if (!Objects.equals(gc.getStroke(), Color.TRANSPARENT)) { + gc.strokeText(label.getText(), 0, 0); + } + gc.restore(); } @@ -1110,10 +1117,9 @@ protected static void drawTickMarkLabel(final GraphicsContext gc, final double x gc.fillText(tickMark.getText(), 0, 0); - // TODO: support strokes for outlined labels? - // if (!Objects.equals(style.getStroke(), Color.TRANSPARENT) && !Objects.equals(style.getStroke(), style.getFill())) { - // gc.strokeText(tickMark.getText(), 0, 0); - // } + if (!Objects.equals(gc.getStroke(), Color.TRANSPARENT)) { + gc.strokeText(tickMark.getText(), 0, 0); + } gc.restore(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java index c1774c696..f0195c33d 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java @@ -36,11 +36,16 @@ public TextStyle(String... styles) { */ public void copyStyleTo(GraphicsContext gc) { gc.setFont(getFont()); - gc.setFill(getFill()); - gc.setStroke(getStroke()); gc.setTextAlign(getTextAlignment()); gc.setTextBaseline(getTextOrigin()); + + gc.setFill(getFill()); + + gc.setStroke(getStroke()); + gc.setLineCap(getStrokeLineCap()); + gc.setLineJoin(getStrokeLineJoin()); gc.setLineWidth(getStrokeWidth()); + gc.setGlobalAlpha(getOpacity()); // gc.rotate(getRotate()); // could cause issues with translation } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index bd3bc896b..7d33343ea 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -8,8 +8,12 @@ } .axis-label { - -fx-stroke: rgb(255, 0, 0); -fx-axis-label-alignment: center; + -fx-stroke: transparent; +} + +.axis-tick-label { + -fx-stroke: transparent; } .chart, .chart-content, .chart-plot-area { From a3e490349bbaa55ea5862ac97632ba96a1d911da Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 17:40:41 +0200 Subject: [PATCH 65/81] updated tests to use primitive tickmarks --- .../io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java | 9 +++------ .../chartfx/axes/spi/DefaultNumericAxisTests.java | 7 +++++-- .../fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java | 9 +++++++-- .../chartfx/renderer/spi/HistogramRendererTests.java | 2 -- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java index afc33bfd3..32021e29b 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/AbstractAxisTests.java @@ -390,25 +390,22 @@ public boolean isLogAxis() { } @Override - protected void calculateMajorTickValues(final AxisRange axisRange, DoubleArrayList tickValues) { - final List majorTicks = new ArrayList<>(); + protected void calculateMajorTickValues(final AxisRange axisRange, DoubleArrayList majorTicks) { final double range = Math.abs(axisRange.getMax() - axisRange.getMin()); final double min = Math.min(getMin(), getMax()); for (int i = 0; i < 10; i++) { majorTicks.add(min + i * range / 10.0); } - return majorTicks; + return; } @Override - protected void calculateMinorTickValues(DoubleArrayList tickValues) { - final List minorTicks = new ArrayList<>(); + protected void calculateMinorTickValues(DoubleArrayList minorTicks) { final double range = Math.abs(getMax() - getMin()); final double min = Math.min(getMin(), getMax()); for (int i = 0; i < 100; i++) { minorTicks.add(min + i * range / 100.0); } - return minorTicks; } @Override diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxisTests.java index 02e27315a..7e3820d20 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/DefaultNumericAxisTests.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.ThrowingSupplier; import org.slf4j.Logger; @@ -73,8 +74,9 @@ public void parameterTests() { assertFalse(axis.isLogAxis()); assertEquals(LogAxisType.LINEAR_SCALE, axis.getLogAxisType()); + DoubleArrayList tickValues = new DoubleArrayList(); axis.setLogAxis(true); - axis.calculateMinorTickValues(); + axis.calculateMinorTickValues(tickValues); assertTrue(axis.isLogAxis()); assertEquals(LogAxisType.LOG10_SCALE, axis.getLogAxisType()); axis.setMin(0.1); @@ -88,6 +90,7 @@ public void parameterTests() { axis.setLogAxis(false); assertFalse(axis.isLogAxis()); axis.updateCachedVariables(); - axis.calculateMinorTickValues(); + tickValues.clear(); + axis.calculateMinorTickValues(tickValues); } } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java index 2e450bac4..095264160 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/axes/spi/OscilloscopeAxisTests.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.fair_acc.chartfx.axes.spi.format.DefaultTickUnitSupplier; +import io.fair_acc.dataset.spi.fastutil.DoubleArrayList; import org.junit.jupiter.api.Test; import io.fair_acc.chartfx.axes.LogAxisType; @@ -177,7 +178,11 @@ public void setterGetterTests() { assertNotNull(axis.getAxisTransform()); // TODO: make proper sanity checks - assertNotNull(axis.calculateMajorTickValues(axis.getRange(), )); - assertNotNull(axis.calculateMinorTickValues()); + var ticks = new DoubleArrayList(); + axis.calculateMajorTickValues(axis.getRange(), ticks); + assertEquals(21, ticks.size()); + ticks.clear(); + axis.calculateMinorTickValues(ticks); + assertEquals(90, ticks.size()); } } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/HistogramRendererTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/HistogramRendererTests.java index 8b1d3d295..9a8876e33 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/HistogramRendererTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/HistogramRendererTests.java @@ -98,10 +98,8 @@ void basicInterfaceTests() { final XYChart chart = new XYChart(); assertNotNull(renderer.chartProperty()); assertNull(renderer.getChart()); - assertDoesNotThrow(renderer::requestLayout); assertDoesNotThrow(() -> renderer.setChartChart(chart)); assertEquals(chart, renderer.getChart()); - assertDoesNotThrow(renderer::requestLayout); assertTrue(renderer.isRoundedCorner()); assertNotNull(renderer.roundedCornerProperty()); From bb03ef326d6590a25837ea0485c2326c66472583 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 18:52:22 +0200 Subject: [PATCH 66/81] fixed dataset tests by making limit computation explicit --- .../main/java/io/fair_acc/dataset/DataSet.java | 16 ++++++++++++++++ .../io/fair_acc/dataset/event/EventSource.java | 5 ----- .../spi/CircularDoubleErrorDataSetTests.java | 12 +++++++++--- .../dataset/spi/DataSetEqualityTests.java | 6 ++++-- .../dataset/spi/DimReductionDataSetTests.java | 6 ++---- .../fair_acc/dataset/spi/DoubleDataSetTests.java | 6 +++--- .../dataset/spi/DoubleErrorDataSetTests.java | 4 ++-- .../dataset/spi/DoubleGridDataSetTests.java | 1 + .../fair_acc/dataset/spi/FloatDataSetTests.java | 6 +++--- .../dataset/spi/GenericDataSetTests.java | 13 ++++++++++++- .../dataset/spi/MultiDimDoubleDataSetTests.java | 2 +- .../fair_acc/dataset/utils/DataSetUtilsTest.java | 1 + 12 files changed, 54 insertions(+), 24 deletions(-) diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/DataSet.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/DataSet.java index 04d1afdeb..5c97bb2ba 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/DataSet.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/DataSet.java @@ -4,6 +4,7 @@ import java.util.List; import io.fair_acc.dataset.event.EventSource; +import io.fair_acc.dataset.events.ChartBits; import io.fair_acc.dataset.locks.DataSetLock; /** @@ -113,6 +114,21 @@ default AxisDescription getAxisDescription(int dim) { */ DataSetLock lock(); + /** + * recomputes the limits of all dimensions + * + * @return this + */ + default DataSet recomputeLimits() { + if (getBitState().isDirty(ChartBits.DataSetData, ChartBits.DataSetRange)) { + for (int i = 0; i < getDimension(); i++) { + recomputeLimits(i); + } + getBitState().clear(ChartBits.DataSetData, ChartBits.DataSetRange); + } + return this; + }; + /** * @param dimIndex the dimension to recompute the range for (-1 for all dimensions) * @return itself for method chaining diff --git a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java index b9f4b7267..a4922ed9a 100644 --- a/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java +++ b/chartfx-dataset/src/main/java/io/fair_acc/dataset/event/EventSource.java @@ -34,11 +34,6 @@ default void addListener(StateListener listener) { getBitState().getBits(listener); // initialize to the current state } - @Deprecated // for backwards compatibility with tests - default void addListener(Consumer listener) { - getBitState().addInvalidateListener((src, bits) -> listener.accept(new InvalidatedEvent((EventSource) src))); - } - /** * Removes the given listener from the list of listeners, that are notified whenever the value of the * {@code UpdateSource} becomes invalid. diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSetTests.java index ed932ac33..d0f52d97b 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/CircularDoubleErrorDataSetTests.java @@ -88,17 +88,17 @@ void testThatAddingSingleValuesWillUpdateAxisDescriptionAccordingToNewValue() { assertAxisDescriptionRange(yAxisDescription, Double.NaN, Double.NaN); dataSet.add(1., 2., 0, 0); - + dataSet.recomputeLimits(); assertAxisDescriptionRange(xAxisDescription, 1., 1.); assertAxisDescriptionRange(yAxisDescription, 2., 2.); dataSet.add(2., 3., 0, 0); - + dataSet.recomputeLimits(); assertAxisDescriptionRange(xAxisDescription, 1., 2.); assertAxisDescriptionRange(yAxisDescription, 2., 3.); dataSet.add(3., -1., 0, 0); - + dataSet.recomputeLimits(); assertAxisDescriptionRange(xAxisDescription, 1., 3.); assertAxisDescriptionRange(yAxisDescription, -1., 3.); } @@ -114,6 +114,7 @@ void testThatAddingMultipleValuesWillUpdateAxisDescriptionAccordingToNewValues() new double[] { 10., 20. }, // new double[] { 1., 2. }, // new double[] { 3., 4. }); + dataSet.recomputeLimits(); assertAxisDescriptionRange(xAxisDescription, 1., 2.); assertAxisDescriptionRange(yAxisDescription, 9., 24.); @@ -123,6 +124,7 @@ void testThatAddingMultipleValuesWillUpdateAxisDescriptionAccordingToNewValues() new double[] { 30., 40. }, // new double[] { 1., 2. }, // new double[] { 3., 4. }); + dataSet.recomputeLimits(); assertAxisDescriptionRange(xAxisDescription, 1., 4.); assertAxisDescriptionRange(yAxisDescription, 9., 44.); @@ -132,6 +134,7 @@ void testThatAddingMultipleValuesWillUpdateAxisDescriptionAccordingToNewValues() new double[] { -50., -60. }, // new double[] { 1., 2. }, // new double[] { 3., 4. }); + dataSet.recomputeLimits(); // size of five, the first values gets evicted! assertAxisDescriptionRange(xAxisDescription, 2., 6.); @@ -150,18 +153,21 @@ void testUpdateAxisRange() { assertEquals(Double.NaN, yAxisDescription.getMax()); dataSet.add(1, 1, 0.1, 0.1); + dataSet.recomputeLimits(); assertEquals(1, xAxisDescription.getMin()); assertEquals(1, xAxisDescription.getMax()); assertEquals(0.9, yAxisDescription.getMin()); assertEquals(1.1, yAxisDescription.getMax()); dataSet.add(2, 1, 0.1, 0.1); + dataSet.recomputeLimits(); assertEquals(1, xAxisDescription.getMin()); assertEquals(2, xAxisDescription.getMax()); assertEquals(0.9, yAxisDescription.getMin()); assertEquals(1.1, yAxisDescription.getMax()); dataSet.add(2, 2, 0.1, 0.1); + dataSet.recomputeLimits(); assertEquals(1, xAxisDescription.getMin()); assertEquals(2, xAxisDescription.getMax()); assertEquals(0.9, yAxisDescription.getMin()); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetEqualityTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetEqualityTests.java index e33a47581..5a2e9fd96 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetEqualityTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DataSetEqualityTests.java @@ -43,6 +43,8 @@ void testDataSetEquality() { void testDoubleDataSetEquality() { // NOPMD NOSONAR number of asserts in method final DoubleErrorDataSet ds1 = new DoubleErrorDataSet("default"); final DoubleErrorDataSet ds2 = new DoubleErrorDataSet("default"); + ds1.recomputeLimits(); + ds2.recomputeLimits(); assertEquals(ds1, ds1); assertNotEquals(null, ds1); @@ -180,9 +182,9 @@ void testDoubleDataSetEquality() { // NOPMD NOSONAR number of asserts in method assertEquals(ds1, ds2); // listeners are explicitly not checked - ds1.addListener(e -> System.err.print("do nothing")); + ds1.addListener((src, bits) -> System.err.print("do nothing")); assertEquals(ds1, ds2); - ds2.addListener(e -> System.err.print("do also nothing")); + ds2.addListener((src, bits) -> System.err.print("do also nothing")); assertEquals(ds1, ds2); } diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java index 1a34c405c..e42920a97 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java @@ -264,10 +264,8 @@ public void testSliceOptions() { Assertions.assertEquals(DimReductionDataSet.Option.SLICE, sliceDataSetX.getReductionOption(), "reduction option"); nEvent.set(0); - sliceDataSetX.addListener(evt -> { - if (evt instanceof AddedDataEvent) { - nEvent.incrementAndGet(); - } + sliceDataSetX.getBitState().addInvalidateListener(ChartBits.DataSetDataAdded, (src, bits) -> { + nEvent.incrementAndGet(); }); testData.fireInvalidated(ChartBits.DataSetData); Awaitility.await().atMost(1, TimeUnit.SECONDS).alias("DataSet3D event propagated").until(() -> nEvent.get() == 1); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleDataSetTests.java index f97ee027f..3b4888b84 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleDataSetTests.java @@ -28,11 +28,11 @@ public void defaultTests() { checkAddPoints(firstDataSet, 0); // w/o errors final DoubleDataSet secondDataSetA = new DoubleDataSet("test", testCoordinate[0], testCoordinate[1], n, true); - assertEquals(firstDataSet, secondDataSetA, "DoubleDataSet(via arrays, deep copy) constructor"); + assertEquals(firstDataSet.recomputeLimits(), secondDataSetA.recomputeLimits(), "DoubleDataSet(via arrays, deep copy) constructor"); final DoubleDataSet secondDataSetB = new DoubleDataSet("test", Arrays.copyOf(testCoordinate[0], n), Arrays.copyOf(testCoordinate[1], n), n, false); - assertEquals(firstDataSet, secondDataSetB, "DoubleDataSet(via arrays, no deep copy) constructor"); + assertEquals(firstDataSet.recomputeLimits(), secondDataSetB.recomputeLimits(), "DoubleDataSet(via arrays, no deep copy) constructor"); checkAddPoints(firstDataSet, 1); // X, Y, and label @@ -50,7 +50,7 @@ public void defaultTests() { firstDataSet.addDataStyle(0, "color: red"); final DoubleDataSet thirdDataSet = new DoubleDataSet(firstDataSet); - assertEquals(firstDataSet, thirdDataSet, "DoubleDataSet(DataSet2D) constructor"); + assertEquals(firstDataSet.recomputeLimits(), thirdDataSet.recomputeLimits(), "DoubleDataSet(DataSet2D) constructor"); assertNotEquals(0, firstDataSet.getDataCount(), "pre-check clear method"); firstDataSet.clearData(); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleErrorDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleErrorDataSetTests.java index 8151d9dd4..11fb1d495 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleErrorDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleErrorDataSetTests.java @@ -29,12 +29,12 @@ public void defaultTests() { final DoubleErrorDataSet secondDataSetA = new DoubleErrorDataSet("test", testCoordinate[0], testCoordinate[1], testEYZERO, testEYZERO, n, true); - assertEquals(firstDataSet, secondDataSetA, "DoubleErrorDataSet(via arrays, deep copy) constructor"); + assertEquals(firstDataSet.recomputeLimits(), secondDataSetA.recomputeLimits(), "DoubleErrorDataSet(via arrays, deep copy) constructor"); final DoubleErrorDataSet secondDataSetB = new DoubleErrorDataSet("test", Arrays.copyOf(testCoordinate[0], n), Arrays.copyOf(testCoordinate[1], n), Arrays.copyOf(testEYZERO, n), Arrays.copyOf(testEYZERO, n), n, false); - assertEquals(firstDataSet, secondDataSetB, "DoubleErrorDataSet(via arrays, no deep copy) constructor"); + assertEquals(firstDataSet.recomputeLimits(), secondDataSetB.recomputeLimits(), "DoubleErrorDataSet(via arrays, no deep copy) constructor"); checkAddPoints(firstDataSet, 1); // with errors but w/o label and style diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java index bc1de9ba3..ba9c7f961 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DoubleGridDataSetTests.java @@ -179,6 +179,7 @@ void testCopyConstructor() { void testSettersAndListeners() { double[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", false, new double[][] { { 0.1, 0.2 }, { 1.1, 2.2, 3.3 }, { -0.5, 0.5 } }, data); + dataset.recomputeLimits(); dataset.set(3, new int[] { 1, 2, 1 }, 23.0); assertEquals(23.0, dataset.get(3, 1, 2, 1)); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/FloatDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/FloatDataSetTests.java index 5d3fb602c..4da374b86 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/FloatDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/FloatDataSetTests.java @@ -26,11 +26,11 @@ public void defaultTests() { checkAddPoints(firstDataSet, 0); // w/o errors final FloatDataSet secondDataSetA = new FloatDataSet("test", testCoordinate[0], testCoordinate[1], n, true); - assertEquals(firstDataSet, secondDataSetA, "FloatDataSet(via arrays, deep copy) constructor"); + assertEquals(firstDataSet.recomputeLimits(), secondDataSetA.recomputeLimits(), "FloatDataSet(via arrays, deep copy) constructor"); final FloatDataSet secondDataSetB = new FloatDataSet("test", Arrays.copyOf(testCoordinate[0], n), Arrays.copyOf(testCoordinate[1], n), n, false); - assertEquals(firstDataSet, secondDataSetB, "FloatDataSet(via arrays, no deep copy) constructor"); + assertEquals(firstDataSet.recomputeLimits(), secondDataSetB.recomputeLimits(), "FloatDataSet(via arrays, no deep copy) constructor"); checkAddPoints(firstDataSet, 1); // X, Y, and label @@ -45,7 +45,7 @@ public void defaultTests() { checkAddPoints(firstDataSet, 6); // X, Y (via arrays and in front) but w/o label final FloatDataSet thirdDataSet = new FloatDataSet(firstDataSet); - assertEquals(firstDataSet, thirdDataSet, "FloatDataSet(DataSet2D) constructor"); + assertEquals(firstDataSet.recomputeLimits(), thirdDataSet.recomputeLimits(), "FloatDataSet(DataSet2D) constructor"); assertNotEquals(0, firstDataSet.getDataCount(), "pre-check clear method"); firstDataSet.clearData(); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java index 8e0fbb909..99c0a2749 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/GenericDataSetTests.java @@ -12,6 +12,7 @@ import java.util.stream.Stream; import io.fair_acc.dataset.event.UpdatedMetaDataEvent; +import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; @@ -93,8 +94,12 @@ void testSetDataSet(final Class clazz) throws AssertionError { testDataSetError.recomputeLimits(plane); } + final AtomicInteger notifyCounter = new AtomicInteger(); - dataSet.addListener(evt -> notifyCounter.getAndIncrement()); + final int bit = BitState.mask(ChartBits.DataSetDataAdded); + dataSet.getBitState().addChangeListener(bit, (src, bits) -> notifyCounter.getAndIncrement()); + + dataSet.getBitState().clear(bit); assertDoesNotThrow(() -> dataSet.set(testDataSet)); assertSameDataRanges(testDataSet, dataSet); dataSet.recomputeLimits(DIM_X); @@ -102,10 +107,13 @@ void testSetDataSet(final Class clazz) throws AssertionError { assertSameDataRanges(testDataSet, dataSet); assertEquals(1, notifyCounter.get()); + dataSet.getBitState().clear(bit); assertDoesNotThrow(() -> dataSet.set(testDataSet, true)); assertEquals(2, notifyCounter.get()); + dataSet.getBitState().clear(bit); assertDoesNotThrow(() -> dataSet.set(testDataSet, false)); assertEquals(3, notifyCounter.get()); + dataSet.getBitState().clear(bit); notifyCounter.set(0); assertDoesNotThrow(() -> dataSet.set(testDataSetError)); @@ -115,10 +123,13 @@ void testSetDataSet(final Class clazz) throws AssertionError { assertSameDataRanges(testDataSetError, dataSet); assertEquals(1, notifyCounter.get()); + dataSet.getBitState().clear(bit); assertDoesNotThrow(() -> dataSet.set(testDataSetError, true)); assertEquals(2, notifyCounter.get()); + dataSet.getBitState().clear(bit); assertDoesNotThrow(() -> dataSet.set(testDataSetError, false)); assertEquals(3, notifyCounter.get()); + dataSet.getBitState().clear(bit); } public static void assertSameDataRanges(final DataSet reference, final DataSet test) throws AssertionError { diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSetTests.java index 7204d0546..447045d3d 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/MultiDimDoubleDataSetTests.java @@ -119,7 +119,7 @@ public void testCopyConstructors() { DoubleDataSet doubleDataSet = new DoubleDataSet("doubleTest", new double[] { 1, 2, 3, 4 }, new double[] { 10, 20, 30, 40 }, 4, false); MultiDimDoubleDataSet multiDimFromDoubleDataSet = new MultiDimDoubleDataSet(doubleDataSet); - assertEquals(doubleDataSet, multiDimFromDoubleDataSet); + assertEquals(doubleDataSet.recomputeLimits(), multiDimFromDoubleDataSet.recomputeLimits()); assertEquals(multiDimFromDoubleDataSet, doubleDataSet); } diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/utils/DataSetUtilsTest.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/utils/DataSetUtilsTest.java index cd9486d55..dc54146d1 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/utils/DataSetUtilsTest.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/utils/DataSetUtilsTest.java @@ -255,6 +255,7 @@ void testGenerateFileName() { .setMetaInfoMap(Map.of("metaInt", "1337", "metaString", "testMetaInfo", "metaDouble", "1.337", "metaEng", "1.33e-7", "acqStamp", Long.toString(acqStamp))) .build(); + dataSet.recomputeLimits(); assertEquals("file.bin.gz", DataSetUtils.getFileName(dataSet, "file.bin.gz")); assertEquals("file_metaDataFieldMissing.bin.gz", DataSetUtils.getFileName(dataSet, "file_{}.bin.gz")); assertEquals("dsName", DataSetUtils.getFileName(dataSet, "{dataSetName}")); From 638ad8889758328ffbbaed52e355daabeb038194 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 18:56:29 +0200 Subject: [PATCH 67/81] @Alex: disabled some tests that will need to be updated --- .../java/io/fair_acc/chartfx/plugins/TableViewerTest.java | 3 +++ .../chartfx/plugins/YWatchValueIndicatorTest.java | 2 +- .../plugins/measurements/SimpleMeasurementsTests.java | 8 +++++--- .../io/fair_acc/dataset/spi/DimReductionDataSetTests.java | 3 +++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java index c1a2eef7d..9cf2cb3c8 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java @@ -21,6 +21,7 @@ import org.hamcrest.Matcher; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testfx.api.FxAssert; @@ -91,6 +92,7 @@ public void testThatTableIsOnlyAddedToSceneWhenActive() throws TimeoutException FxAssert.verifyThat(chart.getPlotForeground(), Matchers.not(NodeMatchers.hasChild(".table-view"))); } + @Disabled // TODO: fix after layout refactoring. Where did the .table-view element come from? @Test public void testThatTableCellsAreClickable() throws TimeoutException { // NOPMD JUnitTestsShouldIncludeAssert fxRobot.interact(() -> { @@ -128,6 +130,7 @@ public void testThatTableCellsAreClickable() throws TimeoutException { // NOPMD assertEquals(valueAtX1, selectedItem.getValue(dataset, ColumnType.Y)); } + @Disabled // TODO: fix after layout refactoring. Where did the .table-view element come from? @Test public void testThatDataSetsRowHashCodeEqualsWorks() throws TimeoutException { fxRobot.interact(() -> { diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java index ab0909237..8684f217e 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java @@ -75,7 +75,7 @@ void rightSide() { @TestFx void setMarkerValue() { valueWatchIndicatorTested.setMarkerValue(35.15); - assertEquals("35", valueWatchIndicatorTested.getText()); + assertTrue(valueWatchIndicatorTested.getText().matches("35[\\.,]15")); // US or German locale assertEquals(35.15, valueWatchIndicatorTested.getValue(), 1e-2); } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java index 21beddb15..9abeb3565 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurementsTests.java @@ -20,6 +20,7 @@ import javafx.stage.Stage; import org.awaitility.Awaitility; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; @@ -45,6 +46,7 @@ * * @author rstein */ +@Disabled // TODO: fix when measurements work properly again @ExtendWith(ApplicationExtension.class) @ExtendWith(JavaFXInterceptorUtils.SelectiveJavaFxInterceptor.class) class SimpleMeasurementsTests { @@ -190,7 +192,7 @@ public void testSimpleMeasurements() throws Exception { // NOPMD assertNotNull(field.getDataSet(), "DataSet is null for type = " + type); - field.getValueIndicators().forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(1, indicator.getBitState().size(), "error for type = " + type)); + // TODO: field.getValueIndicators().forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(1, indicator.getBitState().size(), "error for type = " + type)); final int nXIndicators = (int) chart.getPlugins().stream().filter(p -> p instanceof XValueIndicator).count(); assertEquals(type.isVerticalMeasurement() ? type.getRequiredSelectors() : 0, nXIndicators, "error for type = " + type); final int nYIndicators = (int) chart.getPlugins().stream().filter(p -> p instanceof YValueIndicator).count(); @@ -209,7 +211,7 @@ public void testSimpleMeasurements() throws Exception { // NOPMD }); // trigger DataSet update - sine.invokeListener(); + // TODO: sine.invokeListener(); // force field computation // FXUtils.runAndWait(() -> field.handle(null)); assertTrue(FXUtils.waitForFxTicks(chart.getScene(), 3, 1000), "wait for handler to update"); @@ -219,7 +221,7 @@ public void testSimpleMeasurements() throws Exception { // NOPMD final List tmp = new ArrayList<>(field.getValueIndicators()); FXUtils.runAndWait(field::removeAction); - tmp.forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(0, indicator.getBitState().size())); + // TODO: tmp.forEach((final AbstractSingleValueIndicator indicator) -> assertEquals(0, indicator.getBitState().size())); // Assert that there are no Indicators left after removing the measurement assertEquals(0, chart.getPlugins().stream().filter(p -> p instanceof AbstractSingleValueIndicator).count(), "error for type = " + type); diff --git a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java index e42920a97..ae54fbebb 100644 --- a/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java +++ b/chartfx-dataset/src/test/java/io/fair_acc/dataset/spi/DimReductionDataSetTests.java @@ -12,6 +12,7 @@ import io.fair_acc.dataset.events.ChartBits; import org.awaitility.Awaitility; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** @@ -245,6 +246,7 @@ public void testMinOptions() { assertArrayEquals(minY, sliceDataSetY.getValues(DataSet.DIM_Y), "y-min"); } + @Disabled // TODO: update does not get triggered automatically. check after refactoring dependent data sets @Test public void testSliceOptions() { GridDataSet testData = new DataSetBuilder("test") // @@ -287,6 +289,7 @@ public void testSliceOptions() { assertArrayEquals(new double[] { 2, 5, 8 }, sliceDataSetY.getValues(DataSet.DIM_Y), "second column match"); } + @Disabled // TODO: update does not get triggered automatically. check after refactoring dependent data sets @Test public void testInvalid2DInputDataSet() { GridDataSet testData = new DoubleGridDataSet("test", false, new double[][] { { 1, 2, 3 } }, new double[] { 6, 7, 8 }); From 9631ca58b9bff35a0a25fa5765547c15c7b44347 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 19:03:49 +0200 Subject: [PATCH 68/81] minor change to keep sonarcloud happy --- .../io/fair_acc/chartfx/viewer/DataViewWindow.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java index 0fbff744f..e5b0686ea 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/viewer/DataViewWindow.java @@ -154,16 +154,8 @@ public void set(final WindowState state) { } updatingStage.set(true); - if (this.isMaximised()) { - fireInvalidated(ChartBits.DataViewWindow); - } else { - // either minimised, normal (and/or detached) state - if (isMinimised()) { - fireInvalidated(ChartBits.DataViewWindow); - } else { - fireInvalidated(ChartBits.DataViewWindow); - } - } + fireInvalidated(ChartBits.DataViewWindow); + if (dialog.isShowing()) { // enlarge to maximum screen size dialog.maximizeRestore(this); From 38e925be8c563e269e0c42c0a64a4477d089f0e5 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 20:26:22 +0200 Subject: [PATCH 69/81] added deregistration of legend item listener to avoid potential memory leaks --- .../fair_acc/chartfx/legend/spi/DefaultLegend.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java index 0e463eae3..ebcfccd98 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java @@ -118,9 +118,15 @@ public LegendItem getNewLegendItem(final Renderer renderer, final DataSet series final Canvas symbol = renderer.drawLegendSymbol(series, seriesIndex, SYMBOL_WIDTH, SYMBOL_HEIGHT); var item = new LegendItem(series.getName(), symbol); item.setOnMouseClicked(event -> series.setVisible(!series.isVisible())); - item.pseudoClassStateChanged(disabledClass, !series.isVisible()); - series.getBitState().addInvalidateListener(ChartBits.DataSetVisibility, (obj, bits) -> { - item.pseudoClassStateChanged(disabledClass, !series.isVisible()); // TODO: do we need to unregister? It did not before. + Runnable updateCss = () -> item.pseudoClassStateChanged(disabledClass, !series.isVisible()); + updateCss.run(); + StateListener listener = (obj, bits) -> updateCss.run(); + item.sceneProperty().addListener((obs, oldScene, scene) -> { + if (scene == null) { + series.getBitState().removeInvalidateListener(listener); + } else if (oldScene == null) { + series.getBitState().addInvalidateListener(ChartBits.DataSetVisibility, listener); + } }); return item; } From 5daad7bdc3ac3f437871ba623ad570d544317e8e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Wed, 2 Aug 2023 20:58:41 +0200 Subject: [PATCH 70/81] fixed an issue where switching datasets with the same name were not updating the legend properly --- .../src/main/java/io/fair_acc/chartfx/legend/Legend.java | 3 ++- .../java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java index b8d9da239..bdea8512a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/Legend.java @@ -21,7 +21,8 @@ public interface Legend { * @param renderers corresponding renderers */ default void updateLegend(List dataSets, List renderers) { - updateLegend(dataSets, renderers, false); + // TODO: we currently force an update because the diff checker could link the visibility clicks to the wrong ds + updateLegend(dataSets, renderers, true); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java index ebcfccd98..591c0a34a 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/legend/spi/DefaultLegend.java @@ -119,12 +119,12 @@ public LegendItem getNewLegendItem(final Renderer renderer, final DataSet series var item = new LegendItem(series.getName(), symbol); item.setOnMouseClicked(event -> series.setVisible(!series.isVisible())); Runnable updateCss = () -> item.pseudoClassStateChanged(disabledClass, !series.isVisible()); - updateCss.run(); StateListener listener = (obj, bits) -> updateCss.run(); item.sceneProperty().addListener((obs, oldScene, scene) -> { if (scene == null) { series.getBitState().removeInvalidateListener(listener); } else if (oldScene == null) { + updateCss.run(); // changing pseudo class in CSS does not trigger another pulse series.getBitState().addInvalidateListener(ChartBits.DataSetVisibility, listener); } }); From 86dc664c19db898ea47aff1326a6e2137e7df13e Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 3 Aug 2023 00:40:02 +0200 Subject: [PATCH 71/81] fixed an issue where the layout hooks would not be added on a manual resize --- .../io/fair_acc/chartfx/ui/utils/LayoutHook.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java index 1af4bd64b..4c03bb902 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java @@ -2,7 +2,6 @@ import io.fair_acc.dataset.utils.AssertUtils; import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.scene.Node; import javafx.scene.Scene; @@ -62,6 +61,17 @@ private LayoutHook(Node node, Runnable preLayoutAction, Runnable postLayoutActio if (newValue != null) { runPreLayoutAndAdd(); } + + // Also register scene size triggers to catch any manual resizing before + // the pulse begins. + if (oldValue != null) { + oldValue.widthProperty().removeListener(windowResizeListener); + oldValue.heightProperty().removeListener(windowResizeListener); + } + if (newValue != null) { + newValue.widthProperty().addListener(windowResizeListener); + newValue.heightProperty().addListener(windowResizeListener); + } }); } @@ -149,6 +159,7 @@ private void unregister() { final Runnable preLayoutAndAdd = this::runPreLayoutAndAdd; final Runnable postLayoutAndRemove = this::runPostLayoutAndRemove; + final ChangeListener windowResizeListener = (obs, old, size) -> registerOnce(); Scene registeredScene = null; } From 3ea64baca8e632ce9a7af0730a75f09ad4d11142 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 3 Aug 2023 12:45:53 +0200 Subject: [PATCH 72/81] added a length update in axis layout --- .../java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java | 9 ++++++++- .../java/io/fair_acc/chartfx/ui/layout/ChartPane.java | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 8db302abe..2b1a5bc44 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -242,7 +242,7 @@ protected void updateDirtyContent(double length) { } protected void updateAxisRange(double length) { - if(state.isClean(ChartBits.AxisRange, ChartBits.AxisTickLabelText) && length == getLength()) { + if (state.isClean(ChartBits.AxisRange, ChartBits.AxisTickLabelText) && length == getLength()) { return; } @@ -875,6 +875,13 @@ protected void layoutChildren() { // to guarantee ordering (e.g. ticks are available before the grid) canvas.resizeRelocate(-canvasPadX, -canvasPadY, getWidth() + 2 * canvasPadX, getHeight() + 2 * canvasPadY); + // Update actual displayed length in case the layout container decides + // to not provide the preferred size that was requested. Most of the time + // this is already correct, but it can differ due to e.g. rounding or if they + // are inside a non-custom container. + var length = getSide().isHorizontal() ? getWidth() : getHeight(); + updateDirtyContent(length); + // Only called on actual size changes, so definitely redraw invalidateCanvas.run(); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java index e90b3b0ce..355ab63dc 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/layout/ChartPane.java @@ -233,7 +233,7 @@ protected void layoutChildren() { } i++; } - final double contentWidth = width - leftWidth - rightWidth; + final double contentWidth = snapSizeX(width - leftWidth - rightWidth); // (3) Determine the height of all horizontal parts. The labels are generated // for the actual width, so the placement should be correct. @@ -263,7 +263,7 @@ protected void layoutChildren() { } i++; } - final double contentHeight = height - topHeight - bottomHeight; + final double contentHeight = snapSizeY(height - topHeight - bottomHeight); // Layout all center content final var xContent = xLeft + leftWidth; From 37d760fe513e79da1f7dfd5c1267938727fa4aed Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 3 Aug 2023 14:38:56 +0200 Subject: [PATCH 73/81] fixed an issue that caused unnecessary redraws on hidden menu animations --- .../main/java/io/fair_acc/chartfx/Chart.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 2b8e30b2c..dfa57a4d7 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -589,21 +589,23 @@ public void layoutChildren() { child.resizeRelocate(x, y, w, h); } - // request re-layout of plugins - if(layoutHooks.hasRunPreLayout()) { - // datasets are locked, so we can add plugins + // Note: size changes should maybe trigger a redraw, but this + // creates problems with the HiddenSidesPane because for some + // reason the size fluctuates a few pixels during animations. + // Commenting this out prevents unnecessary redraws, and it + // looks like actual size changes still trigger subsequent + // events through other components (e.g. axes). + // fireInvalidated(ChartBits.ChartCanvas); + + // Note: there are some rare corner cases, e.g., computing + // the pref size of the scene (and the HiddenSidesPane), + // that call for a layout without calling the hooks. The + // plugins may rely on datasets being locked, so we skip + // the update to be safe. + if (layoutHooks.hasRunPreLayout()) { layoutPluginsChildren(); - } else { - // There are some rare corner cases, e.g., computing - // the pref size of the scene, that call for a layout - // without calling the hooks. The plugins may rely - // on datasets being locked, so we try again next - // pulse to be safe. - Platform.runLater(this::requestLayout); } - // Make sure things will get redrawn - fireInvalidated(ChartBits.ChartCanvas); } protected void runPostLayout() { From 32d8d0e0ebf1e50c8739306c69ad112faf5e6ff5 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Thu, 3 Aug 2023 19:16:48 +0200 Subject: [PATCH 74/81] fixed grid renderer CSS styling --- .../main/java/io/fair_acc/chartfx/Chart.java | 32 +++-- .../java/io/fair_acc/chartfx/XYChart.java | 29 ++-- .../chartfx/axes/spi/AbstractAxis.java | 4 +- .../axes/spi/AbstractAxisParameter.java | 11 +- .../fair_acc/chartfx/renderer/Renderer.java | 4 + .../chartfx/renderer/spi/GridRenderer.java | 133 ++++++------------ .../ui/css/{PathStyle.java => LineStyle.java} | 27 ++-- .../io/fair_acc/chartfx/ui/css/StyleUtil.java | 85 ++++++++++- .../io/fair_acc/chartfx/ui/css/TextStyle.java | 24 +--- .../resources/io/fair_acc/chartfx/chart.css | 10 +- 10 files changed, 200 insertions(+), 159 deletions(-) rename chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/{PathStyle.java => LineStyle.java} (59%) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index dfa57a4d7..24b1c9954 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -557,12 +557,16 @@ public boolean isToolBarPinned() { return toolBarPinned.get(); } - protected void runPreLayout() { - forEachDataSet(ds -> lockedDataSets.add(ds.lock().readLock())); - - if (state.isDirty(ChartBits.ChartLegend, ChartBits.ChartDataSets, ChartBits.ChartRenderers)) { + protected void updateLegend() { + if (state.isDirty(ChartBits.ChartLegend)) { updateLegend(getDatasets(), getRenderers()); } + state.clear(ChartBits.ChartLegend); + } + + protected void runPreLayout() { + forEachDataSet(ds -> lockedDataSets.add(ds.lock().readLock())); + updateLegend(); final long start = ProcessingProfiler.getTimeStamp(); updateAxisRange(); // Update data ranges etc. to trigger anything that might need a layout @@ -619,27 +623,39 @@ protected void runPostLayout() { axis.drawAxis(); } redrawCanvas(); + for (Renderer renderer : renderers) { + renderer.runPostLayout(); + } + for (var plugin : plugins) { + plugin.runPostLayout(); + } + clearStates(); + + // TODO: plugins etc., do locking + ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); + } + + protected void clearStates() { for (var renderer : getRenderers()) { if (renderer instanceof EventSource) { ((EventSource) renderer).getBitState().clear(); } } + for (var plugin : plugins) { - plugin.runPostLayout(); if (plugin instanceof EventSource) { ((EventSource) plugin).getBitState().clear(); } } + dataSetState.clear(); state.clear(); + for (var ds : lockedDataSets) { ds.getBitState().clear(); // technically a 'write' ds.lock().readUnLock(); } lockedDataSets.clear(); - - // TODO: plugins etc., do locking - ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 98086d739..a6af84f62 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -1,31 +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 io.fair_acc.chartfx.utils.PropUtil; import io.fair_acc.dataset.events.ChartBits; -import javafx.animation.KeyFrame; -import javafx.animation.Timeline; -import javafx.application.Platform; 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; @@ -58,7 +49,6 @@ public class XYChart extends Chart { protected final BooleanProperty polarPlot = new SimpleBooleanProperty(this, "polarPlot", false); private final ObjectProperty polarStepSize = new SimpleObjectProperty<>(PolarTickStep.THIRTY); private final GridRenderer gridRenderer = new GridRenderer(); - protected final ChangeListener gridLineVisibilitychange = (ob, o, n) -> requestLayout(); /** * Construct a new XYChart with the given axes. @@ -96,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); diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java index 2b1a5bc44..5a237dd08 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxis.java @@ -4,7 +4,7 @@ import java.util.Objects; import io.fair_acc.chartfx.axes.AxisLabelOverlapPolicy; -import io.fair_acc.chartfx.ui.css.PathStyle; +import io.fair_acc.chartfx.ui.css.LineStyle; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.utils.FXUtils; import io.fair_acc.chartfx.utils.PropUtil; @@ -817,7 +817,7 @@ protected void drawTickLabels(final GraphicsContext gc, final double axisWidth, protected void drawTickMarks(final GraphicsContext gc, final double axisLength, final double axisWidth, final double axisHeight, final ObservableList tickMarks, final double tickLength, - final PathStyle tickStyle) { + final LineStyle tickStyle) { if (tickLength <= 0) { return; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 398ce3a94..67eb92310 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.Objects; -import io.fair_acc.chartfx.ui.css.PathStyle; +import io.fair_acc.chartfx.ui.css.LineStyle; import io.fair_acc.chartfx.ui.css.StyleUtil; import io.fair_acc.chartfx.ui.css.TextStyle; import io.fair_acc.chartfx.ui.layout.ChartPane; @@ -189,10 +189,11 @@ public AbstractAxisParameter() { * Note that we can use the tick label style as a temporary node for getting the font metrics w/ the correct style. * Unmanaged nodes do not trigger a re-layout of the parent, but invisible text still computes valid font metrics. */ - private final transient PathStyle majorTickStyle = new PathStyle("axis-tick-mark"); - private final transient PathStyle minorTickStyle =new PathStyle( "axis-minor-tick-mark"); + private final transient LineStyle majorTickStyle = new LineStyle("axis-tick-mark"); + private final transient LineStyle minorTickStyle = new LineStyle("axis-minor-tick-mark"); private final transient TextStyle tickLabelStyle = new TextStyle("axis-tick-label"); private final transient TextStyle axisLabel = new TextStyle("axis-label"); + { StyleUtil.addStyles(this, "axis"); getChildren().addAll(axisLabel, tickLabelStyle, majorTickStyle, minorTickStyle); @@ -602,7 +603,7 @@ public double getLength() { /** * @return the majorTickStyle for custom user-code based styling */ - public PathStyle getMajorTickStyle() { + public LineStyle getMajorTickStyle() { return majorTickStyle; } @@ -647,7 +648,7 @@ public DoubleArrayList getMinorTickMarkValues() { /** * @return the minorTickStyle for custom user-code based styling */ - public PathStyle getMinorTickStyle() { + public LineStyle getMinorTickStyle() { return minorTickStyle; } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java index 7fe049617..94713cd1c 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/Renderer.java @@ -43,6 +43,10 @@ default void runPreLayout() { // #NOPMD // empty by default } + default void runPostLayout() { // #NOPMD + // empty by default + } + /** * * @param gc the Canvas' GraphicsContext the renderer should draw upon diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java index 7c927a4fe..033c4dc59 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/renderer/spi/GridRenderer.java @@ -3,24 +3,18 @@ import java.security.InvalidParameterException; import java.util.Collections; import java.util.List; -import java.util.Objects; +import io.fair_acc.chartfx.ui.css.LineStyle; +import io.fair_acc.chartfx.ui.css.StyleUtil; import javafx.beans.property.BooleanProperty; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.collections.SetChangeListener; -import javafx.css.CssMetaData; import javafx.css.PseudoClass; -import javafx.css.Styleable; import javafx.geometry.VPos; -import javafx.scene.Group; import javafx.scene.Node; -import javafx.scene.Scene; +import javafx.scene.Parent; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; -import javafx.scene.layout.Pane; -import javafx.scene.shape.Line; import javafx.scene.text.TextAlignment; import io.fair_acc.chartfx.Chart; @@ -33,9 +27,8 @@ import io.fair_acc.dataset.utils.NoDuplicatesList; @SuppressWarnings("PMD.GodClass") -public class GridRenderer extends Pane implements Renderer { +public class GridRenderer extends Parent implements Renderer { private static final double DEG_TO_RAD = Math.PI / 180.0; - private static final String CHART_CSS = Objects.requireNonNull(Chart.class.getResource("chart.css")).toExternalForm(); private static final String STYLE_CLASS_GRID_RENDERER = "grid-renderer"; private static final String STYLE_CLASS_MAJOR_GRID_LINE = "chart-major-grid-lines"; private static final String STYLE_CLASS_MAJOR_GRID_LINE_H = "chart-major-horizontal-lines"; @@ -44,78 +37,43 @@ public class GridRenderer extends Pane implements Renderer { private static final String STYLE_CLASS_MINOR_GRID_LINE_H = "chart-minor-horizontal-lines"; private static final String STYLE_CLASS_MINOR_GRID_LINE_V = "chart-minor-vertical-lines"; private static final String STYLE_CLASS_GRID_ON_TOP = "chart-grid-line-on-top"; - private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("withMinor"); + private static final PseudoClass WITH_MINOR_PSEUDO_CLASS = PseudoClass.getPseudoClass("withMinor"); private static final double[] DEFAULT_GRID_DASH_PATTERM = { 4.5, 2.5 }; - // protected final BooleanProperty drawGridOnTop = new - // SimpleStyleableBooleanProperty(StyleableProperties.GRID_ON_TOP, - // this, "drawGridOnTop", true); - private final Line horMajorGridStyleNode; - private final Line verMajorGridStyleNode; - private final Line horMinorGridStyleNode; - private final Line verMinorGridStyleNode; - private final Line drawGridOnTopNode; - private final Group gridStyleNodes = new Group(); + + private final LineStyle horMajorGridStyleNode = new LineStyle(false, + STYLE_CLASS_MAJOR_GRID_LINE, + STYLE_CLASS_MAJOR_GRID_LINE_H); + private final LineStyle verMajorGridStyleNode = new LineStyle(false, + STYLE_CLASS_MAJOR_GRID_LINE, + STYLE_CLASS_MAJOR_GRID_LINE_V + ); + private final LineStyle horMinorGridStyleNode = new LineStyle(false, + STYLE_CLASS_MINOR_GRID_LINE, + STYLE_CLASS_MINOR_GRID_LINE_H + ); + private final LineStyle verMinorGridStyleNode = new LineStyle(false, + STYLE_CLASS_MINOR_GRID_LINE, + STYLE_CLASS_MINOR_GRID_LINE_V + ); + private final LineStyle drawGridOnTopNode = new LineStyle(false, + STYLE_CLASS_GRID_ON_TOP + ); + protected final ObservableList axesList = FXCollections.observableList(new NoDuplicatesList<>()); public GridRenderer() { super(); - - getStyleClass().setAll(GridRenderer.STYLE_CLASS_GRID_RENDERER); - horMajorGridStyleNode = new Line(); - horMajorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MAJOR_GRID_LINE); - horMajorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MAJOR_GRID_LINE_H); - - verMajorGridStyleNode = new Line(); - verMajorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MAJOR_GRID_LINE); - verMajorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MAJOR_GRID_LINE_V); - - horMinorGridStyleNode = new Line(); - horMinorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MINOR_GRID_LINE); - horMinorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MINOR_GRID_LINE_H); - horMinorGridStyleNode.setVisible(false); - - verMinorGridStyleNode = new Line(); - verMinorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MINOR_GRID_LINE); - verMinorGridStyleNode.getStyleClass().add(GridRenderer.STYLE_CLASS_MINOR_GRID_LINE_V); - verMinorGridStyleNode.setVisible(false); - - drawGridOnTopNode = new Line(); - drawGridOnTopNode.getStyleClass().add(GridRenderer.STYLE_CLASS_GRID_ON_TOP); - drawGridOnTopNode.getStyleClass().add(GridRenderer.STYLE_CLASS_GRID_ON_TOP); - drawGridOnTopNode.setVisible(true); - - gridStyleNodes.getChildren().addAll(horMajorGridStyleNode, verMajorGridStyleNode, horMinorGridStyleNode, - verMinorGridStyleNode, drawGridOnTopNode); - - getChildren().add(gridStyleNodes); - final Scene scene = new Scene(this); - scene.getStylesheets().add(GridRenderer.CHART_CSS); - gridStyleNodes.applyCss(); - final SetChangeListener listener = evt -> gridStyleNodes.applyCss(); - horMajorGridStyleNode.getPseudoClassStates().addListener(listener); - verMajorGridStyleNode.getPseudoClassStates().addListener(listener); - horMinorGridStyleNode.getPseudoClassStates().addListener(listener); - verMinorGridStyleNode.getPseudoClassStates().addListener(listener); - drawGridOnTopNode.getPseudoClassStates().addListener(listener); - - ChangeListener change = (ob, o, n) -> { - horMajorGridStyleNode.pseudoClassStateChanged(GridRenderer.SELECTED_PSEUDO_CLASS, - horMinorGridStyleNode.isVisible()); - verMajorGridStyleNode.pseudoClassStateChanged(GridRenderer.SELECTED_PSEUDO_CLASS, - verMinorGridStyleNode.isVisible()); - drawGridOnTopNode.pseudoClassStateChanged(GridRenderer.SELECTED_PSEUDO_CLASS, - drawGridOnTopNode.isVisible()); - }; - - horizontalGridLinesVisibleProperty().addListener(change); - verticalGridLinesVisibleProperty().addListener(change); - drawOnTopProperty().addListener(change); - } - - @Override - public String getUserAgentStylesheet() { - return GridRenderer.CHART_CSS; + StyleUtil.hiddenStyleNode(this, STYLE_CLASS_GRID_RENDERER); + getChildren().addAll( + horMajorGridStyleNode, + verMajorGridStyleNode, + horMinorGridStyleNode, + verMinorGridStyleNode, + drawGridOnTopNode + ); + StyleUtil.applyPseudoClass(horMajorGridStyleNode, GridRenderer.WITH_MINOR_PSEUDO_CLASS, horMinorGridStyleNode.visibleProperty()); + StyleUtil.applyPseudoClass(verMajorGridStyleNode, GridRenderer.WITH_MINOR_PSEUDO_CLASS, verMinorGridStyleNode.visibleProperty()); } protected void drawEuclideanGrid(final GraphicsContext gc, XYChart xyChart) { @@ -332,11 +290,6 @@ public ObservableList getAxes() { return axesList; } - @Override - public List> getCssMetaData() { - return GridRenderer.getClassCssMetaData(); - } - @Override public ObservableList getDatasets() { return null; @@ -352,7 +305,7 @@ public ObservableList getDatasetsCopy() { * * @return the Line node to be styled */ - public Line getHorizontalMajorGrid() { + public LineStyle getHorizontalMajorGrid() { return horMajorGridStyleNode; } @@ -361,7 +314,7 @@ public Line getHorizontalMajorGrid() { * * @return the Line node to be styled */ - public Line getHorizontalMinorGrid() { + public LineStyle getHorizontalMinorGrid() { return horMinorGridStyleNode; } @@ -370,7 +323,7 @@ public Line getHorizontalMinorGrid() { * * @return the Line node to be styled */ - public Line getVerticalMajorGrid() { + public LineStyle getVerticalMajorGrid() { return verMajorGridStyleNode; } @@ -379,7 +332,7 @@ public Line getVerticalMajorGrid() { * * @return the Line node to be styled */ - public Line getVerticalMinorGrid() { + public LineStyle getVerticalMinorGrid() { return verMinorGridStyleNode; } @@ -470,14 +423,10 @@ public final BooleanProperty verticalMinorGridLinesVisibleProperty() { return verMinorGridStyleNode.visibleProperty(); } - protected static void applyGraphicsStyleFromLineStyle(final GraphicsContext gc, final Line style) { - gc.setStroke(style.getStroke()); - gc.setLineWidth(style.getStrokeWidth()); + protected static void applyGraphicsStyleFromLineStyle(final GraphicsContext gc, final LineStyle style) { + style.copyStyleTo(gc); if (style.getStrokeDashArray() == null || style.getStrokeDashArray().isEmpty()) { gc.setLineDashes(DEFAULT_GRID_DASH_PATTERM); - } else { - final double[] dashes = style.getStrokeDashArray().stream().mapToDouble(d -> d).toArray(); - gc.setLineDashes(dashes); } } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/PathStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java similarity index 59% rename from chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/PathStyle.java rename to chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java index 88f2e53df..714133408 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/PathStyle.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/LineStyle.java @@ -4,8 +4,11 @@ import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.SimpleLongProperty; import javafx.beans.value.ChangeListener; +import javafx.scene.Node; import javafx.scene.canvas.GraphicsContext; +import javafx.scene.shape.Line; import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; /** * An invisible node that lets users change styles @@ -16,19 +19,24 @@ * * @author ennerf */ -public class PathStyle extends Path { +public class LineStyle extends Line { - public PathStyle(String... styles) { - StyleUtil.hiddenStyleNode(this, styles); - strokeProperty().addListener(onChange); - fillProperty().addListener(onChange); - strokeWidthProperty().addListener(onChange); + public LineStyle(String... styles) { + this(true, styles); + } + + public LineStyle(boolean hide, String... styles) { + StyleUtil.addStyles(this, styles); + setManaged(false); + // It looks like a manual set will overwrite any CSS styling + if (hide) { + setVisible(false); + } + StyleUtil.registerShapeListener(this, StyleUtil.incrementOnChange(changeCounter)); } public void copyStyleTo(GraphicsContext gc) { - gc.setStroke(getStroke()); - gc.setFill(getFill()); - gc.setLineWidth(getStrokeWidth()); + StyleUtil.copyShapeStyle(this, gc); } public long getChangeCounter() { @@ -40,6 +48,5 @@ public ReadOnlyLongProperty changeCounterProperty() { } LongProperty changeCounter = new SimpleLongProperty(0); - ChangeListener onChange = (obs, old, value) -> changeCounter.set(changeCounter.get()); } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java index aa161044c..9380d9b5f 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleUtil.java @@ -1,6 +1,15 @@ package io.fair_acc.chartfx.ui.css; +import io.fair_acc.chartfx.utils.PropUtil; +import javafx.beans.binding.Bindings; +import javafx.beans.property.LongProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableBooleanValue; +import javafx.css.PseudoClass; import javafx.scene.Node; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.shape.Shape; +import javafx.scene.text.Text; /** * Utility class for styleable nodes @@ -12,7 +21,6 @@ public class StyleUtil { private StyleUtil() { } - public static NODE hiddenStyleNode(NODE node, String... styles) { hide(node); addStyles(node, styles); @@ -30,4 +38,79 @@ public static NODE hide(NODE node) { return node; } + public static void applyPseudoClass(Node node, PseudoClass clazz, ObservableBooleanValue condition) { + node.pseudoClassStateChanged(clazz, condition.get()); + PropUtil.runOnChange(() -> { + // We immediately apply style changes caused by updating the + // pseudo class to avoid triggering another tick. + node.pseudoClassStateChanged(clazz, condition.get()); + node.applyCss(); + }, condition); + } + + static void copyNodeStyle(Node style, GraphicsContext gc) { + // https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#node + // rotate, translate, etc. would mess up the coordinate frame + gc.setGlobalAlpha(style.getOpacity()); + } + + static void registerNodeListener(Node style, ChangeListener listener) { + style.opacityProperty().addListener(listener); + style.rotateProperty().addListener(listener); + style.visibleProperty().addListener(listener); + } + + static void copyShapeStyle(Shape style, GraphicsContext gc) { + // https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#shape + gc.setFill(style.getFill()); + // style.isSmooth(); // no equivalent + gc.setStroke(style.getStroke()); + // style.getStrokeType(); // no equivalent + if (style.getStrokeDashArray() != null && !style.getStrokeDashArray().isEmpty()) { + double[] dashes = style.getStrokeDashArray().stream().mapToDouble(Double::doubleValue).toArray(); + gc.setLineDashes(dashes); + } + gc.setLineDashOffset(style.getStrokeDashOffset()); + gc.setLineCap(style.getStrokeLineCap()); + gc.setLineJoin(style.getStrokeLineJoin()); + gc.setMiterLimit(style.getStrokeMiterLimit()); + gc.setLineWidth(style.getStrokeWidth()); + copyNodeStyle(style, gc); + } + + static void registerShapeListener(Shape style, ChangeListener listener) { + style.fillProperty().addListener(listener); + style.strokeProperty().addListener(listener); + Bindings.size(style.getStrokeDashArray()).addListener(listener); + style.strokeDashOffsetProperty().addListener(listener); + style.strokeLineCapProperty().addListener(listener); + style.strokeLineJoinProperty().addListener(listener); + style.strokeMiterLimitProperty().addListener(listener); + style.strokeWidthProperty().addListener(listener); + registerNodeListener(style, listener); + } + + static void copyTextStyle(Text style, GraphicsContext gc) { + // https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#text + gc.setFont(style.getFont()); + gc.setFontSmoothingType(style.getFontSmoothingType()); + // style.isStrikethrough(); // no equivalent + gc.setTextAlign(style.getTextAlignment()); + gc.setTextBaseline(style.getTextOrigin()); + // style.isUnderline(); // no equivalent + copyShapeStyle(style, gc); + } + + static void registerTextListener(Text style, ChangeListener listener) { + style.fontProperty().addListener(listener); + style.fontSmoothingTypeProperty().addListener(listener); + style.textAlignmentProperty().addListener(listener); + style.textOriginProperty().addListener(listener); + registerShapeListener(style, listener); + } + + static ChangeListener incrementOnChange(LongProperty counter) { + return (obs, old, value) -> counter.set(counter.get() + 1); + } + } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java index f0195c33d..dd9074a88 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/TextStyle.java @@ -20,14 +20,7 @@ public class TextStyle extends Text { public TextStyle(String... styles) { StyleUtil.hiddenStyleNode(this, styles); - fontProperty().addListener(onChange); - fillProperty().addListener(onChange); - strokeProperty().addListener(onChange); - textAlignmentProperty().addListener(onChange); - textOriginProperty().addListener(onChange); - strokeWidthProperty().addListener(onChange); - opacityProperty().addListener(onChange); - rotateProperty().addListener(onChange); + StyleUtil.registerTextListener(this, StyleUtil.incrementOnChange(changeCounter)); } /** @@ -35,19 +28,7 @@ public TextStyle(String... styles) { * @param gc target context */ public void copyStyleTo(GraphicsContext gc) { - gc.setFont(getFont()); - gc.setTextAlign(getTextAlignment()); - gc.setTextBaseline(getTextOrigin()); - - gc.setFill(getFill()); - - gc.setStroke(getStroke()); - gc.setLineCap(getStrokeLineCap()); - gc.setLineJoin(getStrokeLineJoin()); - gc.setLineWidth(getStrokeWidth()); - - gc.setGlobalAlpha(getOpacity()); - // gc.rotate(getRotate()); // could cause issues with translation + StyleUtil.copyTextStyle(this, gc); } public long getChangeCounter() { @@ -59,6 +40,5 @@ public ReadOnlyLongProperty changeCounterProperty() { } LongProperty changeCounter = new SimpleLongProperty(0); - ChangeListener onChange = (obs, old, value) -> changeCounter.set(changeCounter.get()); } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 7d33343ea..18b51adf9 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -140,17 +140,21 @@ -fx-stroke: derive(-fx-background, -10%); -fx-stroke-dash-array: 4.5, 2.5; -fx-stroke-width: 0.5; - -fx-grid-on-top: true; + visibility: visible; } -.chart-major-grid-lines:withminor { +.chart-major-grid-lines:withMinor { -fx-stroke: derive(-fx-background, -20%); } .chart-minor-grid-lines { -fx-stroke: derive(-fx-background, -5%); -fx-stroke-width: 0.5; - visibility: inherit; + visibility: hidden; +} + +.chart-grid-line-on-top { + visibility: visible; /* 'visible' for front, 'hidden' for back */ } .chart-major-vertical-lines .chart-major-grid-lines { From bc204915a2cbf62d0b3fac02a9df5222c71d99d1 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 4 Aug 2023 01:11:12 +0200 Subject: [PATCH 75/81] added a pref size to keep JavaFX panes from flickering (cherry picked from commit c65575104d38385dc9806506b972409f97e19bd3) --- .../src/main/resources/io/fair_acc/chartfx/chart.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css index 18b51adf9..a5c35cd6f 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.css @@ -17,9 +17,15 @@ } .chart, .chart-content, .chart-plot-area { - /* use a reasonable min and full-screen max */ + /* + Set some reasonable default sizes so users + can override them via CSS and JavaFX panes + won't flicker (usually +/- 1px) or act odd. + */ -fx-min-height: 100px; -fx-min-width: 100px; + -fx-pref-height: 500px; + -fx-pref-width: 500px; -fx-max-width: 4096px; -fx-max-height: 4096px; From 5d159940536321a56e4d0306a23b2823e57f8669 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Fri, 4 Aug 2023 20:59:57 +0200 Subject: [PATCH 76/81] added legacy layout request call to fix some old examples --- .../src/main/java/io/fair_acc/chartfx/Chart.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 24b1c9954..4d176fe55 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -557,6 +557,20 @@ public boolean isToolBarPinned() { return toolBarPinned.get(); } + /** + * Explicit layout request for backwards compatibility. + *

+ * TODO: + * Explicit calls are used in e.g. examples to trigger redraws after the + * property to a renderer changed. We should probably get rid of these + * calls and listen to renderer/plugin changes directly. + */ + @Override + public void requestLayout() { + fireInvalidated(ChartBits.ChartLayout, ChartBits.ChartCanvas); + super.requestLayout(); + } + protected void updateLegend() { if (state.isDirty(ChartBits.ChartLegend)) { updateLegend(getDatasets(), getRenderers()); From 283bc508eb1c66aca86a54ef29056393f2f88768 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Sat, 5 Aug 2023 19:07:54 +0200 Subject: [PATCH 77/81] mapped explicit chart::requestLayout calls to a dedicated method to avoid issues with redraws (only used in samples) --- .../main/java/io/fair_acc/chartfx/Chart.java | 16 ++--- .../java/io/fair_acc/chartfx/XYChart.java | 2 +- .../axes/spi/AbstractAxisParameter.java | 2 +- .../measurements/DataSetMeasurements.java | 2 +- .../measurements/SimpleMeasurements.java | 2 +- .../fair_acc/chartfx/ui/ToolBarFlowPane.java | 2 +- .../plugins/YWatchValueIndicatorTest.java | 2 +- .../spi/ContourDataSetRendererTests.java | 4 +- .../spi/ErrorDataSetRendererTests.java | 4 +- .../spi/LabelledMarkerRendererTests.java | 4 +- .../sample/chart/ContourChartSample.java | 14 ++--- .../chart/CustomColourSchemeSample.java | 9 ++- .../ErrorDataSetRendererStylingSample.java | 60 +++++++++---------- .../sample/chart/Histogram2DimSample.java | 2 +- .../sample/chart/HistogramSample.java | 2 +- .../chart/HistoryDataSetRendererSample.java | 4 +- .../chart/MountainRangeRendererSample.java | 2 +- .../sample/chart/OscilloscopeAxisSample.java | 6 +- .../chart/WaterfallPerformanceSample.java | 24 ++++---- .../AbstractBasicFinancialApplication.java | 3 +- 20 files changed, 82 insertions(+), 84 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java index 4d176fe55..f4ff8d123 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java @@ -558,17 +558,17 @@ public boolean isToolBarPinned() { } /** - * Explicit layout request for backwards compatibility. + * Explicit invalidation call for backwards compatibility that + * replaced all sample calls to requestLayout(). *

- * TODO: - * Explicit calls are used in e.g. examples to trigger redraws after the - * property to a renderer changed. We should probably get rid of these - * calls and listen to renderer/plugin changes directly. + * requestLayout() shouldn't trigger an event because it would + * cause many unnecessary redraws whenever a hidden node gets + * animated into the chart (HiddenSidesPaneSkin::112). + *

+ * TODO: get rid of this after updating the samples */ - @Override - public void requestLayout() { + public void invalidate() { fireInvalidated(ChartBits.ChartLayout, ChartBits.ChartCanvas); - super.requestLayout(); } protected void updateLegend() { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index a6af84f62..6e9e0ebb1 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -301,7 +301,7 @@ protected void axesChanged(final ListChangeListener.Change chang }); } - requestLayout(); + invalidate(); } /** diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java index 67eb92310..edb902a72 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/axes/spi/AbstractAxisParameter.java @@ -79,7 +79,7 @@ public AbstractAxisParameter() { animationDuration, dimIndex ); - state.addChangeListener(ChartBits.AxisLayout, (src, bits) -> requestLayout()); // forward to JavaFX + state.addChangeListener(ChartBits.AxisLayout, (src, bits) -> super.requestLayout()); // forward to JavaFX // Properties that change the placement of ticks // We can ignore the layout if labels can only move linearly along diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java index 384398fc0..89a3474b9 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/DataSetMeasurements.java @@ -300,7 +300,7 @@ private void removeRendererFromOldChart() { if (chart != null) { chart.getRenderers().remove(renderer); chart.getAxes().removeAll(renderer.getAxes()); - chart.requestLayout(); + chart.invalidate(); } } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java index 2d7e7016c..36eb4d8e5 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/measurements/SimpleMeasurements.java @@ -241,7 +241,7 @@ public void initialize() { @Override protected void removeAction() { super.removeAction(); - getMeasurementPlugin().getChart().requestLayout(); + getMeasurementPlugin().getChart().invalidate(); } public enum MeasurementCategory { diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/ToolBarFlowPane.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/ToolBarFlowPane.java index 8bbb6b731..92dc270a0 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/ToolBarFlowPane.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/ToolBarFlowPane.java @@ -127,7 +127,7 @@ public void registerListener() { chart.getMenuPane().setPinnedSide(null); this.setBackground(new Background(new BackgroundFill(defaultColour, CornerRadii.EMPTY, Insets.EMPTY))); } - chart.requestLayout(); + chart.invalidate(); }); } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java index 8684f217e..64f086024 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java @@ -97,7 +97,7 @@ void setOcclusionPrevention(FxRobot robot) { assertFalse(valueWatchIndicatorTested.isPreventOcclusion()); valueWatchIndicatorTested.setPreventOcclusion(true); assertTrue(valueWatchIndicatorTested.isPreventOcclusion()); - chart.requestLayout(); + chart.invalidate(); }); robot.interrupt(1); robot.interact(() -> { diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRendererTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRendererTests.java index 25d7fd38f..7a7369376 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRendererTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ContourDataSetRendererTests.java @@ -89,7 +89,7 @@ private void testRenderer(final ContourType contourType, final boolean altImplem renderer.setContourType(contourType); final String contourTypeString = renderer.getContourType().toString(); final String referenceImage = referenceFileName + contourTypeString + (altImplementation ? "_ALT" : "") + referenceFileExtension; - FXUtils.runAndWait(() -> chart.requestLayout()); + FXUtils.runAndWait(() -> chart.invalidate()); assertTrue(FXUtils.waitForFxTicks(chart.getScene(), WAIT_N_FX_PULSES, MAX_TIMEOUT_MILLIS)); FXUtils.runAndWait(() -> testImage = chart.snapshot(null, null)); @@ -106,7 +106,7 @@ private void testRenderer(final ContourType contourType, final boolean altImplem } } - FXUtils.runAndWait(() -> chart.requestLayout()); + FXUtils.runAndWait(() -> chart.invalidate()); assertTrue(FXUtils.waitForFxTicks(chart.getScene(), WAIT_N_FX_PULSES, MAX_TIMEOUT_MILLIS)); FXUtils.runAndWait(() -> testImage = chart.snapshot(null, null)); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java index f048babd8..29aa1971c 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/ErrorDataSetRendererTests.java @@ -123,7 +123,7 @@ private void testRenderer(final LineStyle lineStyle) throws Exception { final String referenceImage = getReferenceImageFileName(); FXUtils.runAndWait(() -> renderer.getDatasets().setAll(getTestDataSet())); FXUtils.runAndWait(() -> chart.getLegend().updateLegend(renderer.getDatasets(), Collections.singletonList(renderer), true)); - FXUtils.runAndWait(() -> chart.requestLayout()); + FXUtils.runAndWait(() -> chart.invalidate()); assertTrue(FXUtils.waitForFxTicks(chart.getScene(), WAIT_N_FX_PULSES, MAX_TIMEOUT_MILLIS)); FXUtils.runAndWait(() -> testImage = chart.snapshot(null, null)); @@ -140,7 +140,7 @@ private void testRenderer(final LineStyle lineStyle) throws Exception { } } - FXUtils.runAndWait(() -> chart.requestLayout()); + FXUtils.runAndWait(() -> chart.invalidate()); assertTrue(FXUtils.waitForFxTicks(chart.getScene(), WAIT_N_FX_PULSES, MAX_TIMEOUT_MILLIS)); FXUtils.runAndWait(() -> testImage = chart.snapshot(null, null)); diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java index f918d85cb..5e4105537 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/renderer/spi/LabelledMarkerRendererTests.java @@ -84,7 +84,7 @@ public void testSetterGetter() { @Test public void defaultTests() throws Exception { FXUtils.runAndWait(() -> chart.getDatasets().add(getTestDataSet())); - FXUtils.runAndWait(() -> chart.requestLayout()); + FXUtils.runAndWait(() -> chart.invalidate()); assertTrue(FXUtils.waitForFxTicks(chart.getScene(), WAIT_N_FX_PULSES, MAX_TIMEOUT_MILLIS)); FXUtils.runAndWait(() -> testImage = chart.snapshot(null, null)); @@ -103,7 +103,7 @@ public void defaultTests() throws Exception { FXUtils.runAndWait(() -> renderer.enableHorizontalMarker(true)); assertTrue(renderer.isHorizontalMarker()); - FXUtils.runAndWait(() -> chart.requestLayout()); + FXUtils.runAndWait(() -> chart.invalidate()); assertTrue(FXUtils.waitForFxTicks(chart.getScene(), WAIT_N_FX_PULSES, MAX_TIMEOUT_MILLIS)); FXUtils.runAndWait(() -> testImage = chart.snapshot(null, null)); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ContourChartSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ContourChartSample.java index 2404f170f..5cb156f5f 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ContourChartSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ContourChartSample.java @@ -113,13 +113,13 @@ private XYChart getChartPane(final Slider slider1, final Slider slider2, final S // contourRenderer.getDatasets().add(createTestData()); slider1.valueProperty().bindBidirectional(contourRenderer.quantisationLevelsProperty()); - slider1.valueProperty().addListener((ch, o, n) -> chart.requestLayout()); + slider1.valueProperty().addListener((ch, o, n) -> chart.invalidate()); slider2.valueProperty().bindBidirectional(contourRenderer.maxContourSegmentsProperty()); - slider2.valueProperty().addListener((ch, o, n) -> chart.requestLayout()); + slider2.valueProperty().addListener((ch, o, n) -> chart.invalidate()); slider3.valueProperty().bindBidirectional(contourRenderer.minHexTileSizeProperty()); - slider3.valueProperty().addListener((ch, o, n) -> chart.requestLayout()); + slider3.valueProperty().addListener((ch, o, n) -> chart.invalidate()); // chart.getZAxis().setAutoRanging(false); // chart.getZAxis().setUpperBound(1500); @@ -221,24 +221,24 @@ public Node getChartPanel(final Stage primaryStage) { cb1.getItems().addAll(ContourType.values()); cb1.setValue(renderer1.getContourType()); cb1.valueProperty().bindBidirectional(renderer1.contourTypeProperty()); - cb1.valueProperty().addListener((ch, old, selection) -> chartPane1.requestLayout()); + cb1.valueProperty().addListener((ch, old, selection) -> chartPane1.invalidate()); final ColormapComboBox colorGradient1 = new ColormapComboBox(); colorGradient1.getItems().addAll(ColorGradient.colorGradients()); colorGradient1.setValue(renderer1.getColorGradient()); colorGradient1.valueProperty().bindBidirectional(renderer1.colorGradientProperty()); - colorGradient1.valueProperty().addListener((ch, old, selection) -> chartPane1.requestLayout()); + colorGradient1.valueProperty().addListener((ch, old, selection) -> chartPane1.invalidate()); final ComboBox cb2 = new ComboBox<>(); cb2.getItems().addAll(ContourType.values()); cb2.setValue(renderer2.getContourType()); cb2.valueProperty().bindBidirectional(renderer2.contourTypeProperty()); - cb1.valueProperty().addListener((ch, old, selection) -> chartPane2.requestLayout()); + cb1.valueProperty().addListener((ch, old, selection) -> chartPane2.invalidate()); final ColormapComboBox colorGradient2 = new ColormapComboBox(); colorGradient2.setValue(renderer2.getColorGradient()); colorGradient2.valueProperty().bindBidirectional(renderer2.colorGradientProperty()); - colorGradient2.valueProperty().addListener((ch, old, selection) -> chartPane2.requestLayout()); + colorGradient2.valueProperty().addListener((ch, old, selection) -> chartPane2.invalidate()); final HBox parameter = new HBox(new Label("Countour1: "), cb1, colorGradient1, new Label(" Countour2: "), cb2, colorGradient2); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/CustomColourSchemeSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/CustomColourSchemeSample.java index ac5dc0f77..1dbdfe0b5 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/CustomColourSchemeSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/CustomColourSchemeSample.java @@ -3,7 +3,6 @@ import java.util.Collections; import javafx.application.Application; -import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Node; @@ -64,7 +63,7 @@ public Node getChartPanel(final Stage primaryStage) { DefaultRenderColorScheme.Palette.getValue(DefaultRenderColorScheme.strokeColorProperty().get())); strokeStyleCB.getSelectionModel().selectedItemProperty().addListener((ch, o, n) -> { DefaultRenderColorScheme.strokeColorProperty().set(n.getPalette()); - chart.requestLayout(); + chart.invalidate(); chart.getLegend().updateLegend(chart.getDatasets(), Collections.singletonList(renderer), true); LOGGER.atInfo().log("updated stroke colour scheme to " + n.name()); }); @@ -77,7 +76,7 @@ public Node getChartPanel(final Stage primaryStage) { DefaultRenderColorScheme.fillColorProperty().set(n.getPalette()); DefaultRenderColorScheme.fillStylesProperty().clear(); DefaultRenderColorScheme.fillStylesProperty().set(DefaultRenderColorScheme.getStandardFillStyle()); - chart.requestLayout(); + chart.invalidate(); chart.getLegend().updateLegend(chart.getDatasets(), Collections.singletonList(renderer), true); LOGGER.atInfo().log("updated fill colour scheme to " + n.name()); }); @@ -87,7 +86,7 @@ public Node getChartPanel(final Stage primaryStage) { errorStyleCB.getSelectionModel().select(renderer.getErrorType()); errorStyleCB.getSelectionModel().selectedItemProperty().addListener((ch, o, n) -> { renderer.setErrorType(n); - chart.requestLayout(); + chart.invalidate(); LOGGER.atInfo().log("updated error style to " + n.name()); }); @@ -102,7 +101,7 @@ public Node getChartPanel(final Stage primaryStage) { } DefaultRenderColorScheme.fillStylesProperty().clear(); DefaultRenderColorScheme.fillStylesProperty().set(values); - chart.requestLayout(); + chart.invalidate(); chart.getLegend().updateLegend(chart.getDatasets(), Collections.singletonList(renderer), true); LOGGER.atInfo().log("updated to custom filling scheme"); }); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java index 1d3bbdfdf..9dbe5be4f 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/ErrorDataSetRendererStylingSample.java @@ -116,7 +116,7 @@ private void generateData(final XYChart chart) { Platform.runLater(() -> { chart.getRenderers().get(0).getDatasets().setAll(dataSetsWithNaN); - chart.requestLayout(); + chart.invalidate(); }); startTime = ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); } @@ -126,63 +126,63 @@ private ParameterTab getAxisTab(final String name, final XYChart chart, final De final CheckBox animated = new CheckBox(); animated.selectedProperty().bindBidirectional(axis.animatedProperty()); - animated.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + animated.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Amimated: ", animated); final CheckBox autoranging = new CheckBox(); autoranging.selectedProperty().bindBidirectional(axis.autoRangingProperty()); - autoranging.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + autoranging.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Auto-Ranging: ", autoranging); final CheckBox autogrowranging = new CheckBox(); autogrowranging.selectedProperty().bindBidirectional(axis.autoGrowRangingProperty()); - autogrowranging.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + autogrowranging.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Auto-Grow-Ranging: ", autogrowranging); final Spinner upperRange = new Spinner<>(-N_MAX_SAMPLES, +N_MAX_SAMPLES, axis.getMin(), 0.1); upperRange.valueProperty().addListener((ch, old, value) -> { axis.setMax(value.doubleValue()); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Upper Bound): ", upperRange); final Spinner lowerRange = new Spinner<>(-N_MAX_SAMPLES, +N_MAX_SAMPLES, axis.getMin(), 0.1); lowerRange.valueProperty().addListener((ch, old, value) -> { axis.setMin(value.doubleValue()); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Lower Bound): ", lowerRange); final CheckBox autoRangeRounding = new CheckBox(); autoRangeRounding.selectedProperty().bindBidirectional(axis.autoRangeRoundingProperty()); - autoRangeRounding.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + autoRangeRounding.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Auto-Range-Rounding: ", autoRangeRounding); final Spinner autoRangePadding = new Spinner<>(0, 100.0, axis.getAutoRangePadding(), 0.05); autoRangePadding.valueProperty().addListener((ch, old, value) -> { axis.setAutoRangePadding(value); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" auto-range padding): ", autoRangePadding); final CheckBox logAxis = new CheckBox(); logAxis.selectedProperty().bindBidirectional(axis.logAxisProperty()); - logAxis.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + logAxis.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Log-Axis: ", logAxis); final CheckBox timeAxis = new CheckBox(); timeAxis.selectedProperty().bindBidirectional(axis.timeAxisProperty()); - timeAxis.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + timeAxis.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Time-Axis: ", timeAxis); final CheckBox invertAxis = new CheckBox(); invertAxis.selectedProperty().bindBidirectional(axis.invertAxisProperty()); - invertAxis.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + invertAxis.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Invert-Axis: ", invertAxis); final CheckBox autoUnit = new CheckBox(); autoUnit.selectedProperty().bindBidirectional(axis.autoUnitScalingProperty()); - autoUnit.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + autoUnit.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Auto Unit: ", autoUnit); pane.addToParameterPane(" ", null); @@ -278,7 +278,7 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende polyLineSelect.setValue(LineStyle.NORMAL); polyLineSelect.valueProperty().addListener((ch, old, selection) -> { errorRenderer.setPolyLineStyle(selection); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane("PolyLine Style: ", polyLineSelect); @@ -287,7 +287,7 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende errorStyleSelect.setValue(errorRenderer.getErrorType()); errorStyleSelect.valueProperty().addListener((ch, old, selection) -> { errorRenderer.setErrorType(selection); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane("Error-Bar Style: ", errorStyleSelect); @@ -297,7 +297,7 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende drawMarker.setSelected(errorRenderer.isDrawMarker()); drawMarker.selectedProperty().addListener((ch, old, selected) -> { errorRenderer.setDrawMarker(selected); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane("Draw Markers: ", drawMarker); @@ -305,7 +305,7 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende markerSize.isEditable(); markerSize.valueProperty().addListener((ch, old, value) -> { errorRenderer.setMarkerSize(value.intValue()); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Marker Size: ", markerSize); @@ -314,7 +314,7 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende markerStyle.setValue((DefaultMarker) errorRenderer.getMarker()); markerStyle.valueProperty().addListener((ch, old, selection) -> { errorRenderer.setMarker(selection); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Marker Style: ", markerStyle); @@ -322,7 +322,7 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende dashSize.isEditable(); dashSize.valueProperty().addListener((ch, old, value) -> { errorRenderer.setDashSize(value.intValue()); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane("Cap Dash Size: ", dashSize); @@ -332,7 +332,7 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende drawBars.setSelected(errorRenderer.isDrawBars()); drawBars.selectedProperty().addListener((ch, old, selected) -> { errorRenderer.setDrawBars(selected); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane("Draw Bars: ", drawBars); @@ -340,21 +340,21 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende dynBarWidthEnable.setSelected(errorRenderer.isDynamicBarWidth()); dynBarWidthEnable.selectedProperty().addListener((ch, old, selected) -> { errorRenderer.setDynamicBarWidth(selected); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Dyn. Bar Width: ", dynBarWidthEnable); final Spinner dynBarWidth = new Spinner<>(0, 100, errorRenderer.getBarWidthPercentage(), 10); dynBarWidth.valueProperty().addListener((ch, old, value) -> { errorRenderer.setBarWidthPercentage(value.intValue()); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Dyn. Bar Width: ", dynBarWidth); final Spinner barWidth = new Spinner<>(0, 100, errorRenderer.getBarWidth()); barWidth.valueProperty().addListener((ch, old, value) -> { errorRenderer.setBarWidth(value.intValue()); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Abs. Bar Width: ", barWidth); @@ -362,14 +362,14 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende shiftBar.setSelected(errorRenderer.isShiftBar()); shiftBar.selectedProperty().addListener((ch, old, selected) -> { errorRenderer.setShiftBar(selected); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Shift Bar (mult. data sets): ", shiftBar); final Spinner shiftBarOffset = new Spinner<>(0, 100, errorRenderer.getShiftBarOffset()); shiftBarOffset.valueProperty().addListener((ch, old, value) -> { errorRenderer.setshiftBarOffset(value.intValue()); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane(" Shift Bar Offset (mult. DS): ", shiftBarOffset); @@ -377,38 +377,38 @@ private ParameterTab getRendererTab(final XYChart chart, final ErrorDataSetRende final CheckBox pointReduction = new CheckBox(); pointReduction.selectedProperty().bindBidirectional(errorRenderer.pointReductionProperty()); - pointReduction.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + pointReduction.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Point Reduction: ", pointReduction); final DefaultDataReducer dataReducer = (DefaultDataReducer) errorRenderer.getRendererDataReducer(); final Spinner reductionMinSize = new Spinner<>(0, 1000, errorRenderer.getMinRequiredReductionSize()); reductionMinSize.setEditable(true); errorRenderer.minRequiredReductionSizeProperty().bind(reductionMinSize.valueProperty()); - reductionMinSize.valueProperty().addListener((ch, old, value) -> chart.requestLayout()); + reductionMinSize.valueProperty().addListener((ch, old, value) -> chart.invalidate()); pane.addToParameterPane(" Min Req. Samples: ", reductionMinSize); final Spinner reductionDashSize = new Spinner<>(0, 100, dataReducer.getMinPointPixelDistance()); dataReducer.minPointPixelDistanceProperty().bind(reductionDashSize.valueProperty()); - reductionDashSize.valueProperty().addListener((ch, old, value) -> chart.requestLayout()); + reductionDashSize.valueProperty().addListener((ch, old, value) -> chart.invalidate()); pane.addToParameterPane(" Red. Min Distance: ", reductionDashSize); pane.addToParameterPane(" ", null); final CheckBox assumeSorted = new CheckBox(); assumeSorted.selectedProperty().bindBidirectional(errorRenderer.assumeSortedDataProperty()); - assumeSorted.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + assumeSorted.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane("Assume sorted data: ", assumeSorted); pane.addToParameterPane(" ", null); final CheckBox cacheParallel = new CheckBox(); cacheParallel.selectedProperty().bindBidirectional(errorRenderer.parallelImplementationProperty()); - cacheParallel.selectedProperty().addListener((ch, old, selected) -> chart.requestLayout()); + cacheParallel.selectedProperty().addListener((ch, old, selected) -> chart.invalidate()); pane.addToParameterPane(" Point cache parallel: ", cacheParallel); final CheckBox allowNaNs = new CheckBox(); allowNaNs.setSelected(errorRenderer.isallowNaNs()); allowNaNs.selectedProperty().addListener((ch, old, selected) -> { errorRenderer.setAllowNaNs(selected); - chart.requestLayout(); + chart.invalidate(); }); pane.addToParameterPane("Allow NaN: ", allowNaNs); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java index 3f5773c11..ba68bfb3a 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/Histogram2DimSample.java @@ -139,7 +139,7 @@ public Node getChartPanel(final Stage primaryStage) { @Override public void run() { fillData(); - FXUtils.runFX(chart::requestLayout); + FXUtils.runFX(chart::invalidate); } }, Histogram2DimSample.UPDATE_DELAY, Histogram2DimSample.UPDATE_PERIOD); return root; diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java index 37a72247f..f00854c5f 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistogramSample.java @@ -136,7 +136,7 @@ public Node getChartPanel(final Stage primaryStage) { @Override public void run() { fillData(); - FXUtils.runFX(chart::requestLayout); + FXUtils.runFX(chart::invalidate); } }, HistogramSample.UPDATE_DELAY, HistogramSample.UPDATE_PERIOD); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistoryDataSetRendererSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistoryDataSetRendererSample.java index ca2f1cbb3..532f84222 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistoryDataSetRendererSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/HistoryDataSetRendererSample.java @@ -73,7 +73,7 @@ private void generateData(final XYChart chart) { })); Platform.runLater(() -> { - chart.requestLayout(); + chart.invalidate(); final ObservableList rendererList = chart.getRenderers(); for (final Renderer rend : rendererList) { if (rend instanceof HistoryDataSetRenderer) { @@ -152,7 +152,7 @@ public Node getChartPanel(final Stage primaryStage) { hr.clearHistory(); } } - chart.requestLayout(); + chart.invalidate(); }); final Button startTimer = new Button("timer"); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/MountainRangeRendererSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/MountainRangeRendererSample.java index da815f21d..11d3d48a6 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/MountainRangeRendererSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/MountainRangeRendererSample.java @@ -61,7 +61,7 @@ public Node getChartPanel(final Stage primaryStage) { if (n.equals(o)) { return; } - chart.requestLayout(); + chart.invalidate(); }); return new BorderPane(chart, new ToolBar(new Label(""), mountainRangeOffset), null, null, null); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java index b091e97b5..c72e2c2a0 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/OscilloscopeAxisSample.java @@ -150,7 +150,7 @@ private ToolBar getHeaderBar() { continue; } ((OscilloscopeAxis) axis).setAxisZeroPosition(zeroPosition); - chartOscilloscopeAxis.requestLayout(); + chartOscilloscopeAxis.invalidate(); } }); @@ -166,7 +166,7 @@ private ToolBar getHeaderBar() { ((OscilloscopeAxis) axis).getMinRange().clear(); } axis.forceRedraw(); - chartOscilloscopeAxis.requestLayout(); + chartOscilloscopeAxis.invalidate(); } }); @@ -182,7 +182,7 @@ private ToolBar getHeaderBar() { ((OscilloscopeAxis) axis).getMaxRange().clear(); } axis.forceRedraw(); - chartOscilloscopeAxis.requestLayout(); + chartOscilloscopeAxis.invalidate(); } }); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java index 11d186e42..3d3275dbb 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/WaterfallPerformanceSample.java @@ -142,11 +142,11 @@ private ToolBar getContourToolBar(final XYChart chart, final ContourDataSetRende contourType.getItems().addAll(ContourType.values()); contourType.setValue(renderer.getContourType()); contourType.valueProperty().bindBidirectional(renderer.contourTypeProperty()); - contourType.valueProperty().addListener((ch, old, selection) -> chart.requestLayout()); + contourType.valueProperty().addListener((ch, old, selection) -> chart.invalidate()); colorGradient.setValue(renderer.getColorGradient()); colorGradient.valueProperty().bindBidirectional(renderer.colorGradientProperty()); - colorGradient.valueProperty().addListener((ch, old, selection) -> chart.requestLayout()); + colorGradient.valueProperty().addListener((ch, old, selection) -> chart.invalidate()); nCountourLevelSlider.setShowTickLabels(true); nCountourLevelSlider.setShowTickMarks(true); @@ -159,7 +159,7 @@ private ToolBar getContourToolBar(final XYChart chart, final ContourDataSetRende nContourLabel.setTooltip(new Tooltip("adjusts number of contour levels")); final HBox hBoxContourLevelSlider = new HBox(nContourLabel, nCountourLevelSlider); nCountourLevelSlider.valueProperty().bindBidirectional(renderer.quantisationLevelsProperty()); - nCountourLevelSlider.valueProperty().addListener((ch, o, n) -> chart.requestLayout()); + nCountourLevelSlider.valueProperty().addListener((ch, o, n) -> chart.invalidate()); nSegmentSlider.setShowTickLabels(true); nSegmentSlider.setShowTickMarks(true); @@ -169,7 +169,7 @@ private ToolBar getContourToolBar(final XYChart chart, final ContourDataSetRende HBox.setHgrow(nSegmentSlider, Priority.ALWAYS); final HBox hBoxSegmentSlider = new HBox(new Label("n segments :"), nSegmentSlider); nSegmentSlider.valueProperty().bindBidirectional(renderer.maxContourSegmentsProperty()); - nSegmentSlider.valueProperty().addListener((ch, o, n) -> chart.requestLayout()); + nSegmentSlider.valueProperty().addListener((ch, o, n) -> chart.invalidate()); minHexSizeSlider.setShowTickLabels(true); minHexSizeSlider.setShowTickMarks(true); @@ -179,24 +179,24 @@ private ToolBar getContourToolBar(final XYChart chart, final ContourDataSetRende HBox.setHgrow(minHexSizeSlider, Priority.ALWAYS); final HBox hBoxHexSizeSlider = new HBox(new Label("HexSize :"), minHexSizeSlider); minHexSizeSlider.valueProperty().bindBidirectional(renderer.minHexTileSizeProperty()); - minHexSizeSlider.valueProperty().addListener((ch, o, n) -> chart.requestLayout()); + minHexSizeSlider.valueProperty().addListener((ch, o, n) -> chart.invalidate()); localRange.setSelected(renderer.computeLocalRange()); localRange.setTooltip(new Tooltip("select for auto-adjusting the colour axis for the selected sub-range")); localRange.selectedProperty().bindBidirectional(renderer.computeLocalRangeProperty()); - localRange.selectedProperty().addListener((ch, old, selection) -> chart.requestLayout()); + localRange.selectedProperty().addListener((ch, old, selection) -> chart.invalidate()); final ToolBar standardCountourParameters = new ToolBar(contourType, colorGradient, hBoxContourLevelSlider, hBoxSegmentSlider, hBoxHexSizeSlider, localRange); dataReduction.setSelected(renderer.isReducePoints()); dataReduction.selectedProperty().bindBidirectional(renderer.pointReductionProperty()); - dataReduction.selectedProperty().addListener((ch, old, selection) -> chart.requestLayout()); + dataReduction.selectedProperty().addListener((ch, old, selection) -> chart.invalidate()); ChangeListener reductionListener = (ch, o, n) -> { renderer.setReductionFactorX(reductionFactorX.getValue()); renderer.setReductionFactorY(reductionFactorY.getValue()); - chart.requestLayout(); + chart.invalidate(); }; reductionFactorX.getValueFactory().setValue(renderer.getReductionFactorX()); @@ -214,19 +214,19 @@ private ToolBar getContourToolBar(final XYChart chart, final ContourDataSetRende reductionType.getItems().addAll(ReductionType.values()); reductionType.setValue(renderer.getReductionType()); reductionType.valueProperty().bindBidirectional(renderer.reductionTypeProperty()); - reductionType.valueProperty().addListener((ch, old, selection) -> chart.requestLayout()); + reductionType.valueProperty().addListener((ch, old, selection) -> chart.invalidate()); smooth.setSelected(renderer.isSmooth()); smooth.selectedProperty().bindBidirectional(renderer.smoothProperty()); - smooth.selectedProperty().addListener((ch, old, selection) -> chart.requestLayout()); + smooth.selectedProperty().addListener((ch, old, selection) -> chart.invalidate()); altImplementation.setSelected(renderer.isAltImplementation()); altImplementation.selectedProperty().bindBidirectional(renderer.altImplementationProperty()); - altImplementation.selectedProperty().addListener((ch, old, selection) -> chart.requestLayout()); + altImplementation.selectedProperty().addListener((ch, old, selection) -> chart.invalidate()); parallelImplementation.setSelected(renderer.isParallelImplementation()); parallelImplementation.selectedProperty().bindBidirectional(renderer.parallelImplementationProperty()); - parallelImplementation.selectedProperty().addListener((ch, old, selection) -> chart.requestLayout()); + parallelImplementation.selectedProperty().addListener((ch, old, selection) -> chart.invalidate()); final ToolBar newCountourParameters = new ToolBar(dataReduction, hBoxReductionFactorSlider, reductionType, smooth, altImplementation, parallelImplementation); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java index 622911d95..b04e4bdcc 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/financial/AbstractBasicFinancialApplication.java @@ -28,7 +28,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fair_acc.chartfx.Chart; import io.fair_acc.chartfx.XYChart; import io.fair_acc.chartfx.axes.AxisLabelOverlapPolicy; import io.fair_acc.chartfx.axes.AxisMode; @@ -140,7 +139,7 @@ protected ToolBar getTestToolBar(Chart chart, AbstractFinancialRenderer rende ((Zoomer) plugin).setAxisMode(selection ? AxisMode.X : AxisMode.XY); } } - chart.requestLayout(); + chart.invalidate(); }); Button periodicTimer = null; From d52e3d0c0949f4ebcb908caa2ebc0dccd9e062d9 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 7 Aug 2023 18:57:03 +0200 Subject: [PATCH 78/81] fixed grow ranging example --- chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java | 2 +- .../io/fair_acc/sample/chart/PaddedAutoGrowAxisSample.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java index 6e9e0ebb1..5f9299041 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/XYChart.java @@ -416,7 +416,7 @@ protected static void updateNumericAxis(final Axis axis, final List dat // 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); diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/PaddedAutoGrowAxisSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/PaddedAutoGrowAxisSample.java index 6546b88a0..ffc0e6eb4 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/PaddedAutoGrowAxisSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/PaddedAutoGrowAxisSample.java @@ -33,7 +33,7 @@ public Node getChartPanel(Stage primaryStage) { yAxis.setAutoRangePadding(0.05); yAxis.setAutoRanging(false); yAxis.setAutoGrowRanging(true); - yAxis.set(0, 10); + yAxis.getAutoRange().set(0, 10); var ds = new CircularDoubleErrorDataSet("", 150); chart.getDatasets().addAll(ds); @@ -43,8 +43,7 @@ public Node getChartPanel(Stage primaryStage) { try { TimeUnit.MILLISECONDS.sleep(100); FXUtils.runAndWait(() -> { - yAxis.set(0, 10); - yAxis.getAutoRange().clear(); + yAxis.getAutoRange().set(0, 10); }); TimeUnit.MILLISECONDS.sleep(100); } catch (Exception e) { From db257c3877c3a345928a39031c8bab1255fc7e90 Mon Sep 17 00:00:00 2001 From: Florian Enner Date: Mon, 7 Aug 2023 19:04:38 +0200 Subject: [PATCH 79/81] fixed axis range sample --- .../sample/chart/TimeAxisRangeSample.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java index 5bdbe4661..8cef1f35b 100644 --- a/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java +++ b/chartfx-samples/src/main/java/io/fair_acc/sample/chart/TimeAxisRangeSample.java @@ -5,6 +5,7 @@ import java.util.TimerTask; import java.util.concurrent.TimeUnit; +import io.fair_acc.chartfx.XYChart; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; @@ -31,30 +32,27 @@ public class TimeAxisRangeSample extends ChartSample { @Override public Node getChartPanel(final Stage primaryStage) { final double now = System.currentTimeMillis() / 1000.0; // [s] - final VBox root = new VBox(); - root.setAlignment(Pos.CENTER); + var root = new XYChart(); final TimeAxis xAxis1 = new TimeAxis("standard time axis", now - 3600, now, 3600 / 12); - root.getChildren().add(xAxis1); + root.getAxes().add(xAxis1); final TimeAxis xAxis2 = new TimeAxis("inverted time axis", now - 3600, now, 3600 / 12); xAxis2.invertAxis(true); - root.getChildren().add(xAxis2); + root.getAxes().add(xAxis2); final double[] ranges = { 0.1, 1.0, 10.0, 30.0, 3600.0, 3600 * 24, 3600 * 24 * 31 * 3, 3600 * 24 * 31 * 60 }; for (double range : ranges) { final TimeAxis axis = new TimeAxis("time axis - range = " + range + " s", now - range, now, range / 10); // NOPMD - root.getChildren().add(axis); + root.getAxes().add(axis); } // example for dynamic scaling with metric prefix and unit final TimeAxis xAxisDyn = new TimeAxis("dynamic time axis", now - 1e-3, now, 1); - xAxisDyn.setMinorTickCount(10); + xAxisDyn.setAutoRanging(false); xAxisDyn.setAutoRangeRounding(false); - root.getChildren().add(xAxisDyn); - - final Label xAxis9Text = new Label(); - root.getChildren().add(xAxis9Text); + xAxisDyn.setMinorTickCount(10); + root.getAxes().add(xAxisDyn); final Timer timer = new Timer("sample-update-timer", true); final TimerTask task = new TimerTask() { @@ -70,12 +68,11 @@ public void run() { } Platform.runLater(() -> { final double range = DefaultTimeTickUnitSupplier.TICK_UNIT_DEFAULTS[counter]; - xAxisDyn.minProperty().set(now - range); + xAxisDyn.setMin(now - range); final String text = "actual range [s]: " + String.format("%#.3f", range) + " (" + String.format("%#.1f", range / 3600 / 24) + " days)"; xAxisDyn.setTickUnit(range / 12); - xAxisDyn.forceRedraw(); - xAxis9Text.setText(text); + xAxisDyn.setName(text); }); if (counter >= DefaultTimeTickUnitSupplier.TICK_UNIT_DEFAULTS.length - 1 || counter <= 0) { directionUpwards = !directionUpwards; From 9d94974deecdb306756a612d78540fae45b73d11 Mon Sep 17 00:00:00 2001 From: Alexander Krimm Date: Mon, 7 Aug 2023 17:24:42 +0200 Subject: [PATCH 80/81] TableViewer: fix column update and clipbaoard export --- .../fair_acc/chartfx/plugins/TableViewer.java | 18 ++++++++++-------- .../chartfx/plugins/TableViewerTest.java | 3 --- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java index b0d93acf5..b7820b624 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/TableViewer.java @@ -161,6 +161,7 @@ public void setRefreshRate(final int newVal) { * Copies the (selected) table data to the clipboard in csv Format. */ public void copySelectedToClipboard() { + dsModel.runPreLayout(); final ClipboardContent content = new ClipboardContent(); content.putString(dsModel.getSelectedData(table.getSelectionModel())); Clipboard.getSystemClipboard().setContent(content); @@ -170,6 +171,7 @@ public void copySelectedToClipboard() { * Show a FileChooser and export the (selected) Table Data to the choosen .csv File. */ public void exportGridToCSV() { + dsModel.runPreLayout(); final FileChooser chooser = new FileChooser(); final File save = chooser.showSaveDialog(getChart().getScene().getWindow()); if (save == null) { @@ -251,10 +253,10 @@ protected enum ColumnType { EYN(DIM_Y, "e_y", true, false), EYP(DIM_Y, "e_y", true, true); - int dimIdx; - String label; - boolean errorCol; - boolean positive; + final int dimIdx; + final String label; + final boolean errorCol; + final boolean positive; ColumnType(final int dimIdx, final String label, final boolean errorCol, final boolean positive) { this.dimIdx = dimIdx; @@ -305,15 +307,15 @@ public void runPreLayout() { // Cap at max size List columnsUpdated = getChart().getAllDatasets().stream().sorted(Comparator.comparing(DataSet::getName)).collect(Collectors.toList()); - if(columnsUpdated.size() > MAX_DATASETS_IN_TABLE) { + if(columnsUpdated.size() >= MAX_DATASETS_IN_TABLE) { LOGGER.atWarn().addArgument(columnsUpdated.size()).log("Limiting number of DataSets shown in Table, chart has {} DataSets."); } - var cols = FXUtils.sizedList(columns, Math.min(columnsUpdated.size(), MAX_DATASETS_IN_TABLE), DataSetTableColumns::new); + var cols = FXUtils.sizedList(columns, Math.min(columnsUpdated.size() + 1, MAX_DATASETS_IN_TABLE), DataSetTableColumns::new); // Update the datasets - int i = 0, nRowsNew = 0; + int i = 1, nRowsNew = 0; for (DataSet ds : columnsUpdated) { - if (cols instanceof DataSetTableColumns) { + if (cols.get(i) instanceof DataSetTableColumns) { ((DataSetTableColumns) cols.get(i++)).update(ds); nRowsNew = Math.max(nRowsNew, ds.getDataCount()); } diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java index 9cf2cb3c8..c1a2eef7d 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/TableViewerTest.java @@ -21,7 +21,6 @@ import org.hamcrest.Matcher; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testfx.api.FxAssert; @@ -92,7 +91,6 @@ public void testThatTableIsOnlyAddedToSceneWhenActive() throws TimeoutException FxAssert.verifyThat(chart.getPlotForeground(), Matchers.not(NodeMatchers.hasChild(".table-view"))); } - @Disabled // TODO: fix after layout refactoring. Where did the .table-view element come from? @Test public void testThatTableCellsAreClickable() throws TimeoutException { // NOPMD JUnitTestsShouldIncludeAssert fxRobot.interact(() -> { @@ -130,7 +128,6 @@ public void testThatTableCellsAreClickable() throws TimeoutException { // NOPMD assertEquals(valueAtX1, selectedItem.getValue(dataset, ColumnType.Y)); } - @Disabled // TODO: fix after layout refactoring. Where did the .table-view element come from? @Test public void testThatDataSetsRowHashCodeEqualsWorks() throws TimeoutException { fxRobot.interact(() -> { From 7ed133140619d8cf867694fa532dc34e093b5435 Mon Sep 17 00:00:00 2001 From: Alexander Krimm Date: Tue, 8 Aug 2023 12:20:30 +0200 Subject: [PATCH 81/81] YWatchValueIndicatorTest: update check to value determined by axis formatter --- .../io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java index 64f086024..0c5affcf6 100644 --- a/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java +++ b/chartfx-chart/src/test/java/io/fair_acc/chartfx/plugins/YWatchValueIndicatorTest.java @@ -75,7 +75,7 @@ void rightSide() { @TestFx void setMarkerValue() { valueWatchIndicatorTested.setMarkerValue(35.15); - assertTrue(valueWatchIndicatorTested.getText().matches("35[\\.,]15")); // US or German locale + assertEquals("35", valueWatchIndicatorTested.getText()); // uses axis formatting. If this changes to include decimals, make sure to be locale agnostic assertEquals(35.15, valueWatchIndicatorTested.getValue(), 1e-2); }