Introduction to MAIDR
MAIDR (Multimodal Access and Interactive Data Representation) is an R package that makes data visualizations accessible to users with visual impairments. It converts ggplot2 and Base R plots into interactive, accessible formats with:
- Keyboard navigation - Explore data using arrow keys
- Screen reader support - Full ARIA labels and descriptions
- Sonification - Hear data patterns through sound
- HTML/SVG output - Standalone accessible visualizations
MAIDR helps data scientists and researchers create inclusive visualizations that everyone can explore, regardless of visual ability.
Installation
Install the development version from GitHub:
# Install the released version from CRAN
install.packages("maidr")
# Or the development version from GitHub:
# install.packages("devtools")
devtools::install_github("xability/r-maidr")Basic Workflow
MAIDR works with two main functions:
-
show()- Display an interactive plot in RStudio Viewer or browser -
save_html()- Save a plot as a standalone HTML file
Quick Example: ggplot2 Bar Chart
library(maidr)
library(ggplot2)
# Create sample data
sales_data <- data.frame(
Product = c("A", "B", "C", "D"),
Sales = c(150, 230, 180, 290)
)
# Create a bar chart
p <- ggplot(sales_data, aes(x = Product, y = Sales)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(
title = "Product Sales by Category",
x = "Product",
y = "Sales Amount"
) +
theme_minimal()
# Display interactively
show(p)
# Or save as HTML file
save_html(p, "sales_chart.html")Quick Example: Base R Plot
MAIDR also works with Base R plotting functions:
library(maidr)
# Create a simple barplot
categories <- c("A", "B", "C", "D")
values <- c(150, 230, 180, 290)
barplot(
values,
names.arg = categories,
col = "steelblue",
main = "Product Sales by Category",
xlab = "Product",
ylab = "Sales Amount"
)
# Note: For Base R plots, call show() with NO arguments
# after creating the plot
show()Offline vs CDN Usage
By default, show() and save_html() use the
bundled MAIDR.js library, so the result works offline; widgets, knitr
documents and Shiny apps auto-detect internet availability and use the
CDN when online. You can control this behavior with the
use_cdn parameter:
library(maidr)
library(ggplot2)
p <- ggplot(mtcars, aes(x = factor(cyl), y = mpg)) +
geom_bar(stat = "identity")
# Default - bundled files, works offline
show(p)
# Force CDN (requires internet when viewing)
show(p, use_cdn = TRUE)
# Force bundled/local files (works offline)
show(p, use_cdn = FALSE)The same parameter works with save_html():
# Save with CDN links (smaller file, needs internet to view)
save_html(p, "plot_cdn.html", use_cdn = TRUE)
# Save with bundled files (larger file, works offline)
save_html(p, "plot_offline.html", use_cdn = FALSE)When to use use_cdn = FALSE: - Creating
portable HTML files for offline viewing - Sharing files with users who
may not have internet access - Ensuring reproducibility with a specific
MAIDR.js version
The DotPad SDK
One thing an offline document still fetches: the SDK for the DotPad tactile display. maidr.js does not bundle it (its licence does not permit redistribution) and imports the vendor’s copy from jsDelivr the first time a reader connects a DotPad. The document renders, sonifies and brailles without the network; only that first connect needs it.
To keep the DotPad offline as well, serve the SDK yourself and tell maidr where it is before rendering. Options and environment variables of the same names both work; an option wins when both are set:
options(
maidr.dotpad_sdk_url = "https://intranet.example/dotpad/DotPadSDK-3.0.3.js",
# Only if the braille engine (liblouis) is not in lib/ beside the module
maidr.dotpad_asset_base_url = "https://intranet.example/dotpad/lib/"
)
save_html(p, "plot_offline.html", use_cdn = FALSE)Every document maidr produces (show(),
save_html(), widgets, knitr and Shiny) then declares
window.MAIDR_DOTPAD_SDK_URL and
window.MAIDR_DOTPAD_ASSET_BASE_URL ahead of maidr.js, and
the CDN is never asked for the SDK. See ?"maidr-options"
for the details.
Exploring Accessible Plots
When you open a MAIDR plot, you can explore it using:
Keyboard Navigation
- Arrow keys - Navigate between data points
- Tab - Move between interactive elements
- Enter/Space - Activate controls
- Escape - Exit modes
Quarto reveal.js Slides
A revealjs deck needs nothing special from this package:
call maidr_on() once in a setup chunk, as in any other
Quarto or R Markdown document, and every plot the deck draws becomes an
accessible MAIDR chart.
A chart on a revealjs slide is keyboard reachable on its
own: Tab moves into it, the arrow keys explore it, and
Shift+Tab hands focus back to the slide, so
Space advances the deck again. None of that needs
configuring.
What does need attention is a reveal.js behavior that has nothing to
do with MAIDR. reveal.js keeps the slides on either side of the current
one rendered so that transitions stay smooth, and marking them
hidden does not take them out of the tab order — reveal’s
own inline style overrides the attribute. On a deck with a chart on
every slide, a single Tab can therefore land on an
off-screen slide’s chart rather than the one in front of the reader.
This is hakimel/reveal.js#1587,
open since 2016.
The fix is now on reveal.js master, which marks every
slide but the current one inert. It has not reached a
published release yet, and Quarto carries its own copy of reveal.js —
Quarto 1.10 ships 5.1.0 — so it will arrive in a Quarto release some
time after reveal.js cuts one. Nothing will need to change in your deck
when it does.
Until then, quarto-revealjs-a11y does the same thing for a Quarto deck. Add it once per project:
and enable it in the deck’s front matter:
Use 0.2.3 or newer. Earlier versions took off-slide
elements out of the tab order by setting tabindex="-1" on
them and could not find them again to put them back, which left the
chart on the current slide unreachable as well.
With the extension enabled, each slide gives one Tab to its own chart and Shift+Tab back out. The extension also adds a skip link ahead of the slides, so Shift+Tab lands there rather than on the slide element itself; either way Space still moves to the next slide.
Supported Plot Types
MAIDR supports a comprehensive range of visualizations:
Basic Plot Types
- Bar charts (simple, grouped/dodged, stacked)
- Pie charts — ggplot2 via
geom_col()/geom_bar()+coord_polar("y"); Base R viapie() - Histograms
- Scatter plots
- Line plots (single and multi-line)
- Step plots —
geom_step()in ggplot2,plot(type = "s")/plot(type = "S")in Base R — for values that are piecewise constant, such as a sleep-stage hypnogram - Box plots
- Violin plots (ggplot2 only)
- Candlestick (OHLC) charts — ggplot2 via {tidyquant} (with optional
geom_ma()moving-average overlays and a patchwork volume sub-panel); Base R viaquantmod::chartSeries()(OHLC-only — TA overlays such asaddVo(),addSMA(),addEMA()are not supported and fall back to native graphics) - Heatmaps
- Density/smooth curves
See the Heat Map and Candlestick Examples article for the full candlestick + MA + volume pipeline and the Base R support matrix.
Advanced Plot Types
-
Faceted plots -
facet_wrap()andfacet_grid()in ggplot2 -
Multi-panel layouts - patchwork for ggplot2,
par(mfrow/mfcol)for Base R - Multi-layered plots - Combine multiple geoms (e.g., histogram + density)
Next Steps
- Shiny Integration - Use MAIDR in Shiny apps
-
Package documentation - Run
?maidr::showfor function details -
Run examples - Try
maidr::run_example()to see all available plot types
Example Gallery
Histogram
library(maidr)
library(ggplot2)
# Normal distribution
hist_data <- data.frame(values = rnorm(1000, mean = 100, sd = 15))
p <- ggplot(hist_data, aes(x = values)) +
geom_histogram(bins = 30, fill = "skyblue", color = "black") +
labs(
title = "Distribution of Test Scores",
x = "Score",
y = "Frequency"
) +
theme_minimal()
show(p)Scatter Plot
library(maidr)
library(ggplot2)
# Create sample data
scatter_data <- data.frame(
height = rnorm(50, 170, 10),
weight = rnorm(50, 70, 8),
gender = sample(c("Male", "Female"), 50, replace = TRUE)
)
p <- ggplot(scatter_data, aes(x = height, y = weight, color = gender)) +
geom_point(size = 3, alpha = 0.7) +
labs(
title = "Height vs Weight",
x = "Height (cm)",
y = "Weight (kg)"
) +
theme_minimal()
show(p)Line Plot
library(maidr)
library(ggplot2)
# Time series data
months <- month.abb[1:12]
temperature <- c(5, 7, 12, 18, 22, 26, 28, 27, 23, 17, 11, 6)
temp_data <- data.frame(
Month = factor(months, levels = months),
Temperature = temperature
)
p <- ggplot(temp_data, aes(x = Month, y = Temperature, group = 1)) +
geom_line(color = "red", linewidth = 1.5) +
geom_point(color = "darkred", size = 3) +
labs(
title = "Average Monthly Temperature",
x = "Month",
y = "Temperature (°C)"
) +
theme_minimal()
show(p)Tips for Creating Accessible Plots
- Use clear titles - Describe what the plot shows
- Label axes properly - Include units of measurement
- Choose distinct colors - Ensure good contrast
- Add legends - Explain what colors/shapes mean
- Keep it simple - Avoid overcrowded visualizations
Getting Help
- Run
?maidr::showfor function documentation - Visit GitHub issues: maidr/issues
- Read the full documentation:
help(package = "maidr")
Learn More
- Accessibility standards: WCAG 2.1 Guidelines
- MAIDR website: More examples and tutorials
- Research papers: Understanding multimodal data representation