Skip to contents

The same chart rendered with matplotlib defaults versus the package’s accessible theme, palette, and alt text. Toggle WCAG level to see contrast and font thresholds shift; the audit table reports per-criterion status for each version.

#| standalone: true
#| viewerHeight: 720
from shiny import App, ui, render
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.datasets import load_iris
from itables import to_html_datatable
import a11yviz

_iris = load_iris(as_frame=True)
iris = _iris.frame
iris["species"] = _iris.target_names[iris["target"]]
markers = ["o", "s", "^"]

dt_options = dict(
    connected=True,
    buttons=["copy", "csv", "excel", "pdf"],
    pageLength=10,
    scrollX=True,
    autoWidth=True,
    classes="compact stripe hover",
)

app_ui = ui.page_sidebar(
    ui.sidebar(
        ui.input_radio_buttons("level", "WCAG level:",
                               choices=["AA", "AAA"], selected="AA",
                               inline=True),
        width=240,
    ),
    ui.navset_card_underline(
        ui.nav_panel("Baseline",
            ui.output_plot("plot_before", height="320px"),
            ui.h3("Audit", class_="h6 mt-3"),
            ui.output_ui("audit_before"),
        ),
        ui.nav_panel("Improved",
            ui.output_plot("plot_after", height="320px"),
            ui.h3("Audit", class_="h6 mt-3"),
            ui.output_ui("audit_after"),
        ),
    ),
)

def server(input, output, session):
    def make_baseline():
        fig, ax = plt.subplots(figsize=(7, 4))
        for sp, sub in iris.groupby("species"):
            ax.scatter(sub["sepal length (cm)"], sub["sepal width (cm)"],
                       label=sp)
        ax.set_title("Iris (default matplotlib)")
        ax.legend()
        return fig

    def make_improved():
        with a11yviz.theme(level=input.level()):
            fig, ax = plt.subplots(figsize=(7, 4))
            cols = a11yviz.palette("dark2_8")
            for i, (sp, sub) in enumerate(iris.groupby("species")):
                ax.scatter(sub["sepal length (cm)"], sub["sepal width (cm)"],
                           label=sp, color=cols[i], marker=markers[i])
            ax.set_xlabel("Sepal length (cm)")
            ax.set_ylabel("Sepal width (cm)")
            ax.set_title("Iris (theme + dark2_8 palette)")
            ax.legend(title="Species")
        return a11yviz.alt_text(fig,
            "Iris sepal length vs width by species, AA accessible.")

    @render.plot
    def plot_before():
        return make_baseline()

    @render.plot
    def plot_after():
        return make_improved()

    @render.ui
    def audit_before():
        df = pd.DataFrame(a11yviz.audit(make_baseline(), level=input.level()))
        return ui.HTML(to_html_datatable(df, table_id="audit-baseline", **dt_options))

    @render.ui
    def audit_after():
        df = pd.DataFrame(a11yviz.audit(make_improved(), level=input.level()))
        return ui.HTML(to_html_datatable(df, table_id="audit-improved", **dt_options))

app = App(app_ui, server)