如何使用JavaFX解决在Scene Builder中绘制图表的问题

时间:2019-09-13 01:07:01

标签: java javafx fxml

我的程序有问题。 我想在提供数据(等式的系数)之后绘制图表。

我试图更改我的导入(它有助于一些变量) 我将java.awt *更改为javafx.scene ...(少量导入)。

FXML文件:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="grahps.Controller">
<children>
        <TextField fx:id="factorA" layoutX="24.0" layoutY="598.0" prefHeight="33.0" prefWidth="106.0" text="a=" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
        <TextField fx:id="factorB" layoutX="24.0" layoutY="630.0" prefHeight="33.0" prefWidth="106.0" text="b=" AnchorPane.bottomAnchor="37.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
        <TextField fx:id="factorC" layoutX="24.0" layoutY="674.0" prefHeight="33.0" prefWidth="106.0" text="c=" AnchorPane.bottomAnchor="1.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
        <TextField layoutX="158.0" layoutY="592.0" prefHeight="47.0" prefWidth="120.0" text="xMin=" AnchorPane.bottomAnchor="61.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" fx:id="xMin" />
        <TextField layoutX="158.0" layoutY="650.0" prefHeight="47.0" prefWidth="120.0" text="xMax=" AnchorPane.bottomAnchor="3.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" fx:id="xMax" />
        <Label fx:id="label" layoutX="468.0" layoutY="629.0" prefHeight="61.0" prefWidth="276.0" text="f(x)=" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="468.0" AnchorPane.rightAnchor="56.0">
            <font>
                <Font size="18.0" />
            </font>
        </Label>
        <LineChart fx:id="drawChart" prefHeight="598.0" prefWidth="800.0" title="Chart">
            <xAxis>
                <CategoryAxis side="BOTTOM" />
            </xAxis>
            <yAxis>
                <NumberAxis side="LEFT" />
            </yAxis>
        </LineChart>
        <Button fx:id="button" layoutX="317.0" layoutY="612.0" mnemonicParsing="false" prefHeight="61.0" prefWidth="98.0" text="Rysuj wykres" />
    </children>
</AnchorPane>

生成的带有LineChart(fx:id =“ drawChart”)的零件进行通信:“未解决的fx:id参考

我的主班:

package grahps;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;


public class Main extends Application {

    private Controller controller;//

    @Override
    public void start(Stage stage) throws Exception {
        stage.show();


        System.out.println(getClass().getResource("/fxml/sample.fxml").getPath());
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/sample.fxml"));
        loader.setController(controller);
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/sample.fxml"));
        Scene scene = new Scene(root, 800, 800);
        controller.drawChart(stage);
        stage.setScene(scene);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

最后是我的控制器:

package grahps;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.control.TextField;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;

public class Controller {
    @FXML
    TextField factorA;
    @FXML
    TextField factorB;
    @FXML
    TextField factorC;
    @FXML
    TextField xMin;
    @FXML
    TextField xMax;
    @FXML
    Label label;
    @FXML
    Button button;
    @FXML
    XYChart.Series<Number, Number> chart;

    //Parser Text Field -> double
    double xMax1 = Double.parseDouble(xMax.getText());
    double xMin1 = Double.parseDouble(xMin.getText());
    double a = Double.parseDouble(factorA.getText());
    double b = Double.parseDouble(factorB.getText());
    double c = Double.parseDouble(factorC.getText());

    @FXML
    public void drawChart(Stage stage) {
        XYChart.Series<Number, Number> series = chart;
        series.setName("Chart");

        final NumberAxis xAxis = new NumberAxis(xMin1, xMax1, 1);
        final NumberAxis yAxis = new NumberAxis();
        yAxis.setTickUnit(1);
        xAxis.setLabel("X Label");
        yAxis.setLabel("Y Label");
        final javafx.scene.chart.LineChart<Number, Number> lineChart = new javafx.scene.chart.LineChart<Number, Number>(xAxis, yAxis);
        double y;

        String pattern;
        if (a == 0 && c == 0) {
            pattern = "f(x)=" + factorB;
            label.setText(pattern);
        } else if (c == 0) {
            pattern = "f(x)=" + factorA + "x+" + factorB;
            label.setText(pattern);
            for (double i = xMin1; i <= xMax1; i++) {
                y = a * i + b;
                series.getData().add(new XYChart.Data(i, y));
            }
        } else {
            pattern = "f(x)=" + factorC + "x^2" + factorA + "x+" + factorB;
            label.setText(pattern);
            for (double i = xMin1; i < xMax1; i++) {
                y = a * i * i + b * i + c;
                series.getData().add(new XYChart.Data(i, y));
            }
        }

        lineChart.getData().add(series);
        Scene scene = new Scene(lineChart, 800, 800);
        stage.setScene(scene);
        stage.setResizable(false);
        stage.show();
    }
}

请注意一件事:这是我的第一个JavaFX项目。 我只是想填写方程式的系数,并在单击按钮后生成方程式+绘制图表。

我很乐意寻求帮助。

当我删除上述方法中的@FXML批注并编译代码时,出现了这些错误:

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$154(LauncherImpl.java:182)
    at java.lang.Thread.run(Thread.java:748)
Caused by: javafx.fxml.LoadException: 
/C:/Users/Damian/IdeaProjects/Graphs/target/classes/fxml/sample.fxml:14

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
    at grahps.Main.start(Main.java:22)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    ... 1 more
Caused by: java.lang.NullPointerException
    at grahps.Controller.<init>(Controller.java:31)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.lang.Class.newInstance(Class.java:442)
    at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
    at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
    at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
    ... 17 more
Exception running application grahps.Main

Process finished with exit code 1

我也将onAction =“ drawChart”更改为onAction =“ chart”,现在可以看到突出显示的交流信息:“无法将javafx.scene.control.LineChart设置为图表”

1 个答案:

答案 0 :(得分:1)

代码中有几个错误,例如:

  • 控制器中的//Parser Text Field -> double块执行得太早=> NullPointerException

  • controller.drawChart(stage)方法start中调用controller时等于null => NullPointerException

  • 在控制器的drawChart方法中,series等于null,因为chart等于null => NullPointerException

  • 控制器中缺少对图表的引用。评论中已经提到了这一点。

  • CategoryAxis被用作x轴的类型,尽管x数据是数值。

在修复这些错误之前,应改进体系结构(这将自动修复某些错误):

  • Controller级:由于必须先初始化图表,然后每次单击按钮才能更新图表,因此控制器中的以下更改将很有用:

    1. 实施一种initialize方法,可以在其中进行必要的初始化。

    2. 实施一种updateChart方法,该方法在单击按钮时会被调用,并会更新图表。

    3. 定义对LineChart的引用。

    4. 定义对两个轴的引用。

    因此,Controller类的外观如下:

    public class Controller {
        @FXML
        TextField factorA;
        @FXML
        TextField factorB;
        @FXML
        TextField factorC;
        @FXML
        TextField xMin;
        @FXML
        TextField xMax;
        @FXML
        Label label;
        @FXML
        Button button;
        @FXML
        LineChart<Number, Number> chart; 
        @FXML
        NumberAxis xAxis;
        @FXML
        NumberAxis yAxis;
        @FXML
        public void updateChart() {/*ToDo*/}
        public void initialize(){/*ToDo*/}
    } 
    
  • Main类:在start方法中,仅需要加载FXML:

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/sample.fxml"));
        Scene scene = new Scene(root, 800, 800);
        stage.setScene(scene);
        stage.show();
    }
    
  • FXML:应该进行以下更改:

    1. 将LineChart的ID更改为fx:id="chart"
    2. 将LineChart的x轴类型更改为NumberAxis
    3. onAction="#updateChart"添加到按钮。单击按钮时,将调用updateChart方法。
    4. 定义两个轴(fx:id="xAxis"fx:id="yAxis")的ID。
    5. 删除所有文本字段的所有使用非数字字符(例如text="a=")的初始化,否则解析存在问题(使用标签或水印,例如promptText="a"更有意义)。 / li>

    然后,FXML变为:

    <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="grahps.Controller">
        <children>
            <TextField fx:id="factorA" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="a" />
            <TextField fx:id="factorB" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="37.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="b" />
            <TextField fx:id="factorC" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="1.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="c" />
            <TextField fx:id="xMin" prefHeight="47.0" prefWidth="120.0" AnchorPane.bottomAnchor="61.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" promptText="xMin" />
            <TextField fx:id="xMax" prefHeight="47.0" prefWidth="120.0" AnchorPane.bottomAnchor="3.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" promptText="xMax" />
            <Label fx:id="label" prefHeight="61.0" prefWidth="276.0" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="468.0" AnchorPane.rightAnchor="56.0" text="f(x)=" >
                <font>
                    <Font size="18.0" />
                </font>
            </Label>
            <LineChart fx:id="chart" prefHeight="598.0" prefWidth="800.0" title="Chart">
                <xAxis>
                    <NumberAxis fx:id="xAxis" side="BOTTOM" />
                </xAxis>
                <yAxis>
                    <NumberAxis fx:id="yAxis" side="LEFT" />
                </yAxis>
            </LineChart>
            <Button fx:id="button" prefHeight="61.0" prefWidth="98.0" layoutX="317.0" layoutY="612.0" mnemonicParsing="false" text="Rysuj wykres" onAction="#updateChart" />
        </children>
    </AnchorPane>
    

进行这些更改后,启动应用程序时将显示一个空图表(因为尚未实现初始化)。单击该按钮无效(因为尚未执行任何更新)。

  • 初始化:

    public void initialize(){       
        initChartProperties();
        initInputControls();        
        XYChart.Series<Number, Number> series = getSeries();        
        chart.getData().add(series);
    }
    

    getSeries方法本质上包含drawChart方法的逻辑:

    private XYChart.Series<Number, Number> getSeries() {
    
        double xMax1 = Double.parseDouble(xMax.getText());
        double xMin1 = Double.parseDouble(xMin.getText());
        double a = Double.parseDouble(factorA.getText());
        double b = Double.parseDouble(factorB.getText());
        double c = Double.parseDouble(factorC.getText());
    
        XYChart.Series<Number,Number> series = new XYChart.Series<Number, Number>();
        series.setName("Chart");
    
        String pattern;
        if (a == 0 && c == 0) {
            pattern = "f(x)=" + factorB.getText();
            label.setText(pattern);
        } else if (c == 0) {
            pattern = "f(x)=" + factorA.getText() + "x+" + factorB.getText();
            label.setText(pattern);
            for (double i = xMin1; i <= xMax1; i++) {
                double y = a * i + b;
                series.getData().add(new Data<Number, Number>(i, y));
            }
        } else {
            pattern = "f(x)=" + factorA.getText() + "x^2+" + factorB.getText() + "x+" + factorC.getText();
            label.setText(pattern);
            for (double i = xMin1; i < xMax1; i++) {
                double y = a * i * i + b * i + c;
                series.getData().add(new Data<Number, Number>(i, y));
            }
        }
    
        return series;
    }
    

    initInputControls方法初始化输入控件,例如:

    private void initInputControls() {
        xMax.setText("100.0");
        xMin.setText("10.0");
        factorA.setText("1.0");
        factorB.setText("2.0");
        factorC.setText("3.0");
    }
    

    initChartProperties方法初始化图表:

    private void initChartProperties() {
        chart.setAnimated(true);
        xAxis.setLabel("X Label");
        yAxis.setLabel("Y Label");      
    }
    

    如果要在启动时显示空白图表,只需删除initialize方法中的最后三行。

  • 更新:更新只是删除旧系列并将新系列添加到图表中:

    @FXML
    public void updateChart() {     
        XYChart.Series<Number, Number> series = getSeries();
        chart.getData().clear();
        chart.getData().add(series);
    }
    

这些更改之后,应用程序将按预期运行。左图是启动后的应用程序,右图是更新输入值后的图。

enter image description here

有些事情仍然可以改进,例如如果更改窗口大小,应用程序将无法正确缩放。此外,缺少输入字段的验证。可以在initChartProperties方法中禁用图表动画。

更新: 如果没有符号要显示,请添加initChartProperties方法:

chart.setCreateSymbols(false);

结果是:

enter image description here