A bar chart race shows how a ranking changes over time. It suits panel data where the same entities are measured repeatedly and their order is the point: which countries lead, which have caught up, when a position changed hands. This vignette covers the data it expects, the arguments that shape it, and how to write the animation to a file.
ggrace() takes long data with one row per entity per
time point and three bare column names: the value that sets the bar
length, the label, and the time.
head(clefts_qci)
#> country iso region year qci
#> 1 Brazil br Latin America 1990 0.000000
#> 2 Brazil br Latin America 1991 2.922546
#> 3 Brazil br Latin America 1992 6.361471
#> 4 Brazil br Latin America 1993 12.062058
#> 5 Brazil br Latin America 1994 19.362466
#> 6 Brazil br Latin America 1995 29.275937clefts_qci holds the Quality of Care Index for orofacial
clefts in fifteen countries from 1990 to 2019, taken from Sofi-Mahmudi
et al. (2025). Higher is better care. Each country appears once per
year, which is what the function requires: a repeated country and year
pair is an error rather than a silent average.
race <- ggrace(
clefts_qci,
value = qci,
name = country,
time = year,
top_n = 15,
duration = 15,
title = "Quality of care for orofacial clefts",
caption = "Source: Sofi-Mahmudi et al. 2025, PLOS ONE 20(1): e0317267"
)
racePrinted, the race plays in the page, like the package’s other graphs:
the round button plays and pauses it, clicking or dragging along the
timeline moves to any time, and the arrow keys step from one year to the
next. Hovering over a bar shows its value and rank at that moment, and
clicking one follows it through the race while the others fade.
graph_save(race, "race.html") writes it as a single web
page.
The object holds one row per entity per frame, already interpolated,
and the widget plays exactly these frames, so it matches the GIF or
video that animate_race() writes.
Every frame is an ordinary ggplot object, so it can be
printed, saved with ggplot2::ggsave(), or inspected before
committing to a full render.
Frames are set in Lato, which the package registers when it loads.
Devices that understand registered fonts, such as
ragg::agg_png(), will use it; the older pdf()
and png() devices will not, so draw with ragg or pass
family = "" to fall back to the device default.
animate_race() draws every frame and encodes them. The
encoder follows the file extension: .gif uses gifski,
falling back to magick, and anything else is treated as video and uses
av, falling back to an ffmpeg binary on the search
path.
Frames are independent, so they are drawn across cores by default.
Pass cores = 1 to force a single core, which is also what
happens on Windows.
| argument | effect |
|---|---|
top_n |
how many bars are visible at once |
duration, fps, end_pause |
length in seconds, frame rate, hold on the last frame |
swap |
seconds a bar takes to move into a new rank |
group |
color bars by category and draw a legend |
palette |
a color vector, or one named by entity or category |
breaks |
gridline positions, a function or a fixed vector |
label_value, label_time |
formatters for the bar numbers and the large time label |
images |
pictures to sit at the end of the bars |
timeline, play_button,
card |
turn off the timeline strip, the button, or the card |
width, res |
output size; the whole layout scales with width |
top_n smaller than the field is where a race earns its
keep. Entities outside the visible window wait just below it and slide
in when they qualify.
top10 <- ggrace(
clefts_qci, qci, country, year,
top_n = 10,
duration = 15,
title = "Ten highest scoring countries"
)
race_frame(top10, round(top10$n_frames / 2))By default each entity gets its own color. When entities fall into
categories, pass the column that names them as group: bars
are then colored by category and a legend appears above the axis.
regional <- ggrace(
clefts_qci, qci, country, year,
group = region,
legend_title = "Region",
top_n = 15,
duration = 15,
breaks = scales::breaks_extended(6),
title = "Quality of care for orofacial clefts"
)
race_frame(regional, regional$n_frames)Each entity must belong to exactly one category, or the coloring
would be ambiguous and the function stops. A factor keeps the legend in
the order of its levels; anything else is sorted. The card grows to make
room for the legend and wraps onto further rows when the categories do
not fit across it, so nothing is pushed off the edge. Set
legend = FALSE to keep the coloring and drop the
legend.
images takes a character vector of image file paths
named by entity. Pictures are cropped to a circle and right aligned just
inside the end of the bar. Entities with no image simply get none.
The package bundles a circular flag for every ISO 3166-1 country,
plus Kurdistan, so country races need no extra files.
race_flags() maps names or codes to those paths.
key <- unique(clefts_qci[c("country", "iso")])
flags <- setNames(race_flags(key$iso), key$country)
flagged <- ggrace(
clefts_qci, qci, country, year,
top_n = 15,
duration = 15,
images = flags,
breaks = scales::breaks_extended(6),
title = "Quality of care for orofacial clefts"
)
flaggedThe flags travel with their bars in the widget, as in the GIF and video.
Lookup tries the two letter code, the three letter code, the full country name, then a unique partial match. The names follow the World Bank style, so a few common spellings do not match and the code is the reliable key.
subset(race_flag_codes(), grepl("Korea|Turk", country))
#> code code3 country
#> 117 kp prk Korea, Dem. People's Rep.
#> 118 kr kor Korea, Rep.
#> 208 tc tca Turks and Caicos Islands
#> 216 tm tkm Turkmenistan
#> 219 tr tur TurkiyeAny image works, not only flags: pass paths to logos, portraits or team crests in the same way.
Sofi-Mahmudi A, Shamsoddin E, Khademioore S, Khazaei Y, Vahdati A, Tovani-Palone MR (2025). Global, regional, and national survey on burden and Quality of Care Index (QCI) of orofacial clefts: Global burden of disease systematic analysis 1990-2019. PLOS ONE 20(1): e0317267.