启动程序后重新加载并显示空白的JavaFX条形图
我正在开发一个JavaFX程序,它可以将不同的杂货项目显示为条形图。我的代码结构使用一个初始加载类GroceryGrapher.java、一个控制器GroceryGrapherController.java和一个.fxml文件GroceryGrapher.fxml 我对JavaFX比较陌生,我尝试了不同的方法来创建和显示我的条形图。在大多数情况下,我已经解决了问题,可以加载图表及其数据和组件,但我的问题是,在程序启动后,图表不会显示/更新。条形图是AnchorPane的一部分,它在我的FXML文件中位于StackPane下,我尝试在初始化后更新它,因为它开始时为空,但它仍然为空 柱状图是锚烷的一部分,锚烷也有一个菜单栏。我的目标是能够通过菜单栏更新条形图的不同部分。加载程序时,会加载菜单栏和空条形图: 以下是我初始化条形图及其数据的方式:启动程序后重新加载并显示空白的JavaFX条形图,javafx,controller,bar-chart,fxml,scene,Javafx,Controller,Bar Chart,Fxml,Scene,我正在开发一个JavaFX程序,它可以将不同的杂货项目显示为条形图。我的代码结构使用一个初始加载类GroceryGrapher.java、一个控制器GroceryGrapherController.java和一个.fxml文件GroceryGrapher.fxml 我对JavaFX比较陌生,我尝试了不同的方法来创建和显示我的条形图。在大多数情况下,我已经解决了问题,可以加载图表及其数据和组件,但我的问题是,在程序启动后,图表不会显示/更新。条形图是AnchorPane的一部分,它在我的FXML文
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);
}
虽然这确实有效,但它很自然地取代了仅由条形图组成的场景,删除了我的菜单栏和以前的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:
我尝试在loadGraph方法中使用ggController调用它,但我的条形图仍然没有改变
我知道我做错了什么,但我对BarChart类和JavaFX不太熟悉,所以我想我会来这里寻求帮助。我需要更新GUI中的现有条形图,以便在不替换整个场景的情况下加载数据,以便菜单栏在场景中保持可见,并能够执行功能。我将如何改变我的主播的条形图组成部分,根据需要更新数据,而不会像以前那样完全替换场景
非常感谢,如果这个问题不好或者我的编码实践是错误的,那么很抱歉。所有反馈将不胜感激
编辑
为了以防万一,这里是我的条形图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>
大多数问题似乎都已解决,因此我仅对这一部分进行评论: 哦,我明白了。我增加了高度,看起来更厚了,谢谢你的评论,这是图书馆里非常有趣的行为。所以我不应该使用我正在使用的方法 那要看情况了。在条形图中,没有其他方法可以将每个数据系列视为单独的类别列表,至少,我不知道有任何方法,因此,如果您希望有类别组,每个组都有自己的颜色,您必须使用与示例中相同的方法 替代方法是编写自己的条形图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
当图表是场景的一部分并且更改其数据时,应更新图表。是否确实调用了updateDate方法?正如我所看到的,它有一个@FXML注释,它提示在特定事件发生时调用它。可能事件未被触发,并且您的方法从未被调用?一些简单的System.out.println调试可能会有所帮助。嘿,谢谢你的评论,是的,我只是确保它被调用了。我尝试删除@FXML注释,我还尝试删除Platform.runLater功能,并且每次都调用System.out.println行,但条形图没有更新。我试着让他们回来,同样的结果也发生了。下面是一些图片,我按了两次加载按钮:,,好吧,那么看起来你可能以某种方式引用了错误的条形图对象。这可能不是被添加到场景中的那个。实际上,它被注入控制器并自动添加到场景中。你有没有把这张图表放在别的地方?因为那样你就用错了东西。这也解释了为什么只有当您将旧场景完全替换为只包含新实例条形图的新场景时,它才会起作用。为什么要初始化带有@FXML注释的字段?它们是从fxml文件注入的,不能手动初始化。你正在覆盖它们
使用您自己的对象,然后修改那些没有显示在任何位置的新对象,因为它们没有添加到场景图中,所以您的更改不可见。首先从@FXML字段中删除所有构造函数调用。当然,正如我所说的。谢谢@Guest21,它实际上在片段中:groceryBarChart=newbarchartxaxis,yAxis;这就是罪魁祸首。不要初始化用@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>
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);
})
);
}
}