# maidr ## Overview maidr (Multimodal Access and Interactive Data Representation) makes data visualizations accessible to users with visual impairments. It converts ggplot2 and Base R plots into interactive, accessible HTML/SVG formats with keyboard navigation, screen reader support, and sonification. maidr for R is the R binding of [MAIDR](https://maidr.ai/), the JavaScript core developed by the (x)Ability Design Lab at the University of Illinois Urbana-Champaign; the same accessibility layer is available for Python as [py-maidr](https://py.maidr.ai/). The package provides two main functions: - [`show()`](https://r.maidr.ai/reference/show.md) displays an interactive accessible plot in RStudio Viewer or browser - [`save_html()`](https://r.maidr.ai/reference/save_html.md) exports a plot as a standalone HTML file ## Installation maidr requires R 4.0.0 or later. Install the stable release from CRAN: ``` r install.packages("maidr") ``` Or install the development version from GitHub: ``` r # Using pak (recommended) pak::pak("xability/r-maidr") # Alternative: using pacman (auto-installs if missing) pacman::p_load_gh("xability/r-maidr") ``` ## Usage ### ggplot2 ``` r library(maidr) library(ggplot2) p <- ggplot(mpg, aes(x = class)) + geom_bar(fill = "steelblue") + labs(title = "Vehicle Classes", x = "Class", y = "Count") # Display interactive accessible plot show(p) # Or save to file save_html(p, "vehicle_classes.html") ``` ### Base R ``` r library(maidr) # Create plot first barplot( table(mtcars$cyl), main = "Cars by Cylinder Count", xlab = "Cylinders", ylab = "Count" ) # Then call show() without arguments show() ``` ## Supported plot types maidr supports a wide range of visualization types in both ggplot2 and Base R: ### Basic Plot Types | Plot Type | ggplot2 | Base R | |----|----|----| | Bar charts | [`geom_bar()`](https://ggplot2.tidyverse.org/reference/geom_bar.html), [`geom_col()`](https://ggplot2.tidyverse.org/reference/geom_bar.html) | [`barplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Grouped/Dodged bars | `position = "dodge"` | `beside = TRUE` | | Stacked bars | `position = "stack"` | `beside = FALSE` | | Pie charts | [`geom_col()`](https://ggplot2.tidyverse.org/reference/geom_bar.html)/[`geom_bar()`](https://ggplot2.tidyverse.org/reference/geom_bar.html) + `coord_polar("y")` | [`pie()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Histograms | [`geom_histogram()`](https://ggplot2.tidyverse.org/reference/geom_histogram.html) | [`hist()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Scatter plots | [`geom_point()`](https://ggplot2.tidyverse.org/reference/geom_point.html) | [`plot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Line plots | [`geom_line()`](https://ggplot2.tidyverse.org/reference/geom_path.html) | `plot(type = "l")`, [`lines()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Step plots | [`geom_step()`](https://ggplot2.tidyverse.org/reference/geom_path.html) | `plot(type = "s")`, `plot(type = "S")` | | Box plots | [`geom_boxplot()`](https://ggplot2.tidyverse.org/reference/geom_boxplot.html) | [`boxplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Heatmaps | [`geom_tile()`](https://ggplot2.tidyverse.org/reference/geom_tile.html) | [`image()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Contour plots | — (see below) | [`contour()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | Violin plots | [`geom_violin()`](https://ggplot2.tidyverse.org/reference/geom_violin.html) | — | | Candlestick (OHLC) | [`tidyquant::geom_candlestick()`](https://business-science.github.io/tidyquant/reference/geom_chart.html) (+ `geom_ma()`, + patchwork volume) | [`quantmod::chartSeries()`](https://rdrr.io/pkg/quantmod/man/chartSeries.html) (OHLC-only; no TA / no volume) | | Density/Smooth | [`geom_smooth()`](https://ggplot2.tidyverse.org/reference/geom_smooth.html), [`geom_density()`](https://ggplot2.tidyverse.org/reference/geom_density.html) | `lines(density())` | Note: Volume bars and moving-average overlays for candlestick charts are supported only on the ggplot2 + {tidyquant} + {patchwork} path. On the Base R path, [`quantmod::chartSeries()`](https://rdrr.io/pkg/quantmod/man/chartSeries.html) `TA` overlays (`addVo()`, `addSMA()`, `addEMA()`) — and the default `TA` whenever the input `xts` carries a `Volume` column — fall back to native (non-accessible) graphics with a one-time advisory. ### Advanced Plot Types | Plot Type | ggplot2 | Base R | |----|----|----| | Faceted plots | [`facet_wrap()`](https://ggplot2.tidyverse.org/reference/facet_wrap.html), [`facet_grid()`](https://ggplot2.tidyverse.org/reference/facet_grid.html) | `par(mfrow/mfcol)` + loops | | Multi-panel layouts | `patchwork` package | `par(mfrow)`, `par(mfcol)` | | Multi-layered plots | Multiple `geom_*` layers | Sequential plot calls | ### Experimental Plot Types > \[!WARNING\] **These are prototypes. Treat them as prototypes.** They > are under active development, they are unstable, and **none of them > has been through a user study**. Field names, announcement wording and > navigation may change without a deprecation period, including in a > patch release. If you are building something that has to keep working, > build it on the plot types above. Everything in the two tables above predates the plot coverage roadmap ([\#137](https://github.com/xability/r-maidr/issues/137)) and has been exercised by real readers over real charts. Everything below was added by that roadmap and the base R sweeps that followed it ([\#251](https://github.com/xability/r-maidr/issues/251), [\#262](https://github.com/xability/r-maidr/issues/262)), most inside a few weeks. Each was measured against the chart it reads — that is what the issues and the tests record. But measuring that a reading is *faithful to the drawing* is a different claim from establishing that it is *useful to a reader*. Nobody has asked a blind or low-vision reader whether hearing [`stars()`](https://r.maidr.ai/reference/base-r-wrappers.md) as a radar, or navigating a [`termplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) panel by panel, is the right way to read one. Until that happens these are proposals about how a chart could be read, not answers. Feedback is exactly what would move one of these into the tables above. #### ggplot2 | Layer type | Drawn by | |----|----| | `area` | [`geom_area()`](https://ggplot2.tidyverse.org/reference/geom_ribbon.html), `geom_ribbon(aes(ymin = 0, ...))` | | `stacked_area` | stacked [`geom_area()`](https://ggplot2.tidyverse.org/reference/geom_ribbon.html) | | `stacked_normalized_area` | `geom_area(position = "fill")` | | `stacked_normalized_bar` | `geom_bar(position = "fill")` | | `contour` | [`geom_contour()`](https://ggplot2.tidyverse.org/reference/geom_contour.html), [`geom_density_2d()`](https://ggplot2.tidyverse.org/reference/geom_density_2d.html) | | `error_bar` | [`geom_errorbar()`](https://ggplot2.tidyverse.org/reference/geom_linerange.html), [`geom_errorbarh()`](https://ggplot2.tidyverse.org/reference/geom_linerange.html), [`geom_linerange()`](https://ggplot2.tidyverse.org/reference/geom_linerange.html), [`geom_pointrange()`](https://ggplot2.tidyverse.org/reference/geom_linerange.html), [`geom_crossbar()`](https://ggplot2.tidyverse.org/reference/geom_linerange.html), [`geom_ribbon()`](https://ggplot2.tidyverse.org/reference/geom_ribbon.html) as a band | | `gantt` | [`geom_segment()`](https://ggplot2.tidyverse.org/reference/geom_segment.html), [`geom_curve()`](https://ggplot2.tidyverse.org/reference/geom_segment.html), [`maidr_gantt()`](https://r.maidr.ai/reference/maidr_gantt.md) | | `hexbin` | [`geom_hex()`](https://ggplot2.tidyverse.org/reference/geom_hex.html), [`stat_bin_2d()`](https://ggplot2.tidyverse.org/reference/geom_bin_2d.html) | | `polygon` | [`geom_polygon()`](https://ggplot2.tidyverse.org/reference/geom_polygon.html) | | `roc` | [`maidr_roc()`](https://r.maidr.ai/reference/maidr_roc.md), [`pROC::ggroc()`](https://rdrr.io/pkg/pROC/man/ggroc.html), [`autoplot()`](https://ggplot2.tidyverse.org/reference/autoplot.html) of a [`yardstick::roc_curve()`](https://yardstick.tidymodels.org/reference/roc_curve.html) | | `rug` | [`geom_rug()`](https://ggplot2.tidyverse.org/reference/geom_rug.html) | #### Base R | Layer type | Drawn by | |----|----| | `biplot` | [`biplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `box_stats` | [`bxp()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `conditional_density` | [`cdplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `correlogram` | [`acf()`](https://r.maidr.ai/reference/base-r-wrappers.md), [`pacf()`](https://r.maidr.ai/reference/base-r-wrappers.md), [`ccf()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `cumulative_periodogram` | [`cpgram()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `dot` | [`dotchart()`](https://r.maidr.ai/reference/base-r-wrappers.md) (ungrouped) | | `filled_contour` | [`filled.contour()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `fourfold` | [`fourfoldplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) (2x2 tables, `std = "ind.max"` / `"all.max"`) | | `interaction` | [`interaction.plot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `lag` | [`lag.plot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `lollipop` | `plot(type = "h")` | | `mosaic` | [`mosaicplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) (two-way tables) | | `pairs` | [`pairs()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `qq` | [`qqnorm()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `qqline` | [`qqline()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `radar` | [`stars()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `residual` | [`assocplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) (two-way tables) | | `spectral_density` | [`spectrum()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `spine` | [`spineplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `stacked_normalized_bar` | [`barplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) of proportions | | `strip` | [`stripchart()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `subseries` | [`monthplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `termplot` | [`termplot()`](https://r.maidr.ai/reference/base-r-wrappers.md) | | `violin` | [`vioplot::vioplot()`](https://rdrr.io/pkg/vioplot/man/vioplot.html) | | `word_cloud` | [`wordcloud::wordcloud()`](https://rdrr.io/pkg/wordcloud/man/wordcloud.html) | The split is the diff of each factory’s `get_supported_types()` against `8de0e98`, the last commit on `main` before [\#137](https://github.com/xability/r-maidr/issues/137) was filed. `tests/testthat/test-plot-type-stability.R` fails if a supported type appears in neither the stable tables nor the experimental ones, so a new layer type has to be placed deliberately rather than inherit either promise by being forgotten. The [JavaScript core](https://maidr.ai/) and the [Python binding](https://py.maidr.ai/) make the same distinction over their own type lists, with the same boundary and for the same reason. See the [examples gallery](https://r.maidr.ai/articles/examples.html) for a worked example of each plot type. ## Accessibility features - **Keyboard navigation** - explore data points using arrow keys - **Screen reader support** - full ARIA labels and live announcements - **Sonification** - hear data patterns through sound - **Text descriptions** - automatic statistical summaries Press **Tab** (or click) to focus a rendered plot, move between data points with the **arrow keys**, and toggle **B** braille, **T** text, **S** sonification, and **R** review mode. Four global shortcuts open maidr’s own interfaces: | Action | Windows / Linux | macOS | |----|----|----| | Show or hide the keyboard shortcut help | Ctrl + / | Command + / | | Open the command palette listing every available command | Ctrl + Shift + P | Command + Shift + P | | Open the AI chat (requires your own API key, entered in Settings, or a local Ollama server) | Shift + / (that is, **?**) | Shift + / (**?**) | | Open Settings | Ctrl + , | Command + , | The full list, including autoplay, label announcements, and layer switching, is in the [maidr controls documentation](https://maidr.ai/docs/CONTROLS.html). ## Offline support By default, [`show()`](https://r.maidr.ai/reference/show.md) and [`save_html()`](https://r.maidr.ai/reference/save_html.md) use the bundled maidr.js library, so the result works offline ([`save_html()`](https://r.maidr.ai/reference/save_html.md) writes it to a `lib/` folder beside the file). Widgets, knitr documents and Shiny apps auto-detect internet availability and use the CDN when online. Use the `use_cdn` parameter for explicit control: ``` r # Force CDN (requires internet) show(p, use_cdn = TRUE) # Force bundled files (works offline) show(p, use_cdn = FALSE) save_html(p, "plot.html", use_cdn = FALSE) ``` One path still reaches the network from an offline document: connecting a [DotPad tactile display](https://maidr.ai/docs/TACTILE_DISPLAY.html). maidr.js does not bundle the DotPad SDK, whose braille engine is a 14 MB liblouis build, and imports the vendor’s copy from jsDelivr the first time a DotPad is connected. Rendering, sonification and braille work offline regardless. To keep the DotPad offline too, download the pinned SDK once and every `use_cdn = FALSE` document carries it in its `lib/` folder: ``` r maidr_download_dotpad_sdk() # ~14 MB, once, into a per-user cache save_html(p, "plot.html", use_cdn = FALSE) # lib/dotpad-sdk-/ beside it ``` A page served from elsewhere, or a knitr document (whose charts live in `srcdoc` frames with no base URL), names its copy by URL instead, through options or the environment variables of the same names: ``` r options( maidr.dotpad_sdk_url = "https://intranet.example/dotpad/DotPadSDK-3.0.3.js", maidr.dotpad_asset_base_url = "https://intranet.example/dotpad/lib/" ) # or: Sys.setenv(MAIDR_DOTPAD_SDK_URL = "...", MAIDR_DOTPAD_ASSET_BASE_URL = "...") ``` Every document maidr produces then declares `window.MAIDR_DOTPAD_SDK_URL` and `window.MAIDR_DOTPAD_ASSET_BASE_URL` ahead of maidr.js. See [`?"maidr-options"`](https://r.maidr.ai/reference/maidr-options.md). ## Getting help - Report bugs or request features at [GitHub Issues](https://github.com/xability/r-maidr/issues) - Read the documentation at the [package website](https://r.maidr.ai/) ## Learning more - [`vignette("getting-started", package = "maidr")`](https://r.maidr.ai/articles/getting-started.md) for an introduction - The [examples gallery](https://r.maidr.ai/articles/examples.html) for supported visualizations - [`vignette("shiny-integration", package = "maidr")`](https://r.maidr.ai/articles/shiny-integration.md) for Shiny apps - The [maidr skill](https://github.com/xability/maidr-skill) for AI coding agents (Claude Code, Codex, Cursor, and others): once installed with `npx skills add xability/maidr-skill`, an agent that writes ggplot2 or base R plotting code routes the result through this package so the chart comes out accessible ## Related projects maidr for R is one of three MAIDR packages, all developed by the (x)Ability Design Lab at the University of Illinois Urbana-Champaign: - [MAIDR JavaScript core](https://maidr.ai/), the TypeScript engine (npm package `maidr`) that renders every accessible chart, including the ones this package produces. - [py-maidr for Python](https://py.maidr.ai/), the Python binding for matplotlib, seaborn, Plotly and Altair (PyPI package `maidr`). - [maidr for R](https://r.maidr.ai/), this package, for ggplot2 and Base R graphics (CRAN package `maidr`; source at [xability/r-maidr](https://github.com/xability/r-maidr)). ## Citation If you use maidr in research, please cite the MAIDR papers: - Seo, J., Xia, Y., Lee, B., Mccurry, S., & Yam, Y. J. (2024). MAIDR: Making Statistical Visualizations Accessible with Multimodal Data Representation. In Proceedings of the CHI Conference on Human Factors in Computing Systems (CHI ’24). ACM. - Seo, J., O’Modhrain, S., Xia, Y., Kamath, S., Lee, B., & Coughlan, J. M. (2024). Designing Born-Accessible Courses in Data Science and Visualization: Challenges and Opportunities of a Remote Curriculum Taught by Blind Instructors to Blind Students. In EuroVis 2024 - Education Papers. The Eurographics Association. `citation("maidr")` prints both, plus an entry for the package itself, in text and BibTeX form. # Package index ## Main functions Primary user-facing functions for creating accessible plots - [`show()`](https://r.maidr.ai/reference/show.md) : Display Interactive MAIDR Plot - [`save_html()`](https://r.maidr.ai/reference/save_html.md) : Save Interactive Plot as HTML File ## Declaring what a layer means Functions an author adds to a plot to say what a layer is, where the picture alone cannot say it - [`maidr_gantt()`](https://r.maidr.ai/reference/maidr_gantt.md) : Declare that a rectangle layer draws a schedule - [`maidr_roc()`](https://r.maidr.ai/reference/maidr_roc.md) : Declare that a path layer draws a ROC curve ## RMarkdown integration Functions for enabling accessible plots in RMarkdown documents - [`maidr_on()`](https://r.maidr.ai/reference/maidr_on.md) : Enable MAIDR Plot Interception - [`maidr_off()`](https://r.maidr.ai/reference/maidr_off.md) : Disable MAIDR Plot Interception ## Shiny integration Functions for using maidr in Shiny applications - [`render_maidr()`](https://r.maidr.ai/reference/render_maidr.md) : Render MAIDR Plot in Shiny Server - [`maidr_output()`](https://r.maidr.ai/reference/maidr_output.md) : MAIDR Output Container for Shiny UI ## Configuration and utilities Functions for configuring MAIDR behavior and running examples - [`maidr_set_fallback()`](https://r.maidr.ai/reference/maidr_set_fallback.md) : Configure MAIDR Fallback Behavior - [`maidr_get_fallback()`](https://r.maidr.ai/reference/maidr_get_fallback.md) : Get Current MAIDR Fallback Settings - [`maidr_download_dotpad_sdk()`](https://r.maidr.ai/reference/maidr_download_dotpad_sdk.md) : Download the DotPad SDK for use offline - [`run_example()`](https://r.maidr.ai/reference/run_example.md) : Run MAIDR Example Plots ## Internal utilities Internal functions for package developers - [`combine_facet_layer_data()`](https://r.maidr.ai/reference/combine_facet_layer_data.md) : Combine data from multiple layers in facet processing - [`combine_facet_layer_selectors()`](https://r.maidr.ai/reference/combine_facet_layer_selectors.md) : Combine selectors from multiple layers in facet processing - [`extract_leaf_plot_layout()`](https://r.maidr.ai/reference/extract_leaf_plot_layout.md) : Extract layout from a single leaf ggplot - [`extract_patchwork_leaves()`](https://r.maidr.ai/reference/extract_patchwork_leaves.md) : Recursively extract leaf ggplots in patchwork addition order - [`find_children_by_type()`](https://r.maidr.ai/reference/find_children_by_type.md) : Find children matching a type pattern - [`find_graphics_plot_grob()`](https://r.maidr.ai/reference/find_graphics_plot_grob.md) : Find grob by element type pattern - [`find_patchwork_panels()`](https://r.maidr.ai/reference/find_patchwork_panels.md) : Discover panels via gtable layout rows named '^panel-' or '^panel-- ' Returns a data.frame with panel_index, name, t, l, row, col - [`generate_robust_css_selector()`](https://r.maidr.ai/reference/generate_robust_css_selector.md) : Generate robust CSS selector from grob name - [`generate_robust_selector()`](https://r.maidr.ai/reference/generate_robust_selector.md) : Generate robust selector for any element type - [`get_facet_groups()`](https://r.maidr.ai/reference/get_facet_groups.md) : Get facet group information for a panel - [`map_visual_to_dom_panel()`](https://r.maidr.ai/reference/map_visual_to_dom_panel.md) : Map visual panel position to DOM panel name - [`organize_facet_grid()`](https://r.maidr.ai/reference/organize_facet_grid.md) : Organize subplots into 2D grid structure - [`process_facet_panel()`](https://r.maidr.ai/reference/process_facet_panel.md) : Process a single facet panel - [`process_faceted_plot_data()`](https://r.maidr.ai/reference/process_faceted_plot_data.md) : Process a faceted plot and return organized subplot data - [`process_patchwork_panel()`](https://r.maidr.ai/reference/process_patchwork_panel.md) : Process a single patchwork panel - [`process_patchwork_plot_data()`](https://r.maidr.ai/reference/process_patchwork_plot_data.md) : Process a patchwork plot and return organized subplot data # Articles ### Get started - [Getting Started with MAIDR](https://r.maidr.ai/articles/getting-started.md): Install the maidr R package and make a first ggplot2 or Base R plot accessible with keyboard navigation, screen reader text, braille and sonification using show(), save_html() and maidr_on(). - [Using MAIDR in Shiny Applications](https://r.maidr.ai/articles/shiny-integration.md): Serve accessible, sonified ggplot2 and Base R plots inside Shiny apps with maidr_output() and render_maidr(), including reactive inputs and multiple plots per page. ### Examples by plot family Worked, interactive examples of every supported plot type, split by family so each page stays small enough to crawl and load quickly. - [Examples](https://r.maidr.ai/articles/examples.md): Gallery of accessible ggplot2 and Base R plots made with maidr, organized by plot family: bar and pie, distributions, scatter and line, heat map and candlestick, multi-panel and facet, and Base R time-series and experimental charts. - [Bar, Pie and Word Cloud Examples](https://r.maidr.ai/articles/examples-bar-pie.md): Accessible bar charts (simple, dodged and stacked), pie charts and word clouds in ggplot2 and Base R with maidr: keyboard navigation, screen reader text and sonification for each category. - [Histogram, Density, Box and Violin Examples](https://r.maidr.ai/articles/examples-distribution.md): Accessible histograms, kernel density (KDE) curves and box plots in ggplot2 and Base R, plus ggplot2 violin plots, with maidr: navigate bins, quartiles and density curves by keyboard with sonification. - [Scatter, Line, Step and Regression Examples](https://r.maidr.ai/articles/examples-scatter-line.md): Accessible scatter plots, single and multi-line plots, step plots and regression fits in ggplot2 and Base R with maidr, each point or sample navigable by keyboard and audible through sonification. - [Heat Map and Candlestick Examples](https://r.maidr.ai/articles/examples-heatmap-candlestick.md): Accessible heat maps and candlestick (OHLC) charts in ggplot2 and Base R with maidr: navigate a matrix cell by cell or a price series candle by candle, with open, high, low, close and volume announced. - [Multi-Layered, Multi-Panel and Facet Examples](https://r.maidr.ai/articles/examples-multi.md): Accessible multi-layered plots, patchwork and par(mfrow) multi-panel layouts, and facet_wrap() and facet_grid() facets with maidr: switch layers and subplots by keyboard, each with its own sonification. - [Base R Time-Series and Diagnostic Examples](https://r.maidr.ai/articles/examples-base-r-timeseries.md): Accessible Base R time-series and model diagnostics with maidr: acf() correlograms, spectrum() and cpgram() periodograms, monthplot() seasonal subseries, lag plots, termplot() and Q-Q plots. - [Experimental Base R Chart Examples](https://r.maidr.ai/articles/examples-base-r-experimental.md): Accessible readings of experimental Base R charts with maidr: biplot(), stars() radar charts, mosaicplot(), cdplot(), fourfoldplot(), filled.contour() and more, each mapped onto a navigable, sonified layer type.