chart-fx icon indicating copy to clipboard operation
chart-fx copied to clipboard

Axes min/max are updated but charts are not updated to the new range

Open BBOYGF opened this issue 2 years ago • 6 comments

Describe the bug 我在用这个开源软件它非常棒,速度非常快但是我遇到一个问题。我在用DefaultNumericAxis类作为横坐标,但是当横坐标更新后我发现图并没有更新,我看到源码中作者是否忘记取更新缓存(调用AbstractAxisParameter 类的updateCachedVariables 方法)希望作者看到以后能修复这个BUG,谢谢祝您天天开心。

I'm using this open source software and it's great, it's really fast but I ran into a problem. I'm using DefaultNumericAxis as the abscissa, but when the abscissa is updated I find that the graph is not updated, I see whether the author forgot to take the update cache in the source code (call the AbstractAxisParameter class updateCachedVariables method). I hope the author can fix this BUG after reading it. Thank you and wish you a happy day.


    /**
     * 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);
                updateCachedVariables(); // 我认为需要修改的地方 What I think needs to be changed
                invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
            }
        }
    };

    /**
     * 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);
                updateCachedVariables(); // 我认为需要修改的地方 What I think needs to be changed
                invokeListener(new AxisChangeEvent(AbstractAxisParameter.this));
            }
        }
    };

Environment:

  • win10
  • Java version:16
  • JavaFx version: 17
  • ChartFx version: [master, chartfx-chart:11.2.5]

BBOYGF avatar Jul 13 '22 08:07 BBOYGF

Hey, thanks for the feedback on using our library. Could you provide a minimal sample which reproduces your problem? The updateCachedVariables() function is called in the layouting step which is triggered by the AxisChangeEvent emitted in the line below, so it should not be necessary here and there is probably some other problem.

wirew0rm avatar Jul 13 '22 08:07 wirew0rm

谢谢您的回复,这是我提出问题的原因,你可以运行一下下面的代码会发现X轴更新了但是图没有更新,期待你的回复谢谢! Thank you for your reply, this is the reason why I raised the question. You can run the following code and find that the X-axis is updated but the graph is not. Looking forward to your reply, thank you!

import de.gsi.chart.XYChart;
import de.gsi.chart.axes.spi.DefaultNumericAxis;
import de.gsi.chart.renderer.spi.ErrorDataSetRenderer;
import de.gsi.dataset.spi.DoubleDataSet;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

/**
 *
 * @author guofan
 * @date 2022/7/13
 */
public class ChartFxSample extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        DefaultNumericAxis xAxis = new DefaultNumericAxis();
        xAxis.setName("X");
        xAxis.setAutoRanging(false);
        DefaultNumericAxis yAxis = new DefaultNumericAxis();
        yAxis.setName("Y");
        XYChart chart = new XYChart(xAxis, yAxis);
        DoubleDataSet dataSet = new DoubleDataSet("data");
        double[] xValues = new double[1000];
        double[] yValues = new double[1000];
        for (int i = 0; i < 1000; i++) {
            xValues[i] = i;
            yValues[i] = Math.sin(i / 10);
        }
        dataSet.set(xValues, yValues);
        ErrorDataSetRenderer renderer = new ErrorDataSetRenderer();
        renderer.getDatasets().add(dataSet);
        chart.getRenderers().add(renderer);
        xAxis.setMin(-500);
        xAxis.setMax(1500);
        BorderPane borderPane = new BorderPane();
        borderPane.setCenter(chart);
        Button updateXAxisButton = new Button("updateXAxis");
        updateXAxisButton.setOnAction(event -> {
            xAxis.setMin(0);
        });
        borderPane.setBottom(updateXAxisButton);
        Scene scene = new Scene(borderPane);
        primaryStage.setScene(scene);
        primaryStage.setWidth(600);
        primaryStage.setHeight(400);
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

BBOYGF avatar Jul 13 '22 09:07 BBOYGF

Ok, I can reproduce the issue, thanks for the fast reply. We usually have fast updating data or use interactive rescaling where the chart lagging one update behind the axes it not really noticeable. The core problem is, that the axes and charts are both rendered in the layoutChildren step of javafx and the plot is rendered before the axes and so it does not get updated before. Updating the cache on each change kind of defeats its purpose, so we will have to fix the root cause of this.

Meanwhile as a workaround you can either use a custom axis with the changes you proposed above or use this snippet in your app to force a second redraw:

xAxis.setMin(0);
FXUtils.waitForFxTicks(scene, 1);
chart.requestLayout();

I'm actually in the process of restructuring the layouting and rendering code for the 11.3 release where i hope to improve problems like this, see #527 and #529 .

wirew0rm avatar Jul 13 '22 09:07 wirew0rm

谢谢你的回复,但是没有解决我的问题,我的解决方案是这样的。 Thanks for your reply, but it doesn't solve my problem. My solution is as follows.

import de.gsi.chart.axes.spi.DefaultNumericAxis;

/**
 * 可更手动更新的轴
 *
 * @author guofan
 * @date 2022/7/13
 */
public class UpdateNumericAxis extends DefaultNumericAxis {
    /**
     * 更新轴缓存
     */
    public void updateAxisCache() {
        updateCachedVariables();
    }
}

import de.gsi.chart.XYChart;
import de.gsi.chart.axes.spi.DefaultNumericAxis;
import de.gsi.chart.renderer.spi.ErrorDataSetRenderer;
import de.gsi.dataset.spi.DoubleDataSet;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

/**
 *
 * @author guofan
 * @date 2022/7/13
 */
public class ChartFxSample extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        UpdateNumericAxis xAxis = new UpdateNumericAxis();
        xAxis.setName("X");
        xAxis.setAutoRanging(false);
        // 添加更新缓存  Add update cache
        xAxis.minProperty().addListener((observable, oldValue, newValue) -> xAxis.updateAxisCache());

        DefaultNumericAxis yAxis = new DefaultNumericAxis();
        yAxis.setName("Y");
        XYChart chart = new XYChart(xAxis, yAxis);
        DoubleDataSet dataSet = new DoubleDataSet("data");
        double[] xValues = new double[1000];
        double[] yValues = new double[1000];
        for (int i = 0; i < 1000; i++) {
            xValues[i] = i;
            yValues[i] = Math.sin(i / 10);
        }
        dataSet.set(xValues, yValues);
        ErrorDataSetRenderer renderer = new ErrorDataSetRenderer();
        renderer.getDatasets().add(dataSet);
        chart.getRenderers().add(renderer);
        xAxis.setMin(-500);
        xAxis.setMax(1500);
        BorderPane borderPane = new BorderPane();
        borderPane.setCenter(chart);
        Button updateXAxisButton = new Button("updateXAxis");
        updateXAxisButton.setOnAction(event -> {
            xAxis.setMin(0);
        });
        Scene scene = new Scene(borderPane);
        borderPane.setBottom(updateXAxisButton);
        primaryStage.setScene(scene);
        primaryStage.setWidth(600);
        primaryStage.setHeight(400);
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

BBOYGF avatar Jul 13 '22 10:07 BBOYGF

@guofan2019 thanks for reporting this issue and sharing your solution. We also noticed this behavior for a long time and assumed it was a mistake in our implementation. We added an extra layout() call after essentially all plot changes to solve the problem, but I think your solution might be more elegant better.

We also exclusively use chartfx for fixed charts that don't update in real-time, so this explains why we have also see this problem. I assume many others use this awesome library in this way, so it would be really great if this was fixed in the library.

karlduderstadt avatar Jul 27 '22 14:07 karlduderstadt

... Meanwhile as a workaround you can either use a custom axis with the changes you proposed above or use this snippet in your app to force a second redraw:

xAxis.setMin(0);
FXUtils.waitForFxTicks(scene, 1);
chart.requestLayout();

...

I too came across this issue and wanted to add my findings. The work-around will not work, if you already are on the Fx Application Thread, because in that case the chartfx library makes a call to Platform.requestNextPulse(). But this method returns immediatly, not waiting for the pulse to happen and thus chart.requestLayout() is called too early.

ichaus-we avatar Aug 08 '22 13:08 ichaus-we

solved by #592, which will be included in the major release coming soon. Sorry for having to live with the workarounds for so long, feel free to reopen if the problems persist on main or in the upcoming 11.3 release.

wirew0rm avatar Aug 09 '23 09:08 wirew0rm