• Home
  • Projects
  • Software
  • Talks
  • Publications
  • ML
  • Blog
  • More
    • Segmented
    • R for Driving Sim
    • Ontario Weather Dashboard
    • Interactive Plotting in R
    • Histogram y axis
    • Decision Tree Example
    • BBC 100 Women

Images as Facet Labels in ggplot2

visualization
ggplot2
ggh4x
ggtext
The power of {ggtext}
Published

September 4, 2023

In this post, I will show you how to replace the facet labels in ggplot2 with images. The images I’ll use in this post are country flags. Let’s start with loading the required packages:

suppressPackageStartupMessages(library(tidyverse))
suppressPackageStartupMessages(library(jsonlite))
suppressPackageStartupMessages(library(ggtext))
suppressPackageStartupMessages(library(gapminder))
suppressPackageStartupMessages(library(ggh4x))

Data

I am going to get the images of flags of countries that are in the gapminder dataset. The flag images are available in this Github repo. First, {jsonlite} will help in getting a json of country codes and names:

country_code <- jsonlite::read_json("https://raw.githubusercontent.com/hampusborgos/country-flags/main/countries.json") |> 
  data.frame() |>
  pivot_longer(cols = everything(), names_to = "abb", values_to = "country") |> 
  mutate(abb = tolower(abb),
         abb = stringr::str_remove(abb, "\\.$"))

head(country_code)
# A tibble: 6 × 2
  abb   country             
  <chr> <chr>               
1 ad    Andorra             
2 ae    United Arab Emirates
3 af    Afghanistan         
4 ag    Antigua and Barbuda 
5 ai    Anguilla            
6 al    Albania             

Next, I join the country_code with gapminder:

gm_joined <- gapminder |> 
  left_join(country_code, by = "country") |> 
  drop_na(abb) 

head(gm_joined)
# A tibble: 6 × 7
  country     continent  year lifeExp      pop gdpPercap abb  
  <chr>       <fct>     <int>   <dbl>    <int>     <dbl> <chr>
1 Afghanistan Asia       1952    28.8  8425333      779. af   
2 Afghanistan Asia       1957    30.3  9240934      821. af   
3 Afghanistan Asia       1962    32.0 10267083      853. af   
4 Afghanistan Asia       1967    34.0 11537966      836. af   
5 Afghanistan Asia       1972    36.1 13079460      740. af   
6 Afghanistan Asia       1977    38.4 14880372      786. af   

I know that many countries are dropped as not all countries are stored with the exact same string in both dataframes. But I’m not going to worry about that in th is post :)

Since each country flag is stored as a png in the png250px folder on the repo, I create a url based on the country abbreviation as a new column. This will be helpful in downloading the flag images in the next step.

gm_joined <- gm_joined |> 
  mutate(url = paste0("https://github.com/hampusborgos/country-flags/blob/main/png250px/", abb, ".png?raw=true")) 

country_flag_urls <- gm_joined |>
  distinct(country, url) |> 
  mutate(country = gsub(" ", "", country))

urls <- country_flag_urls$url
names(urls) <- country_flag_urls$country

head(country_flag_urls)
# A tibble: 6 × 2
  country     url                                                               
  <chr>       <chr>                                                             
1 Afghanistan https://github.com/hampusborgos/country-flags/blob/main/png250px/…
2 Albania     https://github.com/hampusborgos/country-flags/blob/main/png250px/…
3 Algeria     https://github.com/hampusborgos/country-flags/blob/main/png250px/…
4 Angola      https://github.com/hampusborgos/country-flags/blob/main/png250px/…
5 Argentina   https://github.com/hampusborgos/country-flags/blob/main/png250px/…
6 Australia   https://github.com/hampusborgos/country-flags/blob/main/png250px/…

Now that I have the url to each country flag, I am ready to download the images to a folder named flags:

download.file(urls, paste0("flags/", names(urls), ".png"), mode="wb")

Theme for flags

The trick is to use ggtext::element_markdown() for facet strips to replace the facet labels with images. Therefore, I first generate the markdown for reading images:

flag_markdown <- paste0("<img src=", list.files("flags/", full.names = TRUE), " width='100'/>")

names(flag_markdown) <- names(urls)

head(flag_markdown)
                                   Afghanistan 
"<img src=flags/Afghanistan.png width='100'/>" 
                                       Albania 
    "<img src=flags/Albania.png width='100'/>" 
                                       Algeria 
    "<img src=flags/Algeria.png width='100'/>" 
                                        Angola 
     "<img src=flags/Angola.png width='100'/>" 
                                     Argentina 
  "<img src=flags/Argentina.png width='100'/>" 
                                     Australia 
  "<img src=flags/Australia.png width='100'/>" 

Now each country name and corresponding markdown for its flag image is available in flag_markdown. I also add the continents to this vector so that the plot contains information about the continents as well as countries:

continents <- as.character(unique(gm_joined$continent))
names(continents) <- continents

flag_markdown <- c(flag_markdown, continents)

Next, I create a theme that will use ggtext::element_markdown() to replace the strip text with image:

theme_flag <- function(base_size = 20,
                          title_size = 20,
                         ...){
  # CUSTOM THEME:
  ggplot2::theme_minimal(base_size = base_size) +
    ggplot2::theme(
      # title
      plot.title = element_text(size = title_size),
      plot.title.position = "plot",
      
      #strip
      strip.text.x = element_markdown(size = 30),
      ...
    )
}

Flags!

To demonstrate the use of ggtext::element_markdown(), I’m going to plot life expectancy over GDP per capita in different countries. I also utilize the ggh4x::facet_nested_wrap() function to label both the continents and countries:

gm_joined |> 
  filter(country %in% country_flag_urls$country) |>
  ggplot(mapping = aes(gdpPercap, lifeExp)) +
  geom_smooth() +
  labs(x = "GDP", y = "Life Expectancy") + 
  facet_nested_wrap(~ continent + country, nest_line = TRUE, labeller = as_labeller(flag_markdown),
             scales = "free",
             ncol = 5) +
  theme_flag()