JavaFX comes with 8 charts out of the box. This resource focusses on the charts JavaFX provides by default, rather than how to extend them. I’ll look at how to create them, update them, format them.

A JavaFX Chart is a node designed to display data graphically. Charts display data as symbols such as bars (the BarChart), lines (LineChart), slices (PieChart), points (ScatterChart) or a combination of these.

All charts work by using extra nodes to represent data points and lines. These are laid out in Pane referred to as the plot area, rather than using a Canvas.

How to use this resource

I’ll cover three basic areas in creating and maintaining graphs in JavaFX.

  • Types of chart, and how to create them
  • Updating your chart by adding and remove values or series.
  • Formatting axis values for currency, abbreviations and more

Types of Chart

JavaFX provides 8 default charts to display data. 7 of these have axes and are designed to show data in two dimensions along those axes.

JavaFX has 8 charts suitable for displaying a range of data graphically

Charts with Axes: 7 of the 8 JavaFX charts display data on a two-axis chart. These all extend the XYChart object, a sub-class of Chart, which is responsible for displaying the axes. Each of the chart instances is responsible for placing data points, connecting lines, bars and boxes.

  • LineChart
  • BarChart
  • StackedBarChart
  • StackedAreaChart
  • BubbleChart
  • ScatterChart
  • AreaChart

Charts without axes: the PieChart is the only chart that comes with JavaFX that doesn’t use axes. PieChart directly extends the Chart class and is responsible for all of its own plotting requirements.

  • PieChart

Most operations you’ll do on a chart will depend on whether that chart has axes. So, in each section, I’ll deal with pie charts first, mostly because they’re simpler, and then how to do the same operations on charts with axes.

How to create a Chart

JavaFX charts are a node, like any other UI element, which means you can add it to a layout just like anything else. If you haven’t checked out my article on layouts, I would definitely recommend it. I tried to create a resource that explains every layout and how it sizes and positions it’s children. And, honestly, the time I’ve saved just referring back to it myself while I’m designing programs made it time well-spent.

If you’re creating a pie chart, you can create either an empty PieChart, or optionally initialize it with data.

However, if you’re creating a chart with axes, you’ll need to create your chart with its axes. You can then optionally also create it with data. An XYChart cannot be created without axes.

Creating a Pie Chart.

OK this couldn’t be simpler.

PieChart pieChart = new PieChart();

You can also create them with data, but I’ll cover data a little further down, and then you can loop back and put the data in the constructor if you want. Here’s the method prototype for creating a PieChart with data, though:

PieChart pieChart = new PieChart(ObservableList<PieChart.Data> data)

Creating charts with axes

Any chart with axes is going to require a little more effort, because you will need to specify those which axes you’re plotting against in the constructor.

The absolute simplest way to do this is by creating unparameterized axes as you create your chart

AreaChart<Number, Number> areaChart = new AreaChart<>(
    new NumberAxis(), 
    new NumberAxis()
   );

To be honest, I wouldn’t I’d always create my axes separately, parameterize them, and pass them in. But for a quick-start, that will work on every type of axis-based chart.

On top of that, you can also provide data and (where appropriate) a category gap.

ChartAxesData (as a list)Category gap
AreaChartRequiredOptional
BarChartRequiredOptionalOptional
BubbleChartRequiredOptional
LineChartRequiredOptional
ScatterChartRequiredOptional
StackedAreaChartRequiredOptional
StackedBarChartRequiredOptionalOptional

It doesn’t actually matter to JavaFX what’s on the axes – they can be numbers, or they can be ‘categories’. In fact any graph can have any two types of axes, but some combinations are more sensible than others.

Line charts generally show a trend, so showing a line graph with two Category axes is possible, but probably has limited uses.

Equally, you can make a bar chart with a numerical X-axis, but this isn’t the expectation. The BarChart and StackedBarChart expect that one axis will be categorical. In fact, whether it’s a horizontal or vertical bar chart will depend on which axis is categorical.

Creating the chart with FXML

Similarly charts can be created using FXML. This time, the axes should be defined using the <xAxis> and <yAxis> tags.

<LineChart fx:id="worldPopulationChart">
  <xAxis>
    <NumberAxis fx:id="yearAxis" label="Year"/>
  </xAxis>
  <yAxis>
    <NumberAxis fx:id="populationAxis" label="Population (billion)" lowerBound="-1e9" upperBound="7e9" autoRanging="false" tickUnit="1e9"/>
  </yAxis>
</LineChart>

Warning: If you define an axis in FXML and you want to set the upper and lower bounds yourself, you will also need to specify autoRanging="false" as FXML-created number axes are created by default with auto-ranging enabled.

Axes

There are a lot of benefits to parameterizing axes, which isn’t core to creating charts. If you’re interested, the dropdown has much more detail on how to make axes that work for you.

Updating your Chart

Updating a chart can either be used to add initial data if you created your chart without data, or to update a chart that already has data by adding or removing data.

For pie charts, invoking getData() on an instance of the chart will return an ObservableList of PieChart.Data objects that we can modify.

For charts with axes, invoking getData() on an instance will return an ObservableList of XYChart.Series objects that we can modify to add or remove series. We can also modify each XYChart.Series object to add or remove data.

I’ll take you through the datatypes as I go through updating each chart type.

Updating a pie chart

Pie charts don’t have axes, and don’t support visualising more than one set of data. That makes pie chart data relatively simple.

Adding data to a Pie Chart

Invoking getData() on an instance of the chart returns a modifiable ObservableList of PieChart.Data objects.

Adding data to the pie chart is just a case of passing in one or more PieChart.Data objects to the ObservableList using methods common to all observable lists:

//add data maintaining a strong reference to it
PieChart.Data tuesday = new PieChart.Data("Tuesday", 12);
chart.getData().add(tuesday);
//add data, losing the reference
chart.getData().addAll(
        new PieChart.Data("Wednesday", 15), 
        new PieChart.Data("Thursday", 17), 
        new PieChart.Data("Friday", 4)
);
//add data at a specific point in the list
chart.getData().add(
        0, //add data to the start of the list
        new PieChart.Data("Monday", 5));

Removing data from a Pie Chart

Again by invoking getData() on an instance of the chart returns a modifiable ObservableList of PieChart.Data objects. We can use this list to remove objects by either object reference or index.

chart.getData().remove(0); //remove the first item
chart.getData().remove(tuesday);

Updating a chart with axes

One benefit of JavaFX’s decision to encapsulate all of the data maintenance in the XYChart class is that in spite of the different ways charts can be displayed, all of the operations for updating and maintaining their data are the same.

Axis charts are designed to facilitate showing one or more data series of two dimensional data. Every series is defined as an XYChart.Series object. Inside each series is an ordered list of XYChart.Data objects.

If you’ve parameterized your chart, which I would always recommend you do, the Data objects inside each series will inherit that parameterization. This makes code more explicit, and reduces hard to detect errors.

XYCharts in JavaFX can be parameterized, and store data or the correct parameterized type for display on the chart

You can update a chart either by adding and removing series, or by modifying series by adding and removing data points.

Adding a series

By invoking getData() on an instance of any chart with axes returns a modifiable ObservableList of XYChart.Series objects. We can just add XYChart.Series objects to this list

//add series losing reference from controller
barChart.getData().add(
        new XYChart.Series<>() //this is an empty series, but doesn't have to be.
);
//add maintaining strong reference
XYChart.Series<String, Number> dailyEnergy = new XYChart.Series<>(
        FXCollections.observableArrayList(
                new XYChart.Data<>("Monday", 10),
                new XYChart.Data<>("Tuesday", 17),
                new XYChart.Data<>("Wednesday", 15),
                new XYChart.Data<>("Thursday", 3),
                new XYChart.Data<>("Friday", 45)
                
        )
);
barChart.getData().add(dailyEnergy);

Removing a series

Again let’s invoke getData() to get a modifiable ObservableList of XYChart.Series objects. We can use this list to remove objects by either object reference or index.

barChart.getData().remove(0); //remove the first item
barChart.getData().remove(dailyEnergy);

Adding data points

You can also individually add data points to a series that’s already being displayed on a chart. You can do this with or without a reference to the original series, although it is significantly easier with a reference to the series.

If you still have the reference to the series that holds the data points, you can update the series directly. The ordered list of data in XYChart.Series is observable, so the chart will update if you change the list.

dataSeries.getData().add(new XYChart.Data<>("Monday", 1));

If we’ve lost the reference to the data series, we can still get it, but the method chaining can look a bit long. Bear with me.

We need to:

  • Get the list of data series,
  • Find the series we want (let’s get the first one),
  • Return the data points from that series, and
  • Add the data.

That looks like this:

barChart.getData().get(0).getData().add(XYChart.Data dataPoint)

It’s safe to say I’d always recommend keeping a reference to a series if you want to update it…

Removing old data from charts

Assuming we’ve kept the reference to the data series we want to modify, removing data can be done either by index or by object reference.

dataSeries.getData().remove(reference);
dataSeries.getData().remove(0);

This can be really useful for real-time graphs, where you want to remove old data to avoid graphs becoming cluttered.

Creating Live Charts

By combining the steps above for adding and removing data, it’s a really easy next step to creating live charts. The two things we need to do to accomplish this are:

  • Create a background task and find a way to update the chart on the application thread
  • Use this to add a point, and remove a point

There are several really good ways to achieve this. In fact, if you’ve ever struggled to get something to update in JavaFX, I suggest you check out how to make sure your scene never freezes again.

What we’ll do here is based on the idea of using events to drive updates on the window. Rather than using executor services and having to dump the code into Platform.runlater() calls, the event-handling process is purpose-built to manage updates to the UI.

To create a background process, let’s just create a Timeline that runs every second. We’ll instruct it to fire an ActionEvent every time.

Creating an event to update the Chart

Let’s create an EventHandler that will update our chart for us. Code in event handlers is always fired on the UI thread, so we’re guaranteed not to run into any thread-based problems.

First, we’ll create a member variable to track how many points we’ve plotted so far. Because we’ll start with 9 points, let’s set it to 9. You might want to find a more elegant way to do this, but it’s a good start.

int numberOfPoints = 9;

Now we’ll create an event handler that’s going to update the chart with a new point every time.

EventHandler<ActionEvent> chartUpdater = new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        Random random = new Random();
        //add a new point to the chart
        revenueData.getData().add(
                new XYChart.Data<>(numberOfPoints++, random.nextDouble() * 1e8)
        );
        //remove the first point, because that's the left-most.
        revenueData.getData().remove(0);
    }
};

Firing the event every second

Once we’ve got that sorted, we just need to enter the code that’s going to fire our event every second. We can set this using the Duration value passed to the KeyFrame object, so you’re not limited to running events on a second-by-second basis.

Timeline updateChart = new Timeline(new KeyFrame(Duration.seconds(1), chartUpdater));
updateChart.setCycleCount(Timeline.INDEFINITE);
updateChart.play();

This code uses JavaFX’s in-built animation support to fire an event every second. And it uses JavaFX’s in-built event support to update the chart on the UI thread.

Events are perfect when it comes to driving changes in your view. You could set this process to run when a user requests an update, or on a timer, both with events. To learn more about driving changes in your UI with events, check out this article on exactly that..

Animated JavaFX line chart updated using animation Timeline and events

If you prefer your chart not to grope around like that while it finds where to plot the new point, you can turn animation off using chart.setAnimated(false).

If you’re pretty sharp, you might have noticed that the y-axis values are also formatted to show a double value with a “bn” units identifier. We can accomplish that with axis formatting.

Formatting your axis values

There are a lot of situations where it’s easier to pass raw values into a NumberAxis, but you would like the axis itself to be formatted in some way.

  • Large numbers (1000 to 1k)
  • Currency (55 to $55)

To convert a raw number like a double or an integer into formatted String, we can use a StringConverter object. The StringConverter object allows you to convert both ways, and so requires you to define methods for toString() and fromString() when you create it.

The only method we’re interested in is toString() as this will be used by the chart to display our number on the axis.

StringConverter is parameterizable, and so as we create it, we’ll define it’s specific to Number objects. This is going to let us convert numbers to strings. Then, the toString() method takes that Number object and returns a String.

In this case, I’ll create a converter to test whether a number is greater than a thousand. If it is, we’ll divide that number by 1000 and add “k” to denote thousands instead.

StringConverter<Number> axisFomatter = new StringConverter<Number>() {
    @Override
    public String toString(Number axisValue) {
        if(axisValue.doubleValue() >= 1000){
            return (axisValue.doubleValue() / 1e9) + " bn";
        } else {
            return axisValue.toString();
        }
    }
    @Override
    public Number fromString(String string) {
        return null;
    }
};

Then, we just need to set it on the axis by invoking setTickLabelFormatter() with our converter.

axis.setTickLabelFormatter(axisFomatter);

Conclusions

JavaFX provides 8 charts that can be used to visualise data. 7 of these charts display data plotted on two axes.

Creating charts is simpler than you might think. Even with charts taht require axes, these can be created either in Java code, or via FXML. Axes can be modified to specify the plot area in which to display data by setting lower and upper bounds.

It’s simpler than you think to combine these bits of code to create a simple live chart. By using JavaFX’s in-built animation Timeline, and its event handling framework, we can create an event to fire every second and use it to update our chart.

Finally, axis tick labels can be modified by setting a StringConverter, which will manipulate the given axis value and return a formatted String, which the axis will use to display the label.