Playfair

While searching for a name for the new project, I looked up the inventor of charts for fun. I was amazed to find that this person actually existed. William Playfair (born September 22, 1759, near Dundee, Scotland; † February 11, 1823 in Covent Garden, London, England) is a pioneer of information graphics and is considered the inventor of bar and pie charts. What an unexpected opportunity to honor this man. The name also fits perfectly with an open source project that aims to provide simple charts without a lot of fuss. This article shows what has changed since the first bar chart and how much Java takes off our hands.

I have already reported on the creation of bar charts for Asciidoctor in the article Diagram As Code. There, based on Asciidoctor Diagram, an extension was written that generates SVG graphics and inserts them into the Asciidoctor output.

The first approach was appropriate for a single chart type. The data was read in, a Graphics object was generated from Java AWT, and the corresponding chart was drawn with it on a fixed area. The approach also works for pie charts, but when implementing additional features, such as a title and a legend, the first weaknesses of the naive approach became apparent. The drawing methods became increasingly complicated as new constraints had to be taken into account.

Fortunately, Java AWT also provides us with a solution. We split the charts into individual components and layout them. This could all be done with Java AWT, Swing, or Java FX, but that seems rather excessive for drawing simple SVG charts.

The first change is the use of a custom component base class that provides a rectangle for the position and size. For the moment, there are five implementations of the component class: Canvas, BarChart, PieChart, DonutChart, Legend, and Text.

The Canvas class is the container that holds instances of the other classes and layouts and positions them using a simple algorithm. The BarChar, PieChart, and DonutChart classes draw the respective charts and are flexible in size. The Legend class obviously provides the legend, and Text takes care of the title and footer of the chart.

[barchart,target=barchart-example]
----
title: Missed Roadrunners by Month in 2024
data: 100, 200, 300, 400, 300, 200, 300, 400, 500, 600, 700, 600
index.labels: Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember
palette: funky
footnote: © ACME Inc. All rights reserved.
----

The bar chart example above has an additional title and footer, which are generated via two corresponding lines in the configuration (yes, the footer is named wrong). The fonts and colors are defaults and can also be changed, as we will see later.

The layout for this adjustment is not particularly difficult, as the height of the bar chart is reduced by the height of the title and footer. These two components get exactly the space they need, because we don’t want to produce ugly charts. The bar chart continues to be drawn correctly because the calculations are based on the size of the BarChartComponent. If the bars turn out to be too narrow, we can widen the canvas using the size attribute.

[barchart,target=barchart-grouped]
----
groups: Germany, France, Spain
data.1: 60, 75, 90, 15, 60, 20
data.2: 60, 15, 20, 75, 60, 50
data.3: 30, 50, 40, 25, 40, 90
index.labels: Comedy, Action, Romance, Drama, Sci-Fi, Western
colors: #ffff00, #00ffff
padding: 4
background: #ffffff
legend.padding: 16
legend.font: Serif italic 14
legend.position: left
----

The bar chart above shows three different data series grouped together. It needs a legend to show the names of the data series. In this case, a little space must be left on the left or right side of the chart for the legend. The legend shows the names of the data series and the associated color. A colored circle is placed in front of the name for this purpose. In the example, the text is displayed in a different font. This required very little effort because the notation font-name style size is provided by the java.awt.Font#decode method.

[piechart,target=piechart-inner]
----
data: 60, 75, 90, 15, 60
labels: Comedy, Action, Romance, Drama, Sci-Fi
palette: forrest
label.position: inner
label.color: #fffffff0
gradient: on
legend: on
legend.position: left
legend.marker: ❤
legend.title: Genres
legend.font: Sans-Serif plain 18
legend.padding: 8
----

The new pie chart is created using the piechart keyword in the Asciidoctor environment. It displays a data series as circular segments. In this example, the labels are displayed inside the circle, but they can also be positioned outside. What immediately stands out is the color gradient in the chart. The effort required to achieve this nice effect is surprisingly small.

Paint paint = new RadialGradientPaint(gradientCenter, radius, fractions, new Color[]{color.brighter().brighter(), color});
g.setPaint(paint);
g.draw(area);

The first line creates a java.awt.RadiaGradientPaint that defines a circular color gradient with a lighter shade of the selected color at the center of the gradient and the color as the end of the gradient. Since all areas use the same point in the upper quarter of the circle as the center of the gradient, this effect is created. Since the colors themselves also implement the Paint interface in Java AWT, we can pass the actual color to Graphics#setPaint and thus disable the effect.

As you can see, in this legend, the marker has been replaced by a ❤. If you use the possibilities offered by Unicode, you don’t need to waste time drawing the marker and can still use many different markers (░, ▒, ▓, ▩, ▨, ▧, ▲, ◆, ◉, ●, ◈, ☁, ★, ☀, ♥, ♦, ⚑, ✦, ✸, ✹, ✿, ❖, ❤, ➽, ⬢, ⬣, ⬤, ⬮, ⬬, ⯃, ⯄, ⯀, ⯁,⯅,⯀, ■, 𝄞, 𝅘𝅥𝅮, 🌢, 🞺, 🟓).

[donutchart,target=donutchart-example]
----
data: 31451, 51253, 43149, 32204, 36140, 3078, 54861
labels: Freemarker, FreshMarker, Mustache, Handlebars, Velocity, Thymeleaf, Pebble
colors: #222222, #b3ff80, #444444, #666666, #888888, #aaaaaa, #cccccc
palette: azure
----

Donut charts have also been included in Playfair alongside pie charts. Donut charts are created using the keyword donutchart in the Asciidoctor environment. In this example, there is no legend, so the labels and values are written to the segments. The labels are positioned outside the segments because the label.position attribute is missing. Donut charts can be used in exactly the same way as pie charts because they are actually just pie charts with a hole.

This is literally true because the common base class draws an java.awt.geom.Area instead of an java.awt.geom.Arcs2D. This area is instantiated in the pie chart as follows.

Area area = new Area(new Arc2D.Double(pieX, pieY, diameter, diameter, Math.toDegrees(angleStart), Math.toDegrees(angleExtend), Arc2D.PIE));

The donut chart uses the same area, but it is modified.

Area hole = new Area(new Ellipse2D.Double(pieX + radius / 2, pieY + radius / 2, radius, radius));

Area area = new Area(new Arc2D.Double(pieX, pieY, diameter, diameter, Math.toDegrees(angleStart), Math.toDegrees(angleExtend), Arc2D.PIE));
area.subtract(hole);

A predefined Area named hole is generated from a circle, and this circle is cut out of the circle segment area. Since the hole is located in the center of the circle formed by the circle segments, a hole is cut out in the center.

These are the options currently available in Playfair 0.6.0. Edge cases such as huge legends or excessively long titles are not yet taken into account, but this library is not intended for that purpose.

<dependency>
    <groupId>de.schegge</groupId>
    <artifactId>playfair</artifactId>
    <version>0.6.0</version>
</dependency>

Leave a Comment