Перезагрузите и отобразите пустой JavaFX BarChart после запуска программы - PullRequest
0 голосов
/ 07 февраля 2019

Я работаю над программой JavaFX, которая может отображать различные продукты в виде BarChart.Моя структура кода использует класс начальной загрузки GroceryGrapher.java , контроллер GroceryGrapherController.java и файл .fxml GroceryGrapher.fxml .

Я относительно новичок в JavaFX и пробовал разные подходы к созданию и отображению моего BarChart.По большей части я решил проблемы и могу загрузить диаграмму с ее данными и компонентами, но моя проблема в том, что диаграмма не будет отображаться / обновляться после запуска программы.BarChart является частью AnchorPane (все это входит в StackPane) в моем файле FXML, и я попытался обновить его после инициализации, поскольку он начинается пустым, но остается пустым.

BarChart является частью AnchorPane, который также имеет MenuBar.Моя цель состояла в том, чтобы иметь возможность обновлять различные части BarChart через MenuBar.Когда я загружаю программу, загружаются MenuBar и пустые BarChart:

enter image description here

Вот как я инициализирую свой BarChart и егоданные:

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);
    }

Теперь у меня есть предмет:

enter image description here

В моем MenuBar для загрузки BarChart(поскольку он изначально выглядит пустым), который использует метод, который я создал, loadGraph.Я пробовал разные методы, чтобы мой обновленный BarChart появлялся без особого успеха.Моя первоначальная идея заключалась в том, чтобы использовать setScene в методе loadGraph, чтобы установить сцену на BarChart, как показано в в этом примере .

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

Хотя это сработалоестественно, он заменил сцену, состоящую исключительно из BarChart, удалив мой MenuBar и предыдущий графический интерфейс:

enter image description here

Я видел этот вопрос , у которого была похожая проблема (я думаю), но я не был уверен, как применить его к моей программе.Я получил этот код:

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

Я скопировал инициализацию из метода initialize в новый метод и попытался вызвать его с помощью ggController, но это ничего не изменило.Я также попытался создать новый метод, updateData:

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

Я попытался вызвать его с помощью ggController в методе loadGraph, но мой BarChart все еще не изменился.

Я знаю, что делаю что-то не так, но я не очень хорошо знаком с классом BarChart и JavaFX, поэтому решил, что приду сюда, чтобы попросить о помощи.Мне нужно обновить существующий BarChart в моем графическом интерфейсе, чтобы мои данные могли загружаться без замены всей сцены, чтобы MenuBar оставался видимым в сцене, способной выполнять функции.Как бы я изменил BarChart компонентную часть моего AnchorPane, обновив данные по мере необходимости, без полной замены сцены, как я делал ранее?

Большое спасибо и извините, если этот вопросплохо или мои методы кодирования неверны.Буду признателен за все отзывы!

РЕДАКТИРОВАТЬ На всякий случай это может помочь, вот мой код FXML для 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>

1 Ответ

0 голосов
/ 07 февраля 2019

Большинство проблем, похоже, решены, поэтому я прокомментирую только эту часть:

О, я вижу.Я увеличил высоту, и она кажется более толстой, спасибо за ваш комментарий, это очень интересное поведение из библиотеки.Так что я не должен использовать подход, который я использую?

Ну, это зависит.Нет другого способа рассматривать каждую серию данных как отдельный список категорий в BarChart (по крайней мере, я не знаю ни о каких), поэтому, если вы хотите иметь группы категорий, каждая группа имеет свой собственный цвет, вы 'Вам придется использовать тот же метод, что и в вашем примере.

В качестве альтернативы можно написать собственную реализацию метода гистограммы layoutPlotChildren(), который будет показывать только один столбец на категорию (единственный, если нет двухряды данных, которые содержат ту же категорию), или найти другую библиотеку диаграмм с этими функциями.Конечно, вы всегда можете выбрать отображение данных в другом формате (один ряд данных на каждый месяц).

Если вы планируете использовать свой оригинальный метод, вам придется настроить вертикальное положение столбцов (чтобыотцентрируйте их снова), и вам также придется решить проблему с высотой стержня:

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);
                })
        );
    }
}

Я выбрал фиксированную высоту стержня, но вам не нужно (в этом случае вам придется редактировать recalculateBarPositionsAndHeight метод для использования originalBarHeight).

screenshot

...