Getting started with ggaltdata

Accessibility rules for documents (WCAG 2.1, success criterion 1.1.1) ask for a text alternative for every chart that carries information. A useful alternative text says what the chart shows, not how it is drawn. ggaltdata writes it from the data of the chart itself: every number in the text is a number that is drawn.

A first chart

scores <- data.frame(
  item = rep(c("Apples", "Pears", "Plums", "Figs"), 2),
  area = rep(c("North", "South"), each = 4),
  score = c(2.80, 3.05, 3.35, 2.95, 3.35, 3.30, 3.50, 3.25)
)
p <- ggplot(scores, aes(item, score, fill = area)) +
  geom_col(position = "dodge") +
  labs(title = "Ratings in two markets", x = "products", y = "average score")
p

South is higher than North on all 4 products; the widest gap is Apples (+0.55).

The short alternative text, one or two sentences within 250 characters:

alt_text(p)
#> [1] "South is higher than North on all 4 products; the widest gap is Apples (+0.55)."

In Italian:

alt_text(p, lang = "it")
#> [1] "South è più alto di North in tutte le 4 voci; la distanza maggiore è in Apples (+0,55)."

The long description and the data table, written for a screen reader in the console:

alt_describe(p)
#> Ratings in two markets. Bar chart of average score by products, for each area
#> (North and South). South is higher than North on all 4 products; the widest gap
#> is Apples (+0.55). Values are read from the data of the chart.
#> 
#>  products  area average score
#>    Apples North          2.80
#>     Pears North          3.05
#>     Plums North          3.35
#>      Figs North          2.95
#>    Apples South          3.35
#>     Pears South          3.30
#>     Plums South          3.50
#>      Figs South          3.25

What the text says, chart by chart

Chart Findings
bars and dots, one series highest and lowest, ranking
bars or dots in two groups which group is higher, on how many categories, widest gap
stacked bars highest and lowest total, largest part
differences around zero (lollipop) how many values are above or below zero, largest values
estimates with intervals highest and lowest estimate with their intervals, whether the intervals overlap, how many exclude a reference line
lines over time start and end values, direction, peak, which series ends higher
maps (geom_sf) areas with the highest and lowest values, median

Other chart types get a description of their structure and a warning of class ggaltdata_unsupported; nothing stops a report from rendering.

Where the text goes

R Markdown: set the chunk option fig.alt = alt_text(p), as in the first chunk of this vignette.

Quarto: in the chunk, #| fig-alt: !expr alt_text(p).

The plot object: add_alt() stores the text with labs(alt = ), so that ggplot2::get_alt_text() and Shiny find it. A text written by hand is kept unless overwrite = TRUE.

p2 <- add_alt(p)
ggplot2::get_alt_text(p2)
#> [1] "South is higher than North on all 4 products; the widest gap is Apples (+0.55)."

Shiny: renderPlot() has an alt argument (Shiny 1.6.0 and later).

output$plot <- renderPlot(p, alt = alt_text(p))

Saved images: save_alt() writes the text and the data next to the image.

ggsave("figure1.png", p)
save_alt(p, "figure1.png")   # figure1.alt.txt and figure1.data.csv

Word, with officer: the data table after the chart.

doc <- officer::read_docx()
doc <- as_docx_table(p, doc, caption = "Data of Figure 1")
print(doc, target = "report.docx")

Markdown table of the data, for an appendix:

cat(as_markdown(p, caption = "Data of the chart"))

Data of the chart

products area average score
Apples North 2.80
Pears North 3.05
Plums North 3.35
Figs North 2.95
Apples South 3.35
Pears South 3.30
Plums South 3.50
Figs South 3.25

Intervals and a reference line

towns <- data.frame(
  town = c("Alder", "Birch", "Cedar", "Maple"),
  est = c(4.05, 3.90, 3.31, 3.82), lo = c(3.93, 3.77, 3.15, 3.70), hi = c(4.17, 4.03, 3.47, 3.94)
)
q <- ggplot(towns, aes(est, town)) +
  geom_pointrange(aes(xmin = lo, xmax = hi)) +
  geom_vline(xintercept = 3.85, linetype = 2) +
  labs(x = "satisfaction index")
q

Alder has the highest estimate (4.05; 3.93 to 4.17) and Cedar the lowest (3.31; 3.15 to 3.47); the two intervals do not overlap. 2 of 4 intervals exclude the reference value 3.85 (1 above, 1 below).

alt_text(q, max_chars = 400)
#> [1] "Alder has the highest estimate (4.05; 3.93 to 4.17) and Cedar the lowest (3.31; 3.15 to 3.47); the two intervals do not overlap. 2 of 4 intervals exclude the reference value 3.85 (1 above, 1 below)."

What the package does not do

It does not judge which finding matters most to your argument: it states the facts that the data support, and the author decides what to keep. It does not use a language model, so it cannot invent a number, and it does not describe charts it does not recognize.