8. Charts

JavaFX comes with a handy set of charts to quickly display data visualizations. While there are more comprehensive charting libraries like JFreeChart and Orson Charts which work fine with TornadoFX, the built-in JavaFX charts satisfy a majority of visualization needs. They also have elegant animations when data is populated or changed.

TornadoFX comes with a few builders to streamline the declaration of charts using functional constructs.

PieChart

The PieChart is a common visual aid to illustrate proportions of a whole. It is structurally simpler than XY charts which we will learn about later. Inside a piechart() builder you can call the data() function to pass multiple category-value pairs (Figure 8.1).

piechart("Desktop/Laptop OS Market Share") {
data("Windows", 77.62)
data("OS X", 9.52)
data("Other", 3.06)
data("Linux", 1.55)
data("Chrome OS", 0.55)
}

Figure 8.1

Note you can also provide an explicit ObservableList<PieChart.Data> prepared in advance.

val items = listOf(
PieChart.Data("Windows", 77.62),
PieChart.Data("OS X", 9.52),
PieChart.Data("Other", 3.06),
PieChart.Data("Linux", 1.55),
PieChart.Data("Chrome OS", 0.55)
).observable()
piechart("Desktop/Laptop OS Market Share", items)

The block following piechart can be used to modify any of the attributes of the PieChart just like any other control builder we covered. You can also leverage for() loops, Sequences, and other iterative tools within a block to add any number of data items.

val items = listOf(
PieChart.Data("Windows", 77.62),
PieChart.Data("OS X", 9.52),
PieChart.Data("Other", 3.06),
PieChart.Data("Linux", 1.55),
PieChart.Data("Chrome OS", 0.55)
).observable()
piechart("Desktop/Laptop OS Market Share") {
for (item in items) {
data.add(item)
}
}

Map-Based Data Sources

Sometimes you may want to build a chart using a Map as a datasource. Using the Kotlin to operator, you can construct a Map in a Kotlin-esque way and then pass it to the data function.

val items = mapOf(
"Windows" to 77.62,
"OS X" to 9.52,
"Other" to 3.06,
"Linux" to 1.55,
"Chrome OS" to 0.55
)
piechart("Desktop/Laptop OS Market Share") {
data(items)
}

XY Based Charts

Most charts often deal with one or more series of data points on an XY axis. The most common are bar and line charts.

Bar Charts

You can represent one or more series of data points through a BarChart. This chart makes it easy to compare different data points relative to their distance from the X or Y axis (Figure 8.2).

barchart("Unit Sales Q2 2016", CategoryAxis(), NumberAxis()) {
series("Product X") {
data("MAR", 10245)
data("APR", 23963)
data("MAY", 15038)
}
series("Product Y") {
data("MAR", 28443)
data("APR", 22845)
data("MAY", 19045)
}
}

Figure 8.2

Above, the series() and data() functions allow quick construction of data structures backing the charts. On construction, you will need to construct the proper Axis type for each X and Y axis. In this example, the months are not necessarily numeric but rather Strings. Therefore they are best represented by a CategoryAxis. The units, already being numeric, are fit to use a NumberAxis.

In the series() and data() blocks, you can customize further properties like colors. You can even call style() to quickly apply type-safe CSS to the chart.

LineChart and AreaChart

A LineChart connects data points on an XY axis with lines, quickly visualizing upward and downward trends between them (Figure 8.3)

linechart("Unit Sales Q2 2016", CategoryAxis(), NumberAxis()) {
series("Product X") {
data("MAR", 10245)
data("APR", 23963)
data("MAY", 15038)
}
series("Product Y") {
data("MAR", 28443)
data("APR", 22845)
data("MAY", 19045)
}
}

Figure 8.3

The backing data structure is not much different than a BarChart, and you use the series() and data() functions in the same manner.

You can also use a variant of LineChart called AreaChart, which will shade the area under the lines a distinct color, as well as any overlaps (Figure 8.4).

Figure 8.4

Multiseries

You can streamline the declaration of more than one series using the multiseries() function, and call the data() functions with varargs values. We can consolidate our previous example using this construct:

linechart("Unit Sales Q2 2016", CategoryAxis(), NumberAxis()) {
multiseries("Product X", "Product Y") {
data("MAR", 10245, 28443)
data("APR", 23963, 22845)
data("MAY", 15038, 19045)
}
}

This is just another convenience to reduce boilerplate and quickly declare your data structure for a chart.

ScatterChart

A ScatterChart is the simplest representation of an XY data series. It plots the points without bars or lines. It is often used to plot a large volume of data points in order to find clusters. Here is a brief example of a ScatterChart plotting machine capacities by week for two different product lines (Figure 8.5).

scatterchart("Machine Capacity by Product/Week", NumberAxis(), NumberAxis()) {
series("Product X") {
data(1,24)
data(2,22)
data(3,23)
data(4,19)
data(5,18)
}
series("Product Y") {
data(1,12)
data(2,15)
data(3,9)
data(4,11)
data(5,7)
}
}

Figure 8.5

BubbleChart

BubbleChart is another XY chart similar to the ScatterPlot, but there is a third variable to control the radius of each point. You can leverage this to show, for instance, output by week with the bubble radii reflecting number of machines used (Figure 8.6).

bubblechart("Machine Capacity by Output/Week", NumberAxis(), NumberAxis()) {
series("Product X") {
data(1,24,1)
data(2,46,2)
data(3,23,1)
data(4,27,2)
data(5,18,1)
}
series("Product Y") {
data(1,12,1)
data(2,31,2)
data(3,9,1)
data(4,11,1)
data(5,15,2)
}
}

Figure 8.6

Summary

Charts are a an effective way to visualize data, and the builders in TornadoFX help create them quickly. You can read more about JavaFX charts in Oracle's documentation. If you need more advanced charting functionality, there are libraries like JFreeChart and Orson Charts you can leverage and interop with TornadoFX, but this is beyond the scope of this book.