JavaFX图表自动缩放错误,数字较小

时间:2015-03-18 14:27:06

标签: java javafx

我正在使用JavaFX构建StackedBarChart。随着新数据的出现,图表会发生变化。我用一个更新图表的按钮来模拟图表。

它大部分工作正常,但我注意到值较低(值小于~100),当我第一次更新图表时,Y轴标签看起来有些偏差:

JavaFX chart with okay auto-scaling on Y Axis

Weirder仍然,如果我第二次(或第三次,第四次......)更新图表,Y轴的自动缩放是远离的:

JavaFX StackedBarChart with bad auto-scaling on Y Axis

如果我使用更大的值(值> ~1000),则自动缩放可以正常工作。如果我停用图表动画,那么自动缩放工作正常。第一次更新图表时自动缩放工作正常,但之后没有。

这是我使用的代码,它与this JavaFX教程中的代码完全相同。

import java.util.Arrays;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class StackedBarChartSample extends Application {

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);

    @Override
    public void start(Stage stage) {

        stage.setTitle("Bar Chart Sample");

        sbc.setAnimated(true); //change this to false and autoscaling works!


        Button button = new Button("update");
        button.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                updateChart();
            }

        });

        VBox contentPane = new VBox();
        contentPane.getChildren().addAll(sbc, button);

        Scene scene = new Scene(contentPane, 800, 600);

        stage.setScene(scene);
        stage.show();
    }


    private void updateChart(){

        int m = 1; //change this to 100 and autoscaling works!


        xAxis.setCategories(FXCollections.<String>observableArrayList());
        sbc.getData().clear();

        final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
        final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();

        series1.setName("ABC");
        series1.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series1.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series1.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        series2.setName("XYZ");
        series2.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series2.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series2.getData().add(new XYChart.Data<String, Number>("three", 10*m));


        xAxis.setCategories(FXCollections.<String>observableArrayList(Arrays.asList("one", "two", "three")));
        sbc.getData().addAll(series1, series2);
    }

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

奖励问题:即使动画有效,动画也会显示每个堆叠条形图的2个额外部分,这些部分会在动画完成时消失。有没有办法在动画期间摆脱那些额外的部分?

2 个答案:

答案 0 :(得分:5)

您正在使用图表,动画和可观察列表的修改,似乎不应该这样做。好吧,我最初也是。我遇到过像这样的问题,其中包括ArrayIndexOutOfBounds Exceptions。简而言之:JavaFX图表的动画非常破碎,或者看起来如此。它似乎不可靠,因此在您直接修改列表时无法使用。

我停止使用动画或修改列表,而是在每次更改时设置列表。这两种方法都解决了这个问题。

使用sbc.setData()而不是sbc.getData()。clear()和sbc.getData()。addAll()修改示例。这有效,我。即动画仍在播放:

public class StackedBarChartSample extends Application {

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);

    Random rnd = new Random();

    @Override
    public void start(Stage stage) {

        stage.setTitle("Bar Chart Sample");

        sbc.setAnimated(true); //change this to false and autoscaling works!

        Button button = new Button("update");
        button.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                updateChart();
            }

        });

        VBox contentPane = new VBox();
        contentPane.getChildren().addAll(sbc, button);

        Scene scene = new Scene(contentPane, 800, 600);

        stage.setScene(scene);
        stage.show();
    }


    private void updateChart(){

        int m = 1; //change this to 100 and autoscaling works!

        m = rnd.nextInt(100) + 1; // just some random value to see changes in the chart

        System.out.println( "m = " + m);

        final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
        final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();

        series1.setName("ABC");
        series1.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series1.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series1.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        series2.setName("XYZ");
        series2.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series2.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series2.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        sbc.setData( FXCollections.observableArrayList(series1, series2));
    }

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

我为你的m添加了随机性,以便看到条形图的变化。

但真正的问题是你使用图表的方式。您不应该修改列表,而是应该修改列表项的数据。然后图表动画按预期工作,条形图随数据上升和下降。

使用“创建”(创建新图表列表)和“修改”(修改列表数据,但不创建新列表)按钮的示例:

public class StackedBarChartSample extends Application {

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);

    Random rnd = new Random();

    final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
    final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();

    @Override
    public void start(Stage stage) {

        stage.setTitle("Bar Chart Sample");

        sbc.setAnimated(true); //change this to false and autoscaling works!

        Button createButton = new Button("Create");
        createButton.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                createChartData();
            }

        });

        Button modifyButton = new Button("Modify");
        modifyButton.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                modifyChartData();
            }

        });

        VBox contentPane = new VBox();
        contentPane.getChildren().addAll(sbc, createButton, modifyButton);

        Scene scene = new Scene(contentPane, 800, 600);

        stage.setScene(scene);
        stage.show();
    }


    private void createChartData(){

        int m = 1; //change this to 100 and autoscaling works!

        m = rnd.nextInt(100) + 1; // just some random value to see changes in the chart

        System.out.println( "m = " + m);

        series1.setName("ABC");
        series1.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series1.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series1.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        series2.setName("XYZ");
        series2.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series2.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series2.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        sbc.setData( FXCollections.observableArrayList(series1, series2));
    }

    private void modifyChartData() {

        int m = 1; //change this to 100 and autoscaling works!

        m = rnd.nextInt(100) + 1; // just some random value to see changes in the chart

        System.out.println( "m = " + m);

        for( XYChart.Data<String,Number> data: series1.getData()) {
            data.setYValue(rnd.nextInt(30) * m);
        }

        for( XYChart.Data<String,Number> data: series2.getData()) {
            data.setYValue(rnd.nextInt(30) * m);
        }


    }

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

答案 1 :(得分:2)

问题源于两件事。

  1. 在“同一时间”(在任何动画开始之前)添加和删除数据
  2. y轴自动缩放以适合动画数据
  3. 通过移除旧数据sbc.getData().clear()并同时添加新数据sbc.getData().addAll(series1, series2);,您可以同时启动两组动画。

    我修改了你的代码(在帖子的最后),将更新功能扩展到清除和更新功能,并为它们添加了各种按钮。

    • 清除
    • 更新
    • 清除&amp;更新
    • 清除&amp;稍后更新
    • 清除(无动画)&amp;更新

    并排运行两次,一次是小值,另一次是大值。

    如果点击更新,则会看到数据同时下降。 如果清除该数据,则会看到删除动画,但请注意y轴刻度。两个数据集都是一样的。即使您使用数百万的值,它仍然保持不变。

    现在,如果你正在做Clear&amp;更新yAxis将适合动画的较大值。 使用较小的值时,旧数据占主导地位,因为它在动画期间增长并保持大于新数据。 对于较大的值,新数据占主导地位,因为它大于旧数据的最终结束点。

    然后在删除动画结束时,旧数据被清除,但y轴保持不变,如果小值看起来完全是FUBARd。

    我个人认为这是JavaFX的一个错误。它几乎感觉就像动画在650处以y开始和停止。我这样说是因为当添加具有小值的数据时,条形似乎下降到位,但是具有大的值它们似乎长大到位。我不能肯定地说,因为我没有做过代码潜水。

    我想到的第一个解决方案是在所有动画完成后告诉y轴再次自动缩放。但是,我认为当图表完成动画制作时,目前无法获得通知。这可能是错的,但我认为无论如何都不重要。这些条会缩放到非常小的尺寸然后再次变大。最终结果很好但是奇怪且延迟了用户体验。

    编辑 :正如Roland在下面评论的那样,下一段中的方法有一点代码味道。我将它留在这里因为它有效但不要使用它。要么使用我的最后建议,要么使用Roland在答案中提供的方式。

    解决方法是不要同时启动两个动画。最后运行的动画需要是添加数据的动画。这可以在Clear&amp; amp;的代码中看到。稍后更新按钮。 然而,这对我来说仍然有点奇怪。

    我视觉上喜欢的方式,我认为也解决了红利问题;不要动画数据删除。如果您之前关闭它并在之后重新启用它,您可以获得漂亮的新数据动画,而旧数据只会变得很糟糕。 看到Clear(无动画)&amp;更新按钮。

    import java.util.Arrays;
    import java.util.Timer;
    import java.util.TimerTask;
    
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.collections.FXCollections;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.chart.CategoryAxis;
    import javafx.scene.chart.NumberAxis;
    import javafx.scene.chart.StackedBarChart;
    import javafx.scene.chart.XYChart;
    import javafx.scene.control.Button;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class StackedBarChartSample extends Application
    {
    
        final CategoryAxis xAxis = new CategoryAxis();
        final NumberAxis yAxis = new NumberAxis();
        final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);
    
        @Override
        public void start(final Stage stage)
        {
    
            stage.setTitle("Bar Chart Sample");
    
            sbc.setAnimated(true);
    
            final Button updateButton = new Button("update");
            updateButton.setOnAction(new EventHandler<ActionEvent>()
            {
                @Override
                public void handle(final ActionEvent arg0)
                {
                    updateChart();
                }
    
            });
    
            final Button clearButton = new Button("Clear");
            clearButton.setOnAction(new EventHandler<ActionEvent>()
            {
                @Override
                public void handle(final ActionEvent arg0)
                {
                    clearChart(true);
                }
    
            });
    
            final Button clearAndUpdateButton = new Button("Clear & Update");
            clearAndUpdateButton.setOnAction(new EventHandler<ActionEvent>()
            {
                @Override
                public void handle(final ActionEvent evt)
                {
                    clearChart(true);
                    updateChart();
                }
    
            });
    
            final Button clearAndUpdateLaterButton = new Button("Clear & Update Later");
            clearAndUpdateLaterButton.setOnAction(new EventHandler<ActionEvent>()
            {
                @Override
                public void handle(final ActionEvent evt)
                {
                    clearChart(true);
                    new Timer(true).schedule(new TimerTask()
                    {
                        @Override
                        public void run()
                        {
                            Platform.runLater(() -> updateChart());
                        }
                    }, 1000);
                }
    
            });
    
            final Button clearNoAnimAndUpdateButton = new Button("Clear (no anim) & Update");
            clearNoAnimAndUpdateButton.setOnAction(new EventHandler<ActionEvent>()
            {
                @Override
                public void handle(final ActionEvent evt)
                {
                    clearChart(false);
                    updateChart();
                }
    
            });
    
            final VBox contentPane = new VBox();
            contentPane.getChildren().addAll(sbc, clearButton, updateButton, clearAndUpdateButton, clearAndUpdateLaterButton,
                    clearNoAnimAndUpdateButton);
    
            final Scene scene = new Scene(contentPane, 800, 600);
    
            stage.setScene(scene);
            stage.show();
    
            xAxis.setCategories(FXCollections.<String> observableArrayList(Arrays.asList("one", "two", "three")));
        }
    
        private void clearChart(final boolean animate)
        {
            System.out.println("Clear");
            final boolean wasAnimated = sbc.getAnimated();
            if (!animate) {
                sbc.setAnimated(false);
            }
    
            sbc.getData().clear();
    
            if (!animate) {
                sbc.setAnimated(wasAnimated);
            }
        }
    
        private void updateChart()
        {
            System.out.println("Update");
            final int m = 1; // change this to 100 and autoscaling works!
    
            final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
            final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();
    
            series1.setName("ABC");
            series1.getData().add(new XYChart.Data<String, Number>("one", 25 * m));
            series1.getData().add(new XYChart.Data<String, Number>("two", 20 * m));
            series1.getData().add(new XYChart.Data<String, Number>("three", 10 * m));
    
            series2.setName("XYZ");
            series2.getData().add(new XYChart.Data<String, Number>("one", 25 * m));
            series2.getData().add(new XYChart.Data<String, Number>("two", 20 * m));
            series2.getData().add(new XYChart.Data<String, Number>("three", 10 * m));
    
            sbc.getData().addAll(series1, series2);
        }
    
        public static void main(final String[] args)
        {
            launch(args);
        }
    }