Make matplotlib, seaborn, Plotly, Bokeh, plotnine, and Altair charts accessible — MAIDR (Multimodal Access and Interactive Data Representation) for Python.
py-maidr makes data visualizations accessible to blind and low-vision readers.
import maidr and your existing plotting code renders an interactive figure that
can be navigated by keyboard, heard as sonification, read on a braille
display, and described as text — the same chart in four modalities instead
of one.
Nothing about how you plot changes. py-maidr patches matplotlib and seaborn at
import time, so plt.show() produces accessible HTML instead of a static image;
Plotly, Bokeh, plotnine and Altair figures go through the same maidr.show()
entry point (Bokeh and plotnine support are experimental; install them with
pip install "maidr[bokeh]" and pip install "maidr[plotnine]"), and so do the
charts of an Excel workbook, read without Excel by
maidr.read_excel_charts("book.xlsx") (experimental), the charts of a
PowerPoint presentation or a Word document, read by
maidr.read_powerpoint_charts("deck.pptx") and
maidr.read_word_charts("report.docx") (experimental), and the training
curves, histograms and Keras model graph of a TensorBoard log directory, read without TensorFlow
by maidr.read_tensorboard_scalars("logs"),
maidr.read_tensorboard_distributions("logs"),
maidr.read_tensorboard_histograms("logs"),
maidr.read_tensorboard_hparams("logs"),
maidr.read_tensorboard_pr_curves("logs"),
maidr.read_tensorboard_profile("logs"),
maidr.read_tensorboard_projector("logs") and
maidr.read_tensorboard_graph("logs"), or in a maidr tab of
TensorBoard itself with pip install "maidr[tensorboard]" (experimental). A Keras model's
training curves are drawn by maidr.keras.plot_history(history), or kept up
to date while it trains by maidr.keras.MaidrCallback, and its layers by
maidr.keras.plot_model(model) (experimental). The
training curves of Weights & Biases runs are read by
maidr.read_wandb_history("wandb"), from the runs' own files with no account
or network, and those of MLflow runs by maidr.read_mlflow_metrics(runs),
which maidr.log_mlflow_chart() can store back in a run for the MLflow UI to
show (both experimental). It works in a plain script, a Jupyter notebook,
Quarto, Shiny, Streamlit, and Gradio.
import matplotlib.pyplot as plt
import seaborn as sns
import maidr # the whole integration
penguins = sns.load_dataset("penguins")
fig, ax = plt.subplots()
sns.barplot(x="species", y="body_mass_g", data=penguins, ax=ax)
maidr.show(fig)maidr.save_html(fig, "penguins.html") writes the same plot to a standalone HTML
file instead of displaying it. Because importing maidr also activates its
matplotlib backend, a plain plt.show() renders accessible output too.
| Plotting libraries | matplotlib, seaborn (including seaborn.objects), Plotly, Bokeh [experimental], plotnine [experimental], Altair — plus mplfinance for financial charts (candlestick, volume, moving averages) |
| Files | the charts of an Excel workbook (.xlsx, .xlsm) [experimental]; the charts of a PowerPoint presentation (.pptx) or a Word document (.docx) [experimental]; the training curves, histograms, PR curves, hyperparameter sweeps, embeddings, profiles and the Keras model graph of a TensorBoard log directory (Keras, TensorFlow, PyTorch) [experimental] |
| Model training | the training curves of a Keras model, from model.fit or live while it trains, a classifier's confusion matrix and PR curve, and the model's layer graph [experimental]; the training curves of Weights & Biases runs, online or offline, and of MLflow runs, and an accessible chart stored in an MLflow run [experimental] |
| Environments | Python scripts, Jupyter, Quarto, Shiny for Python, Streamlit, Gradio |
| Plot types | 44 — see Plot Type Stability for which fifteen are settled and which twenty-nine are experimental; the docs mark each experimental type [experimental] after its name |
Plot types not yet supported fall back to a static image with a warning, so a plot is never lost.
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.
# install the latest release from PyPI
pip install -U maidr# or install the development version from GitHub
pip install -U git+https://github.com/xability/py-maidr.gitPlease visit the user guide page.
py-maidr loads its JavaScript from a CDN by default, and resolves the current published version so browsers cannot serve a stale cached copy. That costs one bounded outbound request, the first time a plot is rendered. import maidr itself makes no request.
If you work air-gapped, behind a proxy, or in CI:
export MAIDR_CDN_VERSION=bundled # serve the version in this wheel, no lookup
export MAIDR_USE_CDN=false # or skip the CDN entirelybundled is usually the best choice for restricted networks: it emits an immutable
CDN URL — so browser caching still works correctly — without contacting anything.
It is also the only one of the two that covers Altair charts: the Altair adapter
has no offline path and always references the CDN, so MAIDR_USE_CDN=false does not
apply to it, while MAIDR_CDN_VERSION=bundled still removes the lookup.
A DotPad tactile display needs the
vendor's SDK, which maidr.js fetches from jsDelivr the first time a device is
connected rather than bundling its 14 MB braille engine. To take that offline too,
download the pinned copy once and every use_cdn=False document carries it in
its lib/ folder:
maidr.download_dotpad_sdk() # once; ~14 MB into a per-user cache
maidr.save_html(fig, "chart.html", use_cdn=False)The bundled maidr.js does not carry its non-English locale packs. A document that
runs it points it at the packs of the same version on jsDelivr, so a reader whose
browser asks for, say, Korean fetches that one pack when online and hears English
when not. For strictly no network, and English only, turn that off:
export MAIDR_LOCALE_BASE_URL="" # or maidr.set_locale_base_url(False)See Offline Use and the JavaScript Bundle for the full set of options.
We provide some example code for using py-maidr with matplotlib, seaborn, Jupyter Notebook, Quarto, Shiny, and Streamlit.
Shiny support requires the optional extra pip install "maidr[shiny]", which
provides output_maidr() and @render_maidr in maidr.widget.shiny.
Streamlit support requires pip install "maidr[streamlit]", which provides
render_maidr() and maidr_html() in maidr.widget.streamlit.
If an AI coding agent (Claude Code, Codex, Cursor, GitHub Copilot, and others) writes your plotting code, install the
maidr skill with npx skills add xability/maidr-skill. The agent then
routes every matplotlib, seaborn, Plotly, Bokeh, or Altair chart through py-maidr, verifies the output, and hands back an
accessible HTML file or chat artifact instead of a static image.
Bug reports, plot types and documentation fixes are all welcome — see CONTRIBUTING.md for how to set up a development environment and shape a change, and CONDUCT.md for our code of conduct.
GPL-3.0-or-later. See LICENSE.