JavaFX LineChart - ClassCastException because of the Axis' Type


Question

How do I specify the axis type for the chart from the FXML file? It seems like the default types are <String, Integer>. If I declare my injectable field as LineChart<Number, Number> lineChart, and the I create a data series with (Number, Number), the program throws a ClassCastException.

It is mandatory for a FXML File to be used. The worst case scenario would be that I created my chart manually. My best guess is that this is a bug.


import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.AnchorPane;

/**
 * 
 * @author ggrec
 *
 */
public class TestChart implements Initializable
{

    // ====================== 2. Instance Fields =============================

    @FXML
    private LineChart<Number, Number> testChart;

    private AnchorPane anchorPane;


    // ==================== 4. Constructors ====================

    public TestChart()
    {
        final FXMLLoader fxmlLoader = new FXMLLoader( TestChart.class.getResource("testChart.fxml") );
        fxmlLoader.setController(this);

        try
        {
            anchorPane = (AnchorPane) fxmlLoader.load();
        }
        catch (final IOException e)
        {
            e.printStackTrace();
        }
    }


    // ==================== 5. Creators ====================

    @Override
    public void initialize(final URL arg0, final ResourceBundle arg1)
    {
        //      testChart.getXAxis().setAutoRanging(true);
        //      testChart.getYAxis().setAutoRanging(true);

        testChart.getData().add(getDummyData());
    }


    // ==================== 7. Getters & Setters ====================

    public AnchorPane getAnchorPane()
    {
        return anchorPane;
    }


    // ==================== 13. Utility Methods ====================

    private XYChart.Series getDummyData()
    {
        final XYChart.Series series = new XYChart.Series();
        series.setName("My portfolio");

        series.getData().add(new XYChart.Data<Number, Number>(1, 23)); // Works for ("1", 23)
        //      series.getData().add(new XYChart.Data("2", 14));
        //      series.getData().add(new XYChart.Data("3", 15));
        //      series.getData().add(new XYChart.Data("4", 24));
        //      series.getData().add(new XYChart.Data("5", 34));
        //      series.getData().add(new XYChart.Data("6", 36));
        //      series.getData().add(new XYChart.Data("7", 22));
        //      series.getData().add(new XYChart.Data("8", 45));
        //      series.getData().add(new XYChart.Data("9", 43));
        //      series.getData().add(new XYChart.Data("10", 17));
        //      series.getData().add(new XYChart.Data("11", 29));
        //      series.getData().add(new XYChart.Data("12", 25));

        return series;
    }
}

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at javafx.scene.chart.CategoryAxis.invalidateRange(CategoryAxis.java:399)
at javafx.scene.chart.XYChart.updateAxisRange(XYChart.java:603)
at javafx.scene.chart.XYChart.layoutChartChildren(XYChart.java:620)
at javafx.scene.chart.Chart$1.layoutChildren(Chart.java:84)
at javafx.scene.Parent.layout(Parent.java:1018)
1
8
1/22/2014 1:18:33 PM

Accepted Answer

This works fine for me. I tested with the following FXML:

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

<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>

<AnchorPane xmlns:fx="http://javafx.com/fxml">
    <LineChart fx:id="testChart">
    <xAxis><NumberAxis /></xAxis>
    <yAxis><NumberAxis /></yAxis>
    </LineChart>
</AnchorPane>

and test application:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            TestChart chart = new TestChart();
            Scene scene = new Scene(chart.getAnchorPane(), 600, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

It's a little difficult to tell why you are getting a ClassCastException without seeing your FXML file (and in particular the axes you are using). However, from the stack trace, it appears you are using a CategoryAxis, which is not type-compatible with a LineChart<Number, Number>.

In general, you can declare the two data types (one for x and one for y) to be any types you want. Since you've declared

@FXML
private LineChart<Number, Number> testChart ;

both the type of the x variable and the type of the y variable are Number.

Referring to the Javadocs for LineChart, a LineChart<X,Y> requires an Axis<X> for the x-axis and an Axis<Y> for the y-axis. So you need an Axis<Number> for each axis. A CategoryAxis is an Axis<String> (again, refer to the Javadocs), so it cannot be used as an axis for your line chart: the data type for the axis is incompatible with the data type for the chart.

If you tried the following in Java:

LineChart<Number, Number> testChart ;
testChart = new LineChart<>(new CategoryAxis(), new NumberAxis());

you would get a compile error. Since you're (presumably) initializing the axes in FXML, and FXML has no type checking, you get the ClassCastException at runtime instead.

One thing that would help your code is to use a properly typed Series:

private XYChart.Series<Number, Number> getDummyData()
{
    final XYChart.Series<Number, Number> series = new XYChart.Series<>();
    series.setName("My portfolio");
    // ...
}

Now the compiler will check that the type of the Series matches the type of the chart, and the type of the Data matches the type of the Series. (So series.getData().add(new XYChart.Data("1", 23)); would give a compile error, rather than a runtime error.) Since you are creating the axes in FXML, you still have no type checking on those but I think the cause of the error would (perhaps) become clearer.

The fix is to use a NumberAxis, as in my example above, instead of your CategoryAxis. If you really want to use a CategoryAxis for the x-axis, the x values have to be Strings, and so you need to declare the LineChart as a LineChart<String, Number>. Similarly you would make the Series a Series<String, Number>.

11
8/14/2014 7:04:19 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon