How to build a waffle chart with circle-shaped tiles using {waffle} and {ggplot2} libraries in R?
Creators & Contributors
Waffle charts are a useful way to visualize part-to-whole relationships. Commonly, waffle charts depict a grid of regular squares to represent the distribution of a categorical variable. Previously, in a post on this Blog, I built the following waffle chart to visualize disease outbreaks in the world between 2015 and 2024. This visualization was developed in support of an analysis paper published in the BMJ Public Health journal, which can be accessed here. As can be seen, this waffle chart uses the traditional approach of stacking colored squares to represent the number of outbreaks per year by pathogen. In this post, I will illustrate a visual alternative in which circles are used instead of squares to represent the distribution of diseases by year. The information is sourced from the global dataset of pandemic- and epidemic-prone disease outbreaks, whose data are freely available at the GitHub repository of the disease outbreaks project. This global dataset documents more than 3,450 outbreaks across over 230 countries and territories from January 1996 to January 2026. The diseases are classified according to the International Classification of Diseases, 10th Revision (ICD-10), and the dataset contains information on the year, country, and pathogen for each outbreak. The dataset of pandemic- and epidemic-prone disease outbreaks is also part of the Humanitarian Data Exchange coordinated by the United Nations Office for the Coordination of Humanitarian Affairs (OCHA). To create the waffle chart, we will use the following R packages: The table with the organized data can be downloaded here. This table contains the outbreaks recorded by year and by disease worldwide. The dataset is stored in a This table is shown below: First, I define the font to be used in the final chart. To do this, I commonly use the Then, I customize a theme to be applied to the plot. Then, I define the title, subtitle, and caption of the plot. For the caption, I use rich text by introducing markdown to format specific elements. This is enabled through the First, I use the Additionally, to In this example, I use the set of colors from the paletteMartin palette of the Finally, to transform the tiles from squares into circles, I use radius as an aesthetic inside Overview
About the data
Set-up
Loading data
.csv file and is ready to be downloaded from here.outbreaks_year_disease_grouped <- read.csv("outbreaks_year_disease_grouped.csv")outbreaks_year_disease_grouped |>
arrange(Year, -freq) |>
kbl(caption = "Disease outbreaks per disease and year") |>
kable_paper("hover", full_width = F)Warning in attr(x, "align"): 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Year
icd104n
freq
2021
COVID-19
220
2021
Poliomyelitis
23
2021
Other
11
2021
Yellow fever
10
2021
Influenza
5
2021
Cholera
2
2021
Monkeypox
2
2021
Dengue
1
2022
COVID-19
230
2022
Monkeypox
53
2022
Other
39
2022
Hepatitis
38
2022
Cholera
29
2022
Yellow fever
12
2022
Poliomyelitis
6
2022
Dengue
5
2022
Influenza
5
2022
Measles
2
2023
COVID-19
210
2023
Dengue
60
2023
Other
40
2023
Cholera
19
2023
Yellow fever
12
2023
Influenza
9
2023
Measles
7
2023
Poliomyelitis
5
2023
Chikungunya
5
2023
Monkeypox
1
2024
COVID-19
142
2024
Dengue
95
2024
Other
32
2024
Yellow fever
13
2024
Influenza
10
2024
Monkeypox
8
2024
Poliomyelitis
1
2025
Influenza
121
2025
COVID-19
109
2025
Chikungunya
38
2025
Monkeypox
35
2025
Cholera
31
2025
Other
27
2025
Measles
7
2025
Yellow fever
5
2025
Poliomyelitis
2
Step 1. Visual elements of the plot
font_add_google() function from the showtext package. package. This function retrieves font families from the Google Fonts repository. In this example, I use the Atkinson Hyperlegible Next font.# Add custom font
font_add_google("Atkinson Hyperlegible Next", "Atkinson Hyperlegible Next")
showtext_auto()# Custom theme for the waffle chart
theme_waffle_chart <- function() {
# Introduce the previously selected font
theme_minimal(base_family = "Atkinson Hyperlegible Next") +
# Custom theme settings
theme(
# Axis settings
axis.title = element_blank(), # Remove axis titles
axis.line = element_blank(), # Remove axis lines
# Title settings
plot.title.position = "plot", # Position of the title
plot.title = element_textbox(
color = "black",
face = "bold",
size = 24,
margin = margin(5, 0, 5, 0), # Top, right, bottom, left margins
width = unit(1, "npc") # Full plot width
),
plot.margin = unit(c(0.25, 0.25, 0.25, 0.25), "cm"),
# Legend
legend.justification = c(1, 1),
legend.title = element_text(face = "bold", size = 14),
legend.title.position = "top",
legend.text = element_text(face = "bold", size = 12),
legend.direction = "vertical",
legend.spacing.x = unit(30, "pt"),
legend.key.size = unit(11, "pt"),
legend.key.spacing.y = unit(3, "pt"),
legend.key.spacing.x = unit(10, "pt"),
# Subtitle settings
plot.subtitle = element_textbox(
color = "grey50",
face = "bold",
size = 18,
margin = margin(0, 0, 40, 0), # Top, right, bottom, left margins
width = unit(1, "npc")
),
# Caption settings
plot.caption = element_textbox(
color = "grey70",
size = 14,
hjust = 0
),
plot.caption.position = "plot",
# Background and margins
plot.background = element_rect(
color = "white",
fill = "white"
),
panel.grid = element_blank(),
strip.text.x = element_text(face = "bold", margin = margin(t = 10), color = "black", size = 20),
# Axis
axis.ticks.y = element_line(linewidth = 1),
axis.ticks.length.y = unit(5, "pt"),
axis.text.x = element_text(face = "bold", color = "black", size = 12),
axis.text.y = element_text(face = "bold", color = "black", size = 15),
axis.title.x = element_text(face = "bold", margin = margin(t = 10), color = "black", size = 13),
axis.title.y = element_text(face = "bold", margin = margin(r = 10), color = "black", size = 17)
)
}# Title, subtitle, and caption for the waffle chart
title_chart <- "Pandemic- and epidemic-prone disease outbreaks in the world | 2021–2025"
subtitle_chart <- "In 2025, influenza emerged as the most reported disease behind outbreaks, being responsible for 121 events."ggtext package.caption_chart <- paste0(
"**Note:** Each circle represents a country.",
"<br>",
"**Data:** A global dataset of pandemic- and epidemic-prone disease outbreaks (DOI: 10.1038/s41597-022-01797-2).",
"<br>",
"**Graphic:** Juan Torres Munguía."
)Step 2. Designing the waffle plot using
ggplot2 packagegeom_waffle() function to construct the waffle chart with squares. The main arguments of this function include size (border size of the tiles), n_rows (number of rows in the waffle grid), flip (orientation of the tiles), color (border color), and make_proportional (whether values are rescaled to proportions).ggplot(outbreaks_year_disease_grouped,
aes(fill = icd104n, values = freq)) +
geom_waffle(size = 0.75,
n_rows = 10,
flip = TRUE,
color = "white",
make_proportional = FALSE) +
facet_wrap(~Year,
nrow = 1,
strip.position = "bottom")colorBlindness package. I also add the custom theme() along with the title, subtitle, and caption elements.ggplot(outbreaks_year_disease_grouped,
aes(fill = icd104n, values = freq)) +
geom_waffle(size = 0.75,
n_rows = 10,
flip = TRUE,
color = "white",
make_proportional = FALSE) +
facet_wrap(~Year,
nrow = 1,
strip.position = "bottom") +
scale_fill_manual(values = c(paletteer_d("colorBlindness::paletteMartin"))) +
scale_x_discrete() +
scale_y_continuous(labels = function(x) x * 10,
expand = c(0, 0)) +
coord_equal() +
labs(
title = title_chart,
subtitle = subtitle_chart,
caption = caption_chart,
x = "",
y = "Frequency of disease outbreaks",
fill = "Pathogen:") +
guides(fill = guide_legend(position = "right")) +
theme_waffle_chart()Step 3. Transforming the squares into circles.
aes(), assigning the value grid::unit(0.5, "npc"). This produces circular tiles while preserving the waffle layout structure.ggplot(outbreaks_year_disease_grouped,
aes(fill = icd104n, values = freq)) +
geom_waffle(radius = grid::unit(0.5, "npc"),
size = 0.75,
n_rows = 10,
flip = TRUE,
color = "white",
make_proportional = FALSE) +
facet_wrap(~Year,
nrow = 1,
strip.position = "bottom") +
scale_fill_manual(values = c(paletteer_d("colorBlindness::paletteMartin"))) +
scale_x_discrete() +
scale_y_continuous(labels = function(x) x * 10,
expand = c(0, 0)) +
coord_equal() +
labs(
title = title_chart,
subtitle = subtitle_chart,
caption = caption_chart,
x = "",
y = "Frequency of disease outbreaks",
fill = "Pathogen:") +
guides(fill = guide_legend(position = "right")) +
theme_waffle_chart()Step 4. Save the waffle chart as a high-quality image
showtext_opts(dpi = 320) # Resolution of 320 dpi for high-quality images ("retina")
ggsave(
"waffle-pandemics-2026.png",
dpi = 320,
width = 14,
height = 10,
units = "in"
)
showtext_auto(FALSE)
Citation
@online{torres_munguía2026,
author = {Torres Munguía, Juan Armando},
title = {How to Build a Waffle Chart with Circle-Shaped Tiles Using
\{Waffle\} and \{Ggplot2\} Libraries in {R?}},
date = {2026-02-14},
url = {https://juan-torresmunguia.netlify.app/blog/posts/waffle-chart-disease-outbreaks-2025/},
langid = {en}
}
Additional details
Description
Overview Waffle charts are a useful way to visualize part-to-whole relationships. Commonly, waffle charts depict a grid of regular squares to represent the distribution of a categorical variable. Previously, in a post on this Blog, I built the following waffle chart to visualize disease outbreaks in the world between 2015 and 2024.
Identifiers
- UUID
- 1414d08c-b894-4c04-a8b2-7ba4358448ae
- GUID
- https://juan-torresmunguia.netlify.app/blog/posts/waffle-chart-disease-outbreaks-2025/
- URL
- https://juan-torresmunguia.netlify.app/blog/posts/waffle-chart-disease-outbreaks-2025/
Dates
- Issued
-
2026-02-14T06:00:00
- Updated
-
2026-02-14T06:00:00
