-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add wai-aria property to the artist accessibility annotations. #21328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1639727
3ee25a0
3d570ba
2c57f4b
87032e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,23 @@ | ||||||||||
All ``Arist`` now carry wai-aria data | ||||||||||
ksunden marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
------------------------------------- | ||||||||||
Comment on lines
+1
to
+2
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
It is now possible to attach `wai-aria | ||||||||||
<https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles>`__ 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. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be helpful to have an explicit example along with this that shows the structure of the dict() that you're expecting.
But, then we see this in roles: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/textbox_role
where I'm a bit confused on whether you have a nested dict here or something else? |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This docstring should define or reference what ARIA means. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should include link. |
||
|
||
A primary use of this method is to attach aria-label to the artist to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make this clearer that |
||
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)}') | ||
Comment on lines
+1106
to
+1109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should use |
||
self._aria = aria | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add validation to known aria attributes |
||
|
||
def update_aria(self, **aria): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want I think a user could then do something like: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coming back to this, I am leaning to dropping this method. I can think of two ways you might want to update ("replace what is there" vs "if there is not something there, set these"). I think it is better to leave just |
||
"""Update WAI-ARIA properties on the artist.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the only one that says |
||
# TODO validation | ||
for k, v in aria.items(): | ||
self._aria[k] = v | ||
Comment on lines
+1115
to
+1116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should get a test? |
||
|
||
def get_label(self): | ||
"""Return the label used for this artist in the legend.""" | ||
return self._label | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a safe extension of the API as we only use this additional optional key in this file where we know which class we are going to be calling. I do not know if other file formats also support aria roles (as they are more of an html thing and tend to live on the DOM outside of what ever we generate). If it is more general, we can push to other backends and up to base, otherwise I think we should keep this slight extension local to the svg backend. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should consider ripping this out. |
||
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() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.