Skip to content

Commit

Permalink
feat: Add 'olli' renderer to generate accessible text structures for …
Browse files Browse the repository at this point in the history
…screen reader users (#3580)
  • Loading branch information
binste committed Sep 9, 2024
1 parent 682b169 commit 872c83c
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 3 deletions.
45 changes: 42 additions & 3 deletions altair/utils/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from altair.utils._importers import import_vl_convert, vl_version_for_vl_convert

TemplateName = Literal["standard", "universal", "inline"]
TemplateName = Literal["standard", "universal", "inline", "olli"]
RenderMode = Literal["vega", "vega-lite"]

HTML_TEMPLATE = jinja2.Template(
Expand Down Expand Up @@ -116,11 +116,22 @@
if (outputDiv.id !== "{{ output_div }}") {
outputDiv = document.getElementById("{{ output_div }}");
}
{%- if use_olli %}
const olliDiv = document.createElement("div");
const vegaDiv = document.createElement("div");
outputDiv.appendChild(vegaDiv);
outputDiv.appendChild(olliDiv);
outputDiv = vegaDiv;
{%- endif %}
const paths = {
"vega": "{{ base_url }}/vega@{{ vega_version }}?noext",
"vega-lib": "{{ base_url }}/vega-lib?noext",
"vega-lite": "{{ base_url }}/vega-lite@{{ vegalite_version }}?noext",
"vega-embed": "{{ base_url }}/vega-embed@{{ vegaembed_version }}?noext",
{%- if use_olli %}
"olli": "{{ base_url }}/olli@{{ olli_version }}?noext",
"olli-adapters": "{{ base_url }}/olli-adapters@{{ olli_adapters_version }}?noext",
{%- endif %}
};
function maybeLoadScript(lib, version) {
Expand All @@ -145,20 +156,41 @@
throw err;
}
function displayChart(vegaEmbed) {
function displayChart(vegaEmbed, olli, olliAdapters) {
vegaEmbed(outputDiv, spec, embedOpt)
.catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));
{%- if use_olli %}
olliAdapters.VegaLiteAdapter(spec).then(olliVisSpec => {
// It's a function if it was loaded via maybeLoadScript below.
// If it comes from require, it's a module and we access olli.olli
const olliFunc = typeof olli === 'function' ? olli : olli.olli;
const olliRender = olliFunc(olliVisSpec);
olliDiv.append(olliRender);
});
{%- endif %}
}
if(typeof define === "function" && define.amd) {
requirejs.config({paths});
require(["vega-embed"], displayChart, err => showError(`Error loading script: ${err.message}`));
let deps = ["vega-embed"];
{%- if use_olli %}
deps.push("olli", "olli-adapters");
{%- endif %}
require(deps, displayChart, err => showError(`Error loading script: ${err.message}`));
} else {
maybeLoadScript("vega", "{{vega_version}}")
.then(() => maybeLoadScript("vega-lite", "{{vegalite_version}}"))
.then(() => maybeLoadScript("vega-embed", "{{vegaembed_version}}"))
{%- if use_olli %}
.then(() => maybeLoadScript("olli", "{{olli_version}}"))
.then(() => maybeLoadScript("olli-adapters", "{{olli_adapters_version}}"))
{%- endif %}
.catch(showError)
{%- if use_olli %}
.then(() => displayChart(vegaEmbed, olli, OlliAdapters));
{%- else %}
.then(() => displayChart(vegaEmbed));
{%- endif %}
}
})({{ spec }}, {{ embed_options }});
</script>
Expand Down Expand Up @@ -209,6 +241,7 @@
"standard": HTML_TEMPLATE,
"universal": HTML_TEMPLATE_UNIVERSAL,
"inline": INLINE_HTML_TEMPLATE,
"olli": HTML_TEMPLATE_UNIVERSAL,
}


Expand Down Expand Up @@ -293,6 +326,12 @@ def spec_to_html(
vlc = import_vl_convert()
vl_version = vl_version_for_vl_convert()
render_kwargs["vegaembed_script"] = vlc.javascript_bundle(vl_version=vl_version)
elif template == "olli":
OLLI_VERSION = "2"
OLLI_ADAPTERS_VERSION = "2"
render_kwargs["olli_version"] = OLLI_VERSION
render_kwargs["olli_adapters_version"] = OLLI_ADAPTERS_VERSION
render_kwargs["use_olli"] = True

jinja_template = TEMPLATES.get(template, template) # type: ignore[arg-type]
if not hasattr(jinja_template, "render"):
Expand Down
10 changes: 10 additions & 0 deletions altair/vegalite/v5/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ def browser_renderer(
vegalite_version=VEGALITE_VERSION,
)


olli_renderer = HTMLRenderer(
mode="vega-lite",
template="olli",
vega_version=VEGA_VERSION,
vegaembed_version=VEGAEMBED_VERSION,
vegalite_version=VEGALITE_VERSION,
)

renderers.register("default", html_renderer)
renderers.register("html", html_renderer)
renderers.register("colab", html_renderer)
Expand All @@ -155,6 +164,7 @@ def browser_renderer(
renderers.register("svg", svg_renderer)
renderers.register("jupyter", jupyter_renderer)
renderers.register("browser", browser_renderer)
renderers.register("olli", olli_renderer)
renderers.enable("default")


Expand Down
2 changes: 2 additions & 0 deletions doc/user_guide/display_frontends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ In addition, Altair includes the following renderers:
using the ``"image/png"`` MIME type.
- ``"svg"``: renderer that renders and converts the chart to an SVG image,
outputting it using the ``"image/svg+xml"`` MIME type.
- ``"olli"``: renderer that uses `Olli`_ to generate accessible text structures for screen reader users.
- ``"json"``: renderer that outputs the raw JSON chart specification, using the
``"application/json"`` MIME type.

Expand Down Expand Up @@ -713,3 +714,4 @@ see :ref:`display-general`.
.. _Spyder: https://www.spyder-ide.org/
.. _IPython QtConsole: https://qtconsole.readthedocs.io/en/stable/
.. _webbrowser module: https://docs.python.org/3/library/webbrowser.html#webbrowser.register
.. _Olli: https://mitvis.github.io/olli/

0 comments on commit 872c83c

Please sign in to comment.