--- title: "Bar chart races" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Bar chart races} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", dev = "ragg_png", dpi = 100, fig.width = 7.36, out.width = "100%" ) # Frames have a fixed aspect ratio, so each figure is sized from the race it # draws rather than left to stretch. inches <- function(x) unname(ggextreme::race_size(x)[["height"]]) / 100 ``` 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. ```{r setup} library(ggextreme) ``` ## The data `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. ```{r} head(clefts_qci) ``` `clefts_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. ```{r} 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" ) race ``` Printed, 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. ```{r} head(race$frames) ``` ## Looking at one frame Every frame is an ordinary `ggplot` object, so it can be printed, saved with `ggplot2::ggsave()`, or inspected before committing to a full render. ```{r first-frame, fig.height = inches(race)} race_frame(race, 1) ``` ```{r last-frame, fig.height = inches(race)} race_frame(race, race$n_frames) ``` 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. ## Writing the animation `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. ```{r, eval = FALSE} animate_race(race, "clefts.mp4") animate_race(race, "clefts.gif") ``` 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. ## Arguments worth knowing | 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. ```{r top-ten, fig.height = 4.6} 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)) ``` ## Coloring by group 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. ```{r groups, fig.height = 5.73} 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 on the bars `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. ```{r flags, eval = requireNamespace("magick", quietly = TRUE) && requireNamespace("rsvg", quietly = TRUE)} 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" ) flagged ``` The 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. ```{r} subset(race_flag_codes(), grepl("Korea|Turk", country)) ``` Any image works, not only flags: pass paths to logos, portraits or team crests in the same way. ## References 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.