This post shows how to build a clean
stacked area chart
using R
, ggplot2
and the
tidyverse
. This chart is ready for publication. It has
a nice color palette, some very clean inline labels, a
powerfull title and slick footer caption with logos.
This page showcases the work of Gilbert Fontana, which was originally published on twitter.
Thanks to him for accepting sharing his work here! As a teaser, here is the plot we’re gonna try building:
As usual, let’s start by loading some libraries.
Today’s plot requires quite a lot of packages to be built. You can
install them with the install.packages()
function. Once
installed, load them with the library()
function:
library(tidyverse)
#library(janitor)
library(readxl)
library(ggstream)
library(showtext)
library(ggtext)
You are probably familiar with the tidyverse
already.
readxl
will be used in the next section to load the
dataset from a xlsx
format directly.
ggstream
is used to smooth the area shapes.
showtext is used to load some
custom fonts.
The data is stored on
github
in a xslx
file. To reproduce this tutorial, please
download the file and run the following line of code. The
read_excel()
function of the readxl
package
makes it easy to load this file directly without requiring the
.csv
format.
Note: do not forget to update the path to point to the file on your computer.
Everything start with a basic stacked area chart. You can see many examples in the stacked area chart section of the R graph gallery, including beginner level tutorials.
Basically, the ggplot
function is used to start a chart
with ggplot2
. Then, the year
column of the
dataset (df
) is used for the x
axis,
total_wealth
for the Y axis and everything is stacked and
colored using the country
column.
Last but not least, the geom_area
geom can be used to
create a stacked area chart.
# Stacked area chart with smoothing
df %>%
ggplot(aes(year, total_wealth, fill = country, label = country, color = country)) +
geom_area()
That’s it! 🔥 We now have a first stacked area chart showing what’s happening in our dataset.
It is possible to make the lines smoother thanks to the
geom_stream
geom of the ggstream
package.
It’s gonna create a less accurate but more organic and eye catching
shape to the graph:
# Stacked area chart with smoothing
df %>%
ggplot(aes(year, total_wealth, fill = country, label = country, color = country)) +
geom_stream(type = "ridge", bw=1)
What looks especially good in Gilbert’s chart is the color palette.
Let’s build a vector of color that we then inject into the chart using
the scale_fill_manual
and
scale_color_manual
functions:
#Color palette
pal=c("#003f5c",
"#2f4b7c",
"#665191",
"#a05195",
"#d45087",
"#f95d6a",
"#ff7c43",
"#ffa600")
# Stacking order
order <- c("United States", "China", "Japan", "Germany", "United Kingdom", "France", "India", "Other")
# Use them for the plot
df %>%
arrange(total_wealth) %>%
mutate(country = factor(country, levels=order)) %>%
ggplot(aes(year, total_wealth, fill = country, label = country, color = country)) +
geom_stream(type = "ridge" ,bw=1) +
scale_fill_manual(values=pal) +
scale_color_manual(values=pal) +
scale_x_continuous(breaks=c(2000,2005,2010,2015,2021),labels = c("2000","2005","2010","2015","2021")) +
scale_y_continuous(expand = c(0,0)) +
coord_cartesian(clip = "off")
Before adding the title, legend and inline labels we need to load some custom fonts.
This is made possible thanks to the
showtext package and its
font_add_google()
function. Using custom fonts can be a
bit tricky. Fortunately, I wrote a
complete tutorial
just in case the following code sounds strange to you.
# Name of the fonts we need
font <- "Josefin Sans"
font2 <- "Open Sans"
# Use the font_add_google() function to load fonts from the web
font_add_google(family=font, font, db_cache = FALSE)
font_add_google(family=font2, font2, db_cache = FALSE)
fa_path <- systemfonts::font_info(family = "Font Awesome 6 Brands")[["path"]]
font_add(family = "fa-brands", regular = fa_path)
theme_set(theme_minimal(base_family = font2, base_size = 3))
bg <- "white"
txt_col <- "black"
showtext_auto(enable = TRUE)
Since we are talking about thext, let’s also create the caption text that appear at the bottom of the figure:
caption_text <- str_glue("**Data:** James Davies, Rodrigo Lluberas and Anthony Shorrocks, Credit Suisse Global Wealth Databook 2022<br>",
"**Design:** Gilbert Fontana <br><br>",
"<span style='font-family: \"fa-brands\"'></span> gilbertfontana<br>",
"<span style='font-family: \"fa-brands\"'></span> GilbertFontana<br>",
"<span style='font-family: \"fa-brands\"'></span> gilbert.fontana"
)
The final figure can now be created:
annotate()
function
annotate()
function again
geom_segment()
function
plot <- df %>%
arrange(total_wealth) %>%
mutate(country = factor(country, levels=order)) %>%
ggplot(aes(year, total_wealth, fill = country, label = country, color = country)) +
geom_stream(type = "ridge" ,bw=1) +
#Title
annotate("text", x = 2000, y = 410000,
label = "Aggregated\nHousehold\nWealth",
hjust=0,
size=15,
lineheight=.9,
fontface="bold", family=font,
color="black") +
#USA
annotate("text", x = 2021.2, y = 420000,
label = "USA $145,793B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold", family=font2,
color=pal[1]) +
#China
annotate("text", x = 2021.2, y = 300000,
label = "China $85,107B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=pal[2]) +
#Japan
annotate("text", x = 2021.2, y = 245000,
label = "Japan $25,692B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=pal[3]) +
#Germany
annotate("text", x = 2021.2, y = 220000,
label = "Germany $17,489B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=pal[4]) +
#UK
annotate("text", x = 2021.2, y = 200000,
label = "UK $16,261B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=pal[5]) +
#France
annotate("text", x = 2021.2, y = 183000,
label = "France $16,159B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=pal[6]) +
#India
annotate("text", x = 2021.2, y = 165000,
label = "India $14,225B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=pal[7]) +
#Other
annotate("text", x = 2021.2, y = 80000,
label = "Rest of the world $142,841B",
hjust=0,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=pal[8]) +
## Vertical segments
geom_segment(aes(x = 2000, y = 0, xend = 2000, yend = 117426+20000),color="black") +
geom_point(aes(x = 2000, y = 117426+20000),color="black") +
annotate("text", x = 2000, y = 117426+30000,
label = "$117,844B",
hjust=0.5,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=txt_col) +
geom_segment(aes(x = 2005, y = 0, xend = 2005, yend = 181731+20000),color="black") +
geom_point(aes(x = 2005, y = 181731+20000),color="black") +
annotate("text", x = 2005, y = 181731+30000,
label = "$182,350B",
hjust=0.5,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=txt_col) +
geom_segment(aes(x = 2010, y = 0, xend = 2010, yend = 250932+20000),color="black") +
geom_point(aes(x = 2010, y = 250932+20000),color="black") +
annotate("text", x = 2010, y = 250932+30000,
label = "$251,885B",
hjust=0.5,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=txt_col) +
geom_segment(aes(x = 2015, y = 0, xend = 2015, yend = 296203+25000),color="black") +
geom_point(aes(x = 2015, y = 296203+25000),color="black") +
annotate("text", x = 2015, y = 296203+35000,
label = "$297,698B",
hjust=0.5,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=txt_col) +
geom_segment(aes(x = 2021, y = 0, xend = 2021, yend = 461370+50000),color="black") +
geom_point(aes(x = 2021, y = 461370+50000),color="black") +
annotate("text", x = 2021, y = 461370+50000,
label = "$463,567B",
hjust=1.1,
size=3,
lineheight=.8,
fontface="bold",family=font2,
color=txt_col) +
#Color scale
scale_fill_manual(values=pal) +
scale_color_manual(values=pal) +
scale_x_continuous(breaks=c(2000,2005,2010,2015,2021),labels = c("2000","2005","2010","2015","2021")) +
scale_y_continuous(expand = c(0,0)) +
#Last customization
coord_cartesian(clip = "off") +
xlab("") +
ylab("") +
labs(caption = caption_text #"Data: Flash Eurobarometer, Number 509 (October 2022)"
) +
theme(
axis.line.x = element_line(linewidth = .75),
panel.grid = element_blank(),
axis.text.y=element_blank(),
axis.text.x = element_text(color=txt_col, size=10,margin = margin(5,0,0,0)),
plot.margin = margin(20,120,20,20),
legend.position = "none",
plot.caption = element_markdown(hjust=0, margin=margin(10,0,0,0), size=8, color=txt_col, lineheight = 1.2),
)
👋 After crafting hundreds of R charts over 12 years, I've distilled my top 10 tips and tricks. Receive them via email! One insight per day for the next 10 days! 🔥