我正在使用JavaFX构建StackedBarChart。随着新数据的出现,图表会发生变化。我用一个更新图表的按钮来模拟图表。
它大部分工作正常,但我注意到值较低(值小于~100),当我第一次更新图表时,Y轴标签看起来有些偏差:
Weirder仍然,如果我第二次(或第三次,第四次......)更新图表,Y轴的自动缩放是远离的:
如果我使用更大的值(值> ~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个额外部分,这些部分会在动画完成时消失。有没有办法在动画期间摆脱那些额外的部分?
答案 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)
问题源于两件事。
通过移除旧数据sbc.getData().clear()
并同时添加新数据sbc.getData().addAll(series1, series2);
,您可以同时启动两组动画。
我修改了你的代码(在帖子的最后),将更新功能扩展到清除和更新功能,并为它们添加了各种按钮。
并排运行两次,一次是小值,另一次是大值。
如果点击更新,则会看到数据同时下降。 如果清除该数据,则会看到删除动画,但请注意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);
}
}