diff --git a/altair/utils/html.py b/altair/utils/html.py
index 7b5f4dcf5..ea5269034 100644
--- a/altair/utils/html.py
+++ b/altair/utils/html.py
@@ -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(
@@ -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) {
@@ -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}
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 }});
@@ -209,6 +241,7 @@
"standard": HTML_TEMPLATE,
"universal": HTML_TEMPLATE_UNIVERSAL,
"inline": INLINE_HTML_TEMPLATE,
+ "olli": HTML_TEMPLATE_UNIVERSAL,
}
@@ -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"):
diff --git a/altair/vegalite/v5/display.py b/altair/vegalite/v5/display.py
index aead52a4d..8e6cb39bf 100644
--- a/altair/vegalite/v5/display.py
+++ b/altair/vegalite/v5/display.py
@@ -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)
@@ -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")
diff --git a/doc/user_guide/display_frontends.rst b/doc/user_guide/display_frontends.rst
index b8ecbe67e..12e224f04 100644
--- a/doc/user_guide/display_frontends.rst
+++ b/doc/user_guide/display_frontends.rst
@@ -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.
@@ -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/