启动程序后重新加载并显示空白 JavaFX BarChart
Reload and display blank JavaFX BarChart after launching the program
我正在开发一个 JavaFX 程序,它可以将不同的杂货商品显示为条形图。我的代码结构使用初始加载 class、GroceryGrapher.java、控制器、GroceryGrapherController.java 和.fxml 文件,GroceryGrapher.fxml。
我对 JavaFX 比较陌生,我尝试过不同的方法来创建和显示我的 BarChart
。在大多数情况下,我已经解决了问题并且可以加载图表及其数据和组件,但我的问题是图表在程序启动后不会显示/更新。 BarChart
是我的 FXML 文件中 AnchorPane
的一部分(全部都在 StackPane
下),我试图在初始化后更新它,因为它开始时是空白的,但它仍然存在空白。
BarChart
是 AnchorPane
的一部分,后者还有一个 MenuBar
。我的目标是能够通过 MenuBar
更新 BarChart
的不同部分。当我加载程序时,加载 MenuBar
和空的 BarChart
:
这就是我初始化 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);
}
现在,我有一个项目:
在我的 MenuBar
中加载 BarChart
(因为它最初显示为空),它使用我创建的方法 loadGraph
。我尝试了不同的方法来让我更新的 BarChart
出现,但没有取得重大成功。我最初的想法是在loadGraph
方法中使用setScene
将场景设置为BarChart
,如图this example.
Stage stage = (Stage) anchorPane.getScene().getWindow();
stage.setScene(new Scene(groceryBarChart, 800, 600));
虽然这确实有效,但它自然地将场景替换为仅由 BarChart
组成,删除了我的 MenuBar
和以前的 GUI:
我看到 也有类似的问题(我认为),但我不确定如何将其应用到我的程序中。我得到了这个代码:
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");
});
}
我尝试在 loadGraph
方法中使用 ggController
调用它,但我的 BarChart
仍然没有改变。
我知道我做错了什么,但是我对BarChart
class 和JavaFX 不是太熟悉,所以我想我会来这里求教帮助。我需要更新我的 GUI 中现有的 BarChart
,这样我的数据就可以在不替换整个场景的情况下加载,以便 MenuBar
在能够执行功能的场景中保持可见。我将如何改变 AnchorPane
的 BarChart
组件部分,根据需要更新数据,而不是像以前那样完全替换场景?
非常感谢,如果这个问题不好或者我的编码习惯有误,我深表歉意。非常感谢所有反馈!
编辑
为了以防万一,这里是我的条形图 FXML 代码。
<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>
大部分问题似乎都解决了,所以我只评论这部分:
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?
好吧,这取决于。没有其他方法可以将每个数据系列视为 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
)。
我正在开发一个 JavaFX 程序,它可以将不同的杂货商品显示为条形图。我的代码结构使用初始加载 class、GroceryGrapher.java、控制器、GroceryGrapherController.java 和.fxml 文件,GroceryGrapher.fxml。
我对 JavaFX 比较陌生,我尝试过不同的方法来创建和显示我的 BarChart
。在大多数情况下,我已经解决了问题并且可以加载图表及其数据和组件,但我的问题是图表在程序启动后不会显示/更新。 BarChart
是我的 FXML 文件中 AnchorPane
的一部分(全部都在 StackPane
下),我试图在初始化后更新它,因为它开始时是空白的,但它仍然存在空白。
BarChart
是 AnchorPane
的一部分,后者还有一个 MenuBar
。我的目标是能够通过 MenuBar
更新 BarChart
的不同部分。当我加载程序时,加载 MenuBar
和空的 BarChart
:
这就是我初始化 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);
}
现在,我有一个项目:
在我的 MenuBar
中加载 BarChart
(因为它最初显示为空),它使用我创建的方法 loadGraph
。我尝试了不同的方法来让我更新的 BarChart
出现,但没有取得重大成功。我最初的想法是在loadGraph
方法中使用setScene
将场景设置为BarChart
,如图this example.
Stage stage = (Stage) anchorPane.getScene().getWindow();
stage.setScene(new Scene(groceryBarChart, 800, 600));
虽然这确实有效,但它自然地将场景替换为仅由 BarChart
组成,删除了我的 MenuBar
和以前的 GUI:
我看到
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");
});
}
我尝试在 loadGraph
方法中使用 ggController
调用它,但我的 BarChart
仍然没有改变。
我知道我做错了什么,但是我对BarChart
class 和JavaFX 不是太熟悉,所以我想我会来这里求教帮助。我需要更新我的 GUI 中现有的 BarChart
,这样我的数据就可以在不替换整个场景的情况下加载,以便 MenuBar
在能够执行功能的场景中保持可见。我将如何改变 AnchorPane
的 BarChart
组件部分,根据需要更新数据,而不是像以前那样完全替换场景?
非常感谢,如果这个问题不好或者我的编码习惯有误,我深表歉意。非常感谢所有反馈!
编辑 为了以防万一,这里是我的条形图 FXML 代码。
<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>
大部分问题似乎都解决了,所以我只评论这部分:
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?
好吧,这取决于。没有其他方法可以将每个数据系列视为 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
)。