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 76d0ee996..919df80f0 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,7 +12,7 @@ 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.LayoutHook; +import io.fair_acc.dataset.AxisDescription; import io.fair_acc.dataset.event.EventSource; import io.fair_acc.dataset.events.BitState; import io.fair_acc.dataset.events.ChartBits; @@ -21,7 +21,6 @@ import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.*; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -33,7 +32,6 @@ import javafx.scene.canvas.Canvas; import javafx.scene.control.Control; import javafx.scene.layout.*; -import javafx.scene.paint.Paint; import javafx.util.Duration; import org.slf4j.Logger; @@ -67,13 +65,12 @@ * @author hbraeun, rstein, major refactoring, re-implementation and re-design */ public abstract class Chart extends Region implements EventSource { - private final LayoutHook layoutHooks = LayoutHook.newPreAndPostHook(this, this::runPreLayout, this::runPostLayout); // 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.initDirty(this, BitState.ALL_BITS) - .addChangeListener(ChartBits.KnownMask, (src, bits) -> layoutHooks.registerOnce()) - .addChangeListener(ChartBits.ChartLayout, (src, bits) -> super.requestLayout()); + .addChangeListener(ChartBits.ChartLayout, (src, bits) -> super.requestLayout()) + .addChangeListener(ChartBits.KnownMask, (src, bits) -> ensureJavaFxPulse()); // 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 @@ -88,11 +85,8 @@ public abstract class Chart extends Region implements EventSource { private static final CssPropertyFactory CSS = new CssPropertyFactory<>(Control.getClassCssMetaData()); private static final int DEFAULT_TRIGGER_DISTANCE = 50; protected static final boolean DEBUG = Boolean.getBoolean("chartfx.debug"); // for more verbose debugging - protected final BooleanProperty showing = new SimpleBooleanProperty(this, "showing", false); - { - showing.bind(FXUtils.getShowingBinding(this)); - } + /** * When true any data changes will be animated. @@ -110,18 +104,16 @@ public abstract class Chart extends Region implements EventSource { private final ObservableList datasets = FXCollections.observableArrayList(); protected final ObservableList allDataSets = FXCollections.observableArrayList(); private final ObservableList renderers = FXCollections.observableArrayList(); - { - getRenderers().addListener(this::rendererChanged); - } + // Inner canvas for the drawn content - protected final ResizableCanvas canvas = new ResizableCanvas(); + protected final ResizableCanvas canvas = StyleUtil.addStyles(new ResizableCanvas(), "chart-canvas"); protected final Pane canvasForeground = new Pane(); protected final Group pluginsArea = Chart.createChildGroup(); // Area where plots get drawn protected final Pane plotBackground = StyleUtil.addStyles(new Pane(), "chart-plot-background"); - protected final HiddenSidesPane plotArea = StyleUtil.addStyles(new HiddenSidesPane(), "chart-plot-content"); + protected final HiddenSidesPane plotArea = StyleUtil.addStyles(new HiddenSidesPane(), "chart-plot-area"); protected final Pane plotForeGround = StyleUtil.addStyles(new Pane(), "chart-plot-foreground"); // Outer chart elements @@ -137,40 +129,16 @@ public abstract class Chart extends Region implements EventSource { // Other nodes that need to be styled via CSS protected final StyleGroup styleableNodes = new StyleGroup(this, getChildren(), "chart"); - { - // Build hierarchy - // > menuPane (hidden toolbars that slide in from top/bottom) - // > measurement pane (labels/menus for working with data) - // > legend & title pane (static legend and title) - // > axis pane (x/y axes) - // > axes - // > plot area (plotted content, hidden elements for zoom etc.) - // > canvas (main) - // > canvas foreground - // > plugins - // > plot background/foreground - var canvasPane = StyleUtil.addStyles(new PlotAreaPane(canvas, canvasForeground, pluginsArea), "chart-plot-area"); - plotArea.setContent(canvasPane); - axesAndCanvasPane.addCenter(plotBackground, plotArea, plotForeGround); - titleLegendPane.addCenter(axesAndCanvasPane); - measurementPane.addCenter(titleLegendPane); - menuPane.setContent(measurementPane); - getChildren().add(menuPane); - } + protected final TitleLabel titleLabel = StyleUtil.addStyles(new TitleLabel(), "chart-title"); + + // Listeners + protected final ListChangeListener rendererChangeListener = this::rendererChanged; protected final ListChangeListener axesChangeListenerLocal = this::axesChangedLocal; protected final ListChangeListener axesChangeListener = this::axesChanged; protected final ListChangeListener datasetChangeListener = this::datasetsChanged; protected final ListChangeListener pluginsChangedListener = this::pluginsChanged; - { - getDatasets().addListener(datasetChangeListener); - getAxes().addListener(axesChangeListener); - getAxes().addListener(axesChangeListenerLocal); - } - - protected final TitleLabel titleLabel = StyleUtil.addStyles(new TitleLabel(), "chart-title"); - /** * The node to display as the Legend. Subclasses can set a node here to be displayed on a side as the legend. If no * legend is wanted then this can be set to null @@ -245,6 +213,18 @@ public Chart(Axis... axes) { } } + // Register the layout hooks where chart elements get drawn + FXUtils.registerLayoutHooks(this, this::runPreLayout, this::runPostLayout); + + // Setup listeners + showing.bind(FXUtils.getShowingBinding(this)); + getRenderers().addListener(rendererChangeListener); + getDatasets().addListener(datasetChangeListener); + getPlugins().addListener(pluginsChangedListener); + getAxes().addListener(axesChangeListenerLocal); + getAxes().addListener(axesChangeListener); + + menuPane.setTriggerDistance(Chart.DEFAULT_TRIGGER_DISTANCE); plotBackground.toBack(); plotForeGround.toFront(); @@ -256,24 +236,7 @@ public Chart(Axis... axes) { // hiddenPane.setMouseTransparent(true); plotArea.setPickOnBounds(false); - // alt: canvas resize (default JavaFX Canvas does not automatically - // resize to pref width/height according to parent constraints - // canvas.widthProperty().bind(stackPane.widthProperty()); - // canvas.heightProperty().bind(stackPane.heightProperty()); getCanvasForeground().setManaged(false); - final ChangeListener canvasSizeChangeListener = (ch, o, n) -> { - final double width = getCanvas().getWidth(); - final double height = getCanvas().getHeight(); - - if (getCanvasForeground().getWidth() != width || getCanvasForeground().getHeight() != height) { - // workaround needed so that pane within pane does not trigger - // recursions w.r.t. repainting - getCanvasForeground().resize(width, height); - } - }; - canvas.widthProperty().addListener(canvasSizeChangeListener); - canvas.heightProperty().addListener(canvasSizeChangeListener); - getCanvasForeground().setMouseTransparent(true); getCanvas().toFront(); getCanvasForeground().toFront(); @@ -284,23 +247,37 @@ public Chart(Axis... axes) { canvas.setCacheHint(CacheHint.QUALITY); } - canvas.setStyle("-fx-background-color: rgba(200, 250, 200, 0.5);"); - - // add plugin handling and listeners - getPlugins().addListener(pluginsChangedListener); - // add default chart content ie. ToolBar and Legend // can be repositioned via setToolBarSide(...) and setLegendSide(...) - titleLabel.setAlignment(Pos.CENTER); - HBox.setHgrow(titleLabel, Priority.ALWAYS); - VBox.setVgrow(titleLabel, Priority.ALWAYS); titleLabel.focusTraversableProperty().bind(Platform.accessibilityActiveProperty()); + getTitleLegendPane().getChildren().add(titleLabel); // register listener in tool bar FlowPane toolBar.registerListener(); menuPane.setTop(getToolBar()); - getTitleLegendPane().getChildren().add(titleLabel); + // Chart hierarchy + // > style nodes + // > menuPane (hidden toolbars that slide in from top/bottom) + // > measurement pane (labels/menus for working with data) + // > legend & title pane (static legend and title) + // > axes pane (x/y axes) + // > axes + // > plot background/foreground + // > plot content + // > hidden elements for zoom etc. + // > plot area + // > canvas (main) + // > canvas foreground + // > plugins + var canvasArea = StyleUtil.addStyles(new PlotAreaPane(canvas, canvasForeground, pluginsArea), "chart-canvas-area"); + plotArea.setContent(canvasArea); + axesAndCanvasPane.addCenter(plotBackground, plotArea, plotForeGround); + titleLegendPane.addCenter(axesAndCanvasPane); + measurementPane.addCenter(titleLegendPane); + menuPane.setContent(measurementPane); + getChildren().add(menuPane); + } @Override @@ -502,25 +479,28 @@ public void invalidate() { fireInvalidated(ChartBits.ChartLayout, ChartBits.ChartCanvas); } - protected void updateLegend() { + protected void runPreLayout() { + state.setDirty(dataSetState.clear()); + if (state.isClean()) { + return; + } + + // Update legend if (state.isDirty(ChartBits.ChartLegend)) { updateLegend(getDatasets(), getRenderers()); } state.clear(ChartBits.ChartLegend); - } - - protected void runPreLayout() { - forEachDataSet(ds -> lockedDataSets.add(ds.lock().readLock())); - updateLegend(); + // Update data ranges final long start = ProcessingProfiler.getTimeStamp(); + ensureLockedDataSets(); updateAxisRange(); // Update data ranges etc. to trigger anything that might need a layout ProcessingProfiler.getTimeDiff(start, "updateAxisRange()"); + // Update other components for (Renderer renderer : renderers) { renderer.runPreLayout(); } - for (ChartPlugin plugin : plugins) { plugin.runPreLayout(); } @@ -548,38 +528,59 @@ public void layoutChildren() { // 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()) { + // that call for a layout without any dirty bits. It is also + // possible that the layout triggers a resizing, so we may + // need to lock the datasets here. + if (state.isDirty()) { + ensureLockedDataSets(); layoutPluginsChildren(); } } protected void runPostLayout() { + // nothing to do + if (state.isClean() && !hasLocked) { + return; + } + ensureLockedDataSets(); + // 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(); + + // Redraw the axes (they internally check dirty bits) for (Axis axis : axesList) { axis.drawAxis(); } + + // Redraw the main canvas redrawCanvas(); + + // Update other components for (Renderer renderer : renderers) { renderer.runPostLayout(); } for (var plugin : plugins) { plugin.runPostLayout(); } + + // Clear bits clearStates(); // TODO: plugins etc., do locking ProcessingProfiler.getTimeDiff(start, "updateCanvas()"); } + protected void ensureLockedDataSets() { + if (!hasLocked) { + forEachDataSet(ds -> lockedDataSets.add(ds.lock().readLock())); + hasLocked = true; + } + } + protected void clearStates() { for (var renderer : getRenderers()) { if (renderer instanceof EventSource) { @@ -593,13 +594,16 @@ protected void clearStates() { } } - dataSetState.clear(); state.clear(); for (var ds : lockedDataSets) { + for (AxisDescription axisDescription : ds.getAxisDescriptions()) { + axisDescription.getBitState().clear(); + } ds.getBitState().clear(); // technically a 'write' ds.lock().readUnLock(); } + hasLocked = false; lockedDataSets.clear(); } @@ -638,6 +642,7 @@ private void forEachDataSet(Consumer action) { } private final List lockedDataSets = new ArrayList<>(); + private boolean hasLocked = false; public final ObjectProperty legendProperty() { return legend; @@ -905,6 +910,16 @@ protected void updatePluginsArea() { fireInvalidated(ChartBits.ChartPlugins); } + /** + * Dataset changes do not trigger a pulse, so in order + * to ensure a redraw we manually request a layout. We + * use an unmanaged node without a layout implementation, + * so that we don't accidentally do unnecessary work. + */ + private void ensureJavaFxPulse() { + styleableNodes.requestLayout(); + } + /** * @return The CssMetaData associated with this class, which may include the CssMetaData of its super classes. * @since JavaFX 8.0 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 ab7da835a..1775ae230 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 @@ -208,8 +208,9 @@ public void updateAxisRange() { // are already locked, so we can use parallel stream without extra synchronization. getAllDatasets().stream() .filter(DataSet::isVisible) + .filter(ds -> ds.getBitState().isDirty()) .forEach(dataset -> dataset.getAxisDescriptions().parallelStream() - .filter(axisD -> !axisD.isDefined()) + .filter(axisD -> !axisD.isDefined() || axisD.getBitState().isDirty()) .forEach(axisDescription -> dataset.recomputeLimits(axisDescription.getDimIndex()))); // Update each of the axes @@ -360,7 +361,7 @@ protected static void updateNumericAxis(final Axis axis, final List dat if (axis.isAutoGrowRanging() && axis.getAutoRange().isDefined()) { changed = axis.getAutoRange().add(dsRange); } else { - changed = axis.getAutoRange().set(dsRange); + changed = axis.getAutoRange().set(dsRange.getMin(), dsRange.getMax()); } // Trigger a redraw @@ -368,10 +369,5 @@ protected static void updateNumericAxis(final Axis axis, final List dat axis.invalidateRange(); } - // TODO: is this used for anything? can it be removed? - double axisLength = axis.getLength() == 0 ? 1 : axis.getLength(); - axis.getAutoRange().setAxisLength(axisLength, side); - axis.getUserRange().setAxisLength(axisLength, side); - } } diff --git a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java index 3cc754827..83549bd51 100644 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java +++ b/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/css/StyleGroup.java @@ -7,17 +7,19 @@ import javafx.collections.ObservableList; import javafx.scene.Group; import javafx.scene.Node; +import javafx.scene.Parent; import javafx.scene.layout.Pane; import javax.swing.event.ChangeListener; /** - * A hidden group that holds styles. This hides style nodes even - * if the visibility property is set to true. + * A hidden node that holds styles. This hides style nodes even + * if the visibility property is set to true. This node is + * unmanaged and does not do any layout. * * @author ennerf */ -public class StyleGroup extends Group { +public class StyleGroup extends Parent { public StyleGroup(Pane pane, String... paneStyles) { this(pane, pane.getChildren(), paneStyles); @@ -30,11 +32,19 @@ public StyleGroup(Node parent, ObservableList children, String... parentSt public StyleGroup(ObservableList children) { StyleUtil.hiddenStyleNode(this); - setAutoSizeChildren(false); - relocate(0, 0); children.add(this); } + @Override + public void layoutChildren() { + // do nothing + } + + @Override + public ObservableList getChildren() { + return super.getChildren(); + } + public LineStyle newLineStyle(String... styles) { return addToChildren(new LineStyle(styles)); } 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 deleted file mode 100644 index 4c03bb902..000000000 --- a/chartfx-chart/src/main/java/io/fair_acc/chartfx/ui/utils/LayoutHook.java +++ /dev/null @@ -1,165 +0,0 @@ -package io.fair_acc.chartfx.ui.utils; - -import io.fair_acc.dataset.utils.AssertUtils; -import javafx.beans.value.ChangeListener; -import javafx.scene.Node; -import javafx.scene.Scene; - -/** - * Utility class for registering pre-layout and post-layout hooks - * that get executed once. Each JavaFX tick is executed in phases: - *

- * 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 = 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(); - } - - // 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); - } - }); - } - - /** - * @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 boolean isRegistered() { - return registeredScene != null; - } - - public LayoutHook registerOnce() { - // 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 (isRegistered()) { - unregister(); - } - - // Register only if the scene is valid - if (scene != null) { - scene.addPreLayoutPulseListener(preLayoutAndAdd); - registeredScene = scene; - } - return this; - } - - /** - * 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. - */ - private 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. - hasRunPreLayout = true; - preLayoutAction.run(); - registeredScene.addPostLayoutPulseListener(postLayoutAndRemove); - } - - private void runPostLayoutAndRemove() { - postLayoutAction.run(); - unregister(); - } - - private void unregister() { - if (!isRegistered()) { - return; - } - 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; - final ChangeListener windowResizeListener = (obs, old, size) -> registerOnce(); - 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 5b6893430..588989a9d 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 @@ -11,6 +11,7 @@ import io.fair_acc.chartfx.Chart; import io.fair_acc.dataset.events.StateListener; +import io.fair_acc.dataset.utils.AssertUtils; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -341,4 +342,52 @@ public static Optional tryGetChartParent(Node node) { return Optional.empty(); } + /** + * Utility method for registering pre-layout and post-layout hooks. + * Each JavaFX tick is executed in phases: + *

+ * 1) animations/timers, e.g., Platform.runLater() + * 2) pre-layout hook + * 3) CSS styling pass (styling etc. gets updated) + * 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. + *

+ * The layout hooks will be executed every time there is a pulse, + * (e.g. renders or mouse press events), but they do not trigger + * a pulse by themselves. + *

+ * This class registers actions as soon as a Scene is available, + * and unregisters them when the Scene is removed. Note that the + * Scene is set during the CSS phase, so the first execution is + * triggered immediately. + */ + public static void registerLayoutHooks(Node node, Runnable preLayoutAction, Runnable postLayoutAction) { + AssertUtils.notNull("preLayoutAction", preLayoutAction); + AssertUtils.notNull("preLayoutAction", postLayoutAction); + node.sceneProperty().addListener((observable, oldScene, scene) -> { + // Remove from the old scene + if (oldScene != null) { + oldScene.removePreLayoutPulseListener(preLayoutAction); + oldScene.removePostLayoutPulseListener(postLayoutAction); + } + + // Register when the scene changes. The scene reference gets + // set in the CSS phase, so by the time we can register it is + // already be too late. Waiting for the layout phase wouldn't + // let us change the scene graph, so we need to manually run the + // layout hook during CSS. + if (scene != null) { + scene.addPreLayoutPulseListener(preLayoutAction); + scene.addPostLayoutPulseListener(postLayoutAction); + preLayoutAction.run(); + } + }); + } + } 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 0617fa31d..21cebdd88 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 @@ -115,12 +115,7 @@ -fx-fill: rgba(65, 110, 244, 0.4078431373); } -/* - Set some reasonable default sizes so users - can override them via CSS and JavaFX panes - won't flicker (usually +/- 1px) or act odd. - */ -.chart, .chart-content, .chart-plot-area { +.chart { -fx-padding: 0px; -fx-min-width: 100px; -fx-min-height: 100px; @@ -128,13 +123,11 @@ -fx-pref-height: 450px; -fx-max-height: 4096px; -fx-max-width: 4096px; -} - -.chart { -fx-tool-bar-side: TOP; } .chart .chart-title { visibility: visible; + -fx-alignment: center; -fx-side: top; -fx-rotate: 0; } diff --git a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss index 104d21461..0777fb269 100644 --- a/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss +++ b/chartfx-chart/src/main/resources/io/fair_acc/chartfx/chart.scss @@ -141,8 +141,10 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl } .chart-plot-background { } - .chart-plot-content { - .chart-plot-area { + .chart-plot-area { + .chart-canvas-area { + .chart-canvas { + } } } .chart-plot-foreground { @@ -159,12 +161,11 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl } } -/* - Set some reasonable default sizes so users - can override them via CSS and JavaFX panes - won't flicker (usually +/- 1px) or act odd. - */ -.chart, .chart-content, .chart-plot-area { +// XY Chart styles +.chart { + + // Set some reasonable default sizes so JavaFX panes won't flicker + // (often +/- 1px) and users users can override them via CSS -fx-padding: 0px; -fx-min-width: 100px; -fx-min-height: 100px; @@ -172,15 +173,13 @@ $null: null; // null gets removed from Sass. Maybe create a placeholder and repl -fx-pref-height: 450px; -fx-max-height: 4096px; -fx-max-width: 4096px; -} -// XY Chart styles -.chart { - // TODO: they don't seem to work and maybe we should create panes with a side property? deprecate these? + // TODO: the side property doesn't seem to work. Should probably become a dedicated pane -fx-tool-bar-side: TOP; .chart-title { visibility: visible; + -fx-alignment: center; -fx-side: top; -fx-rotate: 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 index 4e4746236..2752f5093 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 @@ -126,12 +126,15 @@ public void setDirty(int bits) { */ protected abstract int setDirtyAndGetDelta(int bits); - public void clear() { - clear(ALL_BITS); + /** + * @return the bits before clearing + */ + public int clear() { + return clear(ALL_BITS); } /** - * @return the state after clearing the specified bits + * @return the state before clearing the specified bits */ public abstract int clear(int bits); @@ -369,8 +372,9 @@ protected int setDirtyAndGetDelta(int bits) { @Override public int clear(final int bits) { + int previous = state; state &= ~bits; - return state; + return previous; } @Override @@ -413,7 +417,7 @@ public int clear(int bits) { final int current = getBits(); final int newState = current & ~bits; if (state.compareAndSet(current, newState)) { - return newState; + return current; } } }