diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index b99842372bbe..6bba98028147 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -186,6 +186,21 @@ Miscellaneous Artist.get_in_layout Artist.stale + +Accessibility +------------- + + +.. autosummary:: + :template: autosummary.rst + :toctree: _as_gen + :nosignatures: + + Artist.get_aria + Artist.set_aria + Artist.update_aria + + Functions ========= diff --git a/doc/users/next_whats_new/aria.rst b/doc/users/next_whats_new/aria.rst new file mode 100644 index 000000000000..7dde632d00dd --- /dev/null +++ b/doc/users/next_whats_new/aria.rst @@ -0,0 +1,23 @@ +All ``Arist`` now carry wai-aria data +------------------------------------- + +It is now possible to attach `wai-aria +`__ role +information to any `~matplotlib.artist.Artist`. These roles are the +industry standard for providing accessibility mark up on the web. This +information can be used by downstream applications for providing accessible +descriptions of visualizations. Best practices in the space are still +developing, but by providing a mechanism to store and access this information +we will enable this development. + +There are three methods provided: + +- `~matplotlib.artist.Artist.set_aria` which will completely replace any existing roles. +- `~matplotlib.artist.Artist.update_aria` which will update the current roles in-place. +- `~matplotlib.artist.Artist.get_aria` which will return a copy of the current roles. + +We currently do no validation on either the keys or the values. + + +Matplotlib will use the ``'aria-label'`` role when saving svg output if it is +provided. diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 7967f597d483..84372547f542 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -192,6 +192,7 @@ def __init__(self): self._clippath = None self._clipon = True self._label = '' + self._aria = {} self._picker = None self._rasterized = False self._agg_filter = None @@ -1085,6 +1086,35 @@ def set_in_layout(self, in_layout): """ self._in_layout = in_layout + def get_aria(self): + """Return any ARIA properties assigned to the artist""" + return dict(self._aria) + + def set_aria(self, aria): + """ + Set ARIA properties to the artist. + + A primary use of this method is to attach aria-label to the artist to + provide alt text to figures. + + Parameters + ---------- + aria : dict + + """ + # TODO validation + if not isinstance(aria, dict): + if aria is not None: + raise TypeError( + f'aria must be dict or None, not {type(aria)}') + self._aria = aria + + def update_aria(self, **aria): + """Update WAI-ARIA properties on the artist.""" + # TODO validation + for k, v in aria.items(): + self._aria[k] = v + def get_label(self): """Return the label used for this artist in the legend.""" return self._label diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 03b376a69894..1a5190083e2a 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -317,7 +317,7 @@ def _check_is_iterable_of_str(infos, key): class RendererSVG(RendererBase): def __init__(self, width, height, svgwriter, basename=None, image_dpi=72, - *, metadata=None): + *, metadata=None, aria=None): self.width = width self.height = height self.writer = XMLWriter(svgwriter) @@ -337,6 +337,7 @@ def __init__(self, width, height, svgwriter, basename=None, image_dpi=72, self._hatchd = {} self._has_gouraud = False self._n_gradients = 0 + self._aria = dict(aria or {}) super().__init__() self._glyph_map = dict() @@ -350,7 +351,9 @@ def __init__(self, width, height, svgwriter, basename=None, image_dpi=72, viewBox='0 0 %s %s' % (str_width, str_height), xmlns="http://www.w3.org/2000/svg", version="1.1", - attrib={'xmlns:xlink': "http://www.w3.org/1999/xlink"}) + attrib={'xmlns:xlink': "http://www.w3.org/1999/xlink"}, + **{k: self._aria[k] for k in ['aria-label'] if k in self._aria} + ) self._write_metadata(metadata) self._write_default_style() @@ -1373,9 +1376,12 @@ def print_svg(self, filename, *, bbox_inches_restore=None, metadata=None): self.figure.dpi = 72 width, height = self.figure.get_size_inches() w, h = width * 72, height * 72 + aria = self.figure.get_aria() renderer = MixedModeRenderer( self.figure, width, height, dpi, - RendererSVG(w, h, fh, image_dpi=dpi, metadata=metadata), + RendererSVG( + w, h, fh, image_dpi=dpi, metadata=metadata, aria=aria + ), bbox_inches_restore=bbox_inches_restore) self.figure.draw(renderer) renderer.finalize() diff --git a/lib/matplotlib/tests/test_artist.py b/lib/matplotlib/tests/test_artist.py index 9bfb4ebce1bd..10bd7752f68e 100644 --- a/lib/matplotlib/tests/test_artist.py +++ b/lib/matplotlib/tests/test_artist.py @@ -562,3 +562,10 @@ def draw(self, renderer, extra): assert 'aardvark' == art.draw(renderer, 'aardvark') assert 'aardvark' == art.draw(renderer, extra='aardvark') + + +def test_set_aria(): + art = martist.Artist() + with pytest.raises(TypeError, match='^aria must be dict'): + art.set_aria([]) + art.set_aria(dict(label="artist alt text")) diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index 78383904c4fd..7c9dd162518e 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -609,3 +609,20 @@ def test_svg_font_string(font_str, include_generic): assert font_info == f"{size}px {font_str}" assert text_count == len(ax.texts) + + +def test_aria(): + fig, ax = plt.subplots() + + with BytesIO() as fd: + fig.savefig(fd, format="svg") + buf = fd.getvalue() + + assert b'aria-label' not in buf + + fig.set_aria({'aria-label': 'A test of inserting the label'}) + with BytesIO() as fd: + fig.savefig(fd, format="svg") + buf = fd.getvalue() + + assert b'aria-label' in buf pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy