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

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

它主要工作正常,但我注意到,当值较低(值小于~100)时,Y轴标签在我第一次更新图表时似乎有点偏差:

在Y轴上可以自动缩放的JavaFX图表

更奇怪的是,如果我第二次(或第三次,第四次……)更新图表,Y轴的自动缩放就会偏离:

JavaFX StackedBarChart在Y轴上具有错误的自动缩放

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

这是我正在使用的代码,它与此 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 sbc = new StackedBarChart(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(){ @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.observableArrayList()); sbc.getData().clear(); final XYChart.Series series1 = new XYChart.Series(); final XYChart.Series series2 = new XYChart.Series(); series1.setName("ABC"); series1.getData().add(new XYChart.Data("one", 25*m)); series1.getData().add(new XYChart.Data("two", 20*m)); series1.getData().add(new XYChart.Data("three", 10*m)); series2.setName("XYZ"); series2.getData().add(new XYChart.Data("one", 25*m)); series2.getData().add(new XYChart.Data("two", 20*m)); series2.getData().add(new XYChart.Data("three", 10*m)); xAxis.setCategories(FXCollections.observableArrayList(Arrays.asList("one", "two", "three"))); sbc.getData().addAll(series1, series2); } public static void main(String[] args) { launch(args); } } 

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

您正在使用图表,动画和可观察列表的修改,似乎不应该这样做。 好吧,我最初也是。 我遇到过像这样的问题,其中包括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 sbc = new StackedBarChart(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(){ @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 series1 = new XYChart.Series(); final XYChart.Series series2 = new XYChart.Series(); series1.setName("ABC"); series1.getData().add(new XYChart.Data("one", 25*m)); series1.getData().add(new XYChart.Data("two", 20*m)); series1.getData().add(new XYChart.Data("three", 10*m)); series2.setName("XYZ"); series2.getData().add(new XYChart.Data("one", 25*m)); series2.getData().add(new XYChart.Data("two", 20*m)); series2.getData().add(new XYChart.Data("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 sbc = new StackedBarChart(xAxis, yAxis); Random rnd = new Random(); final XYChart.Series series1 = new XYChart.Series(); final XYChart.Series series2 = new XYChart.Series(); @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(){ @Override public void handle(ActionEvent arg0) { createChartData(); } }); Button modifyButton = new Button("Modify"); modifyButton.setOnAction(new EventHandler(){ @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("one", 25*m)); series1.getData().add(new XYChart.Data("two", 20*m)); series1.getData().add(new XYChart.Data("three", 10*m)); series2.setName("XYZ"); series2.getData().add(new XYChart.Data("one", 25*m)); series2.getData().add(new XYChart.Data("two", 20*m)); series2.getData().add(new XYChart.Data("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 data: series1.getData()) { data.setYValue(rnd.nextInt(30) * m); } for( XYChart.Data data: series2.getData()) { data.setYValue(rnd.nextInt(30) * m); } } public static void main(String[] args) { launch(args); } } 

问题源于两件事。

  1. 在“同一时间”添加和删除数据(在任何动画开始之前)
  2. y轴自动缩放以适合动画数据

通过删除旧数据sbc.getData().clear()并添加新数据sbc.getData().addAll(series1, series2); 同时你导致两组动画同时启动。

我已经修改了你的代码(在post的末尾),将更新function扩展到清除和更新function,并为它们添加了各种按钮。

  • 明确
  • 更新
  • 清除和更新
  • 清除并稍后更新
  • 清除(无动画)和更新

并排运行程序两次,一个值小,另一个值大。

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

现在,如果您正在进行清除和更新,则yAxis将适合更大的动画值。 使用较小的值时,旧数据占主导地位,因为它在动画期间增长并保持大于新数据。 对于较大的值,新数据占主导地位,因为它大于旧数据的最终结束点。

然后在删除动画结束时清除旧数据但是y轴保持不变,并且在小值的情况下看起来完全是FUBARd。

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

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

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

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

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

 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 sbc = new StackedBarChart(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() { @Override public void handle(final ActionEvent arg0) { updateChart(); } }); final Button clearButton = new Button("Clear"); clearButton.setOnAction(new EventHandler() { @Override public void handle(final ActionEvent arg0) { clearChart(true); } }); final Button clearAndUpdateButton = new Button("Clear & Update"); clearAndUpdateButton.setOnAction(new EventHandler() { @Override public void handle(final ActionEvent evt) { clearChart(true); updateChart(); } }); final Button clearAndUpdateLaterButton = new Button("Clear & Update Later"); clearAndUpdateLaterButton.setOnAction(new EventHandler() { @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() { @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. 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 series1 = new XYChart.Series(); final XYChart.Series series2 = new XYChart.Series(); series1.setName("ABC"); series1.getData().add(new XYChart.Data("one", 25 * m)); series1.getData().add(new XYChart.Data("two", 20 * m)); series1.getData().add(new XYChart.Data("three", 10 * m)); series2.setName("XYZ"); series2.getData().add(new XYChart.Data("one", 25 * m)); series2.getData().add(new XYChart.Data("two", 20 * m)); series2.getData().add(new XYChart.Data("three", 10 * m)); sbc.getData().addAll(series1, series2); } public static void main(final String[] args) { launch(args); } }