Jaskier7
Jaskier7

Reputation: 23

How to solve problem with drawing Chart in Scene Builder with JavaFX

I have problem with my program. I want to draw chart after providing data (coefficients of the equation).

I tried to change my import (it helped with some variables) I changed java.awt* for javafx.scene ... (few imports).

FXML file:

<?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>

The part with LineChart (fx:id="drawChart") generated communicate: "Unresolved fx:id reference

My main class:

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

And finally my controller:

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

Please notice one thing: this is my first JavaFX project. Simply I want to fill in coefficients of the equation and generate equation pattern + draw chart after clicking button.

I'll be greaftul for help.

When I deleted @FXML annotation above method and compiled code I got those errors:

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

Also I changed onAction="drawChart" for onAction="chart" and now I can see highlighted communicate: "cannot set javafx.scene.control.LineChart to chart"

Upvotes: 1

Views: 1021

Answers (1)

Topaco
Topaco

Reputation: 49201

There are several bugs in the code, e.g.:

  • The //Parser Text Field -> double-block in the controller is executed too early => NullPointerException.

  • When calling controller.drawChart(stage) in the start-method controller equals null => NullPointerException.

  • In the drawChart-method in the controller series equals null, because chart equals null => NullPointerException.

  • A reference to the chart is missing in the controller. This has already been noted in the comments.

  • CategoryAxis is used as the type of the x-axis, although the x-data are numerical values.

Before fixing these bugs, the architecture should be improved (this will automatically fix some of the bugs):

  • Controller-class: Since the chart has to be initialized and then updated with every click on the button, the following changes in the controller would be useful:

    1. Implement an initialize-method, in which the necessary initializations can be made.

    2. Implement an updateChart-method, which is called when the button is clicked and which updates the chart.

    3. Define a reference to the LineChart.

    4. Define references to both axes.

    Thus, the Controller-class looks as follows:

    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-class: In the start-method only the FXML needs to be loaded:

    @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: The following changes should be made:

    1. Change the ID of the LineChart to fx:id="chart"
    2. Change the type of the x-axis of the LineChart to NumberAxis
    3. Add onAction="#updateChart" to the button. This calls the updateChart-method when the button is clicked.
    4. Define an ID for both axes (fx:id="xAxis" and fx:id="yAxis").
    5. Remove all initializations with non-numeric characters (e.g. text="a=") for all text fields, otherwise there are problems with parsing (it makes more sense to use labels or watermarks, e.g. promptText="a").

    Then, the FXML becomes:

    <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>
    

With these changes, an empty chart is displayed when the application is started (since no initialization has yet been implemented). Clicking on the button has no effect (since no updating has yet been implemented).

  • Initialization:

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

    The getSeries-method essentially contains the logic of the drawChart-method:

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

    The initInputControls-method initializes the input controls, e.g.:

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

    The initChartProperties-method initializes the chart:

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

    If you want to display an empty chart at startup, simply remove the last three lines in the initialize-method.

  • Updating: Updating simply deletes the old series and adds the new series to the chart:

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

After these changes, the application behaves as expected. The figure on the left shows the application after startup, the figure on the right after updating the input values.

enter image description here

Some things can still be improved, e.g. the application does not scale properly if the window size is changed. In addition, the validation of the input fields is missing. Chart animation can be disabled in the initChartProperties-method.

UPDATE: If no symbols are to be displayed, add in the initChartProperties-method:

chart.setCreateSymbols(false);

The result is:

enter image description here

Upvotes: 1

Related Questions