启动程序后重新加载并显示空白的JavaFX BarChart [英] Reload and display blank JavaFX BarChart after launching the program

查看:214
本文介绍了启动程序后重新加载并显示空白的JavaFX BarChart的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个JavaFX程序,该程序可以将不同的杂货项目显示为BarChart。我的代码结构使用初始加载类 GroceryGrapher.java ,控制器 GroceryGrapherController.java 和.fxml文件 GroceryGrapher.fxml



我对JavaFX比较陌生,我尝试了各种方法来创建和显示 BarChart 。在大多数情况下,我已经解决了问题,可以将其数据和组件加载到图表中,但是我的问题是,程序启动后,图表不会显示/更新。 BarChart AnchorPane 的一部分(全部位于 StackPane ),并且在初始化后尝试对其进行更新,因为它开始为空白,但仍为空白。



BarChart AnchorPane 的一部分,后者还具有 MenuBar 。我的目标是能够通过 MenuBar 更新 BarChart 的不同部分。加载程序时,将加载 MenuBar 和空的 BarChart





这就是我初始化 BarChart 及其数据:

 公共类GroceryGrapherController实现可初始化{{b 
$ b @FXML
私有AnchorPane anchorPane =新的AnchorPane();

@FXML
私有NumberAxis xAxis = new NumberAxis();

@FXML
私有CategoryAxis yAxis = new CategoryAxis();

@FXML
私人BarChart< Number,String> groceryBarChart;

@FXML
//私有阶段
父根;

@Override
public void initialize(URL位置,ResourceBundle资源){
groceryBarChart = new BarChart< Number,String>(xAxis,yAxis);
groceryBarChart.setTitle( Horizo​​ntal Grocery List);
xAxis.setLabel( Number);
xAxis.setTickLabelRotation(90);
yAxis.setLabel(杂货类型);

//填充BarChart
XYChart.Series< Number,String> series1 =新的XYChart.Series<>();
series1.setName( Chocolates);
series1.getData()。add(new XYChart.Data< Number,String>(25601.34, Reese)));
series1.getData()。add(new XYChart.Data< Number,String>(20148.82,吉百利));
series1.getData()。add(new XYChart.Data< Number,String>(10000, Mars)));
series1.getData()。add(new XYChart.Data< Number,String>(35407.15, Snickers));
series1.getData()。add(new XYChart.Data< Number,String>(12000, Lindt)));

XYChart.Series< Number,String> series2 =新的XYChart.Series<>();
series2.setName( Vegetables);
series2.getData()。add(new XYChart.Data< Number,String>(57401.85,白菜));
series2.getData()。add(new XYChart.Data< Number,String>(41941.19,莴苣)));
series2.getData()。add(new XYChart.Data< Number,String>(45263.37,西红柿));
series2.getData()。add(new XYChart.Data< Number,String>(117320.16,茄子));
series2.getData()。add(new XYChart.Data< Number,String>(14845.27,甜菜根));

XYChart.Series< Number,String> series3 =新的XYChart.Series<>();
series3.setName( Drinks);
series3.getData()。add(new XYChart.Data< Number,String>(45000.65, Water)));
series3.getData()。add(new XYChart.Data< Number,String>(44835.76,可乐));
series3.getData()。add(new XYChart.Data< Number,String>(18722.18, Sprite)));
series3.getData()。add(new XYChart.Data< Number,String>(17557.31,山露水));
series3.getData()。add(new XYChart.Data< Number,String>(92633.68,啤酒)));

//需要这样做,因为存在CategoryAxis错误...手动设置类别
yAxis.setCategories(FXCollections.observableArrayList( Reese, Cadbury, Mars, Snickers, Lindt, Cabbage, Lettuce,
Tomato, Eggplant, Beetroot, Water, Coke, Sprite, Mountain Dew,啤酒));
groceryBarChart.getData()。addAll(series1,series2,series3);
}

现在,我有一个项目:





在我的 MenuBar 中加载 BarChart (因为它最初显示为空),它利用了我创建的方法 loadGraph 。我尝试了多种方法来使更新后的 BarChart 出现并没有取得重大成功。我最初的想法是在 loadGraph 方法中使用 setScene 将场景设置为 BarChart ,如



我看到了


I'm working on a JavaFX program that can display different grocery items as a BarChart. My code structure uses an initial loading class, GroceryGrapher.java, a controller, GroceryGrapherController.java, and an .fxml file, GroceryGrapher.fxml.

I'm relatively new to JavaFX and I've tried different approaches to creating and displaying my BarChart. For the most part I've resolved issues and can load the chart with its data and components, but my issue is that the chart won't display / update after the program has started. The BarChart is part of an AnchorPane (which all comes under a StackPane) in my FXML file, and I've tried to update it after initialization since it starts out blank but it remains blank.

The BarChart is part of the AnchorPane, which also has a MenuBar. My goal was to be able to update different parts of the BarChart via the MenuBar. When I load the program, the MenuBar and empty BarChart are loaded:

This is how I initialize my BarChart and its data:

public class GroceryGrapherController implements Initializable {

    @FXML
    private AnchorPane anchorPane = new AnchorPane();

    @FXML
    private NumberAxis xAxis = new NumberAxis();

    @FXML
    private CategoryAxis yAxis = new CategoryAxis();

    @FXML
    private BarChart<Number, String> groceryBarChart;

    @FXML
    //private Stage stage;
    Parent root;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        groceryBarChart = new BarChart<Number, String>(xAxis, yAxis);
        groceryBarChart.setTitle("Horizontal Grocery List");
        xAxis.setLabel("Number");
        xAxis.setTickLabelRotation(90);
        yAxis.setLabel("Grocery Type");

        // Populate BarChart
        XYChart.Series<Number, String> series1 = new XYChart.Series<>();
        series1.setName("Chocolates");       
        series1.getData().add(new XYChart.Data<Number, String>(25601.34, "Reese"));
        series1.getData().add(new XYChart.Data<Number, String>(20148.82, "Cadbury"));
        series1.getData().add(new XYChart.Data<Number, String>(10000, "Mars"));
        series1.getData().add(new XYChart.Data<Number, String>(35407.15, "Snickers"));
        series1.getData().add(new XYChart.Data<Number, String>(12000, "Lindt"));      

        XYChart.Series<Number, String> series2 = new XYChart.Series<>();
        series2.setName("Vegetables");
        series2.getData().add(new XYChart.Data<Number, String>(57401.85, "Cabbage"));
        series2.getData().add(new XYChart.Data<Number, String>(41941.19, "Lettuce"));
        series2.getData().add(new XYChart.Data<Number, String>(45263.37, "Tomato"));
        series2.getData().add(new XYChart.Data<Number, String>(117320.16, "Eggplant"));
        series2.getData().add(new XYChart.Data<Number, String>(14845.27, "Beetroot"));  

        XYChart.Series<Number, String> series3 = new XYChart.Series<>();
        series3.setName("Drinks");
        series3.getData().add(new XYChart.Data<Number, String>(45000.65, "Water"));
        series3.getData().add(new XYChart.Data<Number, String>(44835.76, "Coke"));
        series3.getData().add(new XYChart.Data<Number, String>(18722.18, "Sprite"));
        series3.getData().add(new XYChart.Data<Number, String>(17557.31, "Mountain Dew"));
        series3.getData().add(new XYChart.Data<Number, String>(92633.68, "Beer"));  

        // Need to do this because of a bug with CategoryAxis... manually set categories
        yAxis.setCategories(FXCollections.observableArrayList("Reese", "Cadbury", "Mars", "Snickers", "Lindt", "Cabbage", "Lettuce",
                "Tomato", "Eggplant", "Beetroot", "Water", "Coke", "Sprite", "Mountain Dew", "Beer"));
        groceryBarChart.getData().addAll(series1, series2, series3);
    }

Now, I have an item:

In my MenuBar to load the BarChart (since it initially appears empty), which utilizes the a method I created, loadGraph. I've tried different methods to get my updated BarChart to show up with no major success. My initial idea was to use setScene in the loadGraph method to set the scene to the BarChart, as shown in this example.

Stage stage = (Stage) anchorPane.getScene().getWindow();
stage.setScene(new Scene(groceryBarChart, 800, 600));

While that did work, it naturally replaced the scene to solely consist of the BarChart, removing my MenuBar and previous GUI:

I saw this question which had a similar issue (I think) but I wasn't really sure how to apply it to my program. I got this code:

try {
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("GroceryGrapher.fxml"));
    root = (Parent) fxmlLoader.load();
    GroceryGrapherController ggController = fxmlLoader.getController();
} catch (Exception e) {
      e.printStackTrace();
}

I copied over my initialization from the initialize method to a new method and tried calling it with ggController but it didn't change anything. I also tried creating a new method, updateData:

@FXML
private void updateData() {
    Platform.runLater(() -> {
        groceryBarChart.setTitle("Horizontal Grocery List");
    });
}

I tried calling it with ggController in the loadGraph method but my BarChart still didn't change.

I know I'm doing something wrong, but I'm not very familiar with the BarChart class and JavaFX in too much depth, so I figured I would come here to ask for help. I need to update the existing BarChart in my GUI so that my data can load without replacing the entire scene, in order for the MenuBar to remain visible in the scene able to perform functions. How would I go about altering the BarChart component part of my AnchorPane, updating the data as necessary, without completely replacing the scene as I did previously?

Thank you very much and sorry if this question is bad or my coding practices are wrong. All feedback would be much appreciated!

EDIT Just in case it might help, here is my FXML code for the BarChart.

<BarChart fx:id="groceryBarChart" layoutY="31.0" prefHeight="566.0" prefWidth="802.0">
          <xAxis>
              <CategoryAxis fx:id="yAxis" side="BOTTOM" />
          </xAxis>
          <yAxis>
              <NumberAxis side="LEFT" fx:id="xAxis" />
          </yAxis>
</BarChart>

解决方案

Most of the problems seem to be resolved, so I'll comment on this part only:

Ohh, I see. I increased the height and it seems to be thicker, thank you for your comment, that's very interesting behavior from the library. So I shouldn't use the approach I'm using?

Well, it depends. There is no other way to treat each data series as a separate list of categories in BarChart (at least, I'm not aware of any), so if you want to have groups of categories, each group with its own color, you'll have to use the same method as in your example.

The alternatives are to write your own implementation of bar chart layoutPlotChildren() method which will show only one bar per category (the only one if there are no two data series that contain the same category), or to find another chart library with those features. Of couse, you can always choose to show your data in different format (one data series for each month).

If you plan to use your original method, you'll have to adjust vertical position of bars (to center them again), and you'll have to fix bar height problem too:

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.chart.*;

import java.net.URL;
import java.util.ResourceBundle;


public class GroceryGrapherController implements Initializable {
    @FXML
    private NumberAxis xAxis;
    @FXML
    private CategoryAxis yAxis;
    @FXML
    private BarChart<Number, String> groceryBarChart;

    //the height of one bar in the chart (if it can fit category area); you can pick another value
    private final double barHeight = 10;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        groceryBarChart.setTitle("Horizontal Grocery List");
        //there's only one bar per category, so remove the gap (don't edit this, it's used in calculations)
        groceryBarChart.setBarGap(0);
        //gap between categories; you can pick another value
        groceryBarChart.setCategoryGap(5);
        //there're some bugs with the chart layout not updating when this is enabled, so disable it
        groceryBarChart.setAnimated(false);

        xAxis.setLabel("Number");
        xAxis.setTickLabelRotation(90);

        yAxis.setLabel("Grocery Type");
        //if category height changes (ex. when you resize the chart), bar positions and height need to be recalculated
        yAxis.categorySpacingProperty().addListener((observable, oldValue, newValue) ->
                recalculateBarPositionsAndHeight()
        );

        //your original data

        // Populate BarChart
        final XYChart.Series<Number, String> series1 = new XYChart.Series<>();
        series1.setName("Chocolates");
        series1.getData().addAll(
                new XYChart.Data<>(25601.34, "Reese"),
                new XYChart.Data<>(20148.82, "Cadbury"),
                new XYChart.Data<>(10000, "Mars"),
                new XYChart.Data<>(35407.15, "Snickers"),
                new XYChart.Data<>(12000, "Lindt")
        );

        final XYChart.Series<Number, String> series2 = new XYChart.Series<>();
        series2.setName("Vegetables");
        series2.getData().addAll(
                new XYChart.Data<>(57401.85, "Cabbage"),
                new XYChart.Data<>(41941.19, "Lettuce"),
                new XYChart.Data<>(45263.37, "Tomato"),
                new XYChart.Data<>(117320.16, "Eggplant"),
                new XYChart.Data<>(14845.27, "Beetroot")
        );

        final XYChart.Series<Number, String> series3 = new XYChart.Series<>();
        series3.setName("Drinks");
        series3.getData().addAll(
                new XYChart.Data<>(45000.65, "Water"),
                new XYChart.Data<>(44835.76, "Coke"),
                new XYChart.Data<>(18722.18, "Sprite"),
                new XYChart.Data<>(17557.31, "Mountain Dew"),
                new XYChart.Data<>(92633.68, "Beer")
        );

        groceryBarChart.getData().addAll(series1, series2, series3);
    }


    private void recalculateBarPositionsAndHeight() {
        final int seriesCount = groceryBarChart.getData().size();
        final double categoryHeight = yAxis.getCategorySpacing() - groceryBarChart.getCategoryGap();
        final double originalBarHeight = categoryHeight / seriesCount;
        final double barTranslateY = originalBarHeight * (seriesCount - 1) / 2;

        //find the (bar) node associated with each series data and adjust its size and position
        groceryBarChart.getData().forEach(numberStringSeries ->
                numberStringSeries.getData().forEach(numberStringData -> {
                    Node node = numberStringData.getNode();

                    //calculate the vertical scaling of the node, so that its height matches "barHeight"
                    //or maximum height available if it's too big
                    double scaleY = Math.min(barHeight, categoryHeight) / originalBarHeight;
                    node.setScaleY(scaleY);

                    //translate the node vertically to center it around the category label
                    node.setTranslateY(barTranslateY);
                })
        );
    }
}

I chose fixed bar height, but you don't need to (in which case you'll have to edit recalculateBarPositionsAndHeight method to use originalBarHeight).

这篇关于启动程序后重新加载并显示空白的JavaFX BarChart的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆