ARIA label synchronization failure during legend interaction in Plotly.js
0 reputation · 01 Apr 2025, 04:14 UTC
0 reputation · 01 Apr 2025, 04:14 UTC
Plotly.js utilizes SVG and WebGL rendering to generate data visualizations. To support screen readers, the library implements ARIA labels and alt text to describe the figure's content. However, the accessibility state must remain synchronized with the visual state of the chart during user interaction.
When users interact with legend toggles to hide or show specific data traces, the visual representation of the data changes immediately. There is uncertainty regarding whether the underlying ARIA labels and descriptive text dynamically update to reflect these visibility changes in real-time, or if they remain static based on the initial render state.
For figures utilizing WebGL (such as scattergl), the rendering to a canvas further complicates the ability of assistive technologies to map current data visibility to the DOM structure.
34775 reputation · 01 Apr 2025, 09:57 UTC
Plotly.js does not automatically update ARIA labels when a legend toggle hides or shows a trace. The legend interaction triggers a full redraw (via Plotly.restyle) that recreates the SVG nodes, discarding any ARIA attributes you added manually. Consequently the accessibility tree still reflects the initial state unless you re‑apply the labels after each redraw. The same behavior occurs for WebGL‑based traces (scattergl, etc.) because the SVG overlay that contains the legend is rebuilt in the same way.
g element with a different id and no custom attributes.plotly_legendclick and plotly_afterplot events after each interaction.const plotDiv = document.getElementById('myPlot');
plotDiv.addEventListener('plotly_afterplot', reapplyLegendAria);
plotDiv.addEventListener('plotly_legendclick', reapplyLegendAria); // optional, ensures immediate update
function reapplyLegendAria(event) {
const legendItems = event.plotDiv.querySelectorAll('.legendtext');
legendItems.forEach((item, idx) => {
const trace = event.plotDiv.data[idx];
const name = trace.name || `Trace ${idx+1}`;
const visible = trace.visible !== 'legendonly';
item.setAttribute('aria-label', `${name} ${visible ? 'visible' : 'hidden'}`);
});
}
// JavaScript
function onLegendClick(evt) {
const traceIdx = evt.target.getAttribute('data-trace');
const trace = Plotly.d3.select('#myPlot').datum().data[traceIdx];
const msg = `${trace.name} is now ${trace.visible === 'legendonly' ? 'hidden' : 'visible'}`;
document.getElementById('plotLive').textContent = msg;
}
To confirm that the above approach matches your setup, let me know which version of Plotly.js you are using and whether you are adding custom ARIA attributes after the initial Plotly.newPlot call. This information helps verify that the event names and SVG structure assumed here are correct for your environment.
Use comments to ask for clarification. Post a solution as an answer.
6,260 reputation · 01 Apr 2025, 14:53 UTC
To complement the use of plotly_afterplot for reapplying labels, it is important to address the state of the toggle itself. Since legend items act as switches, simply updating the aria-label may not be sufficient for screen reader users to understand if a trace is currently active or hidden.
When implementing the reapplyLegendAria handler, consider adding aria-pressed or aria-expanded attributes to the legend elements. This provides a semantic signal of the toggle state rather than relying solely on descriptive text. For example:
aria-pressed="true"aria-pressed="false"Because SVG accessibility varies across browser/screen-reader combinations (e.g., NVDA vs. JAWS), verifying the announcement of these state changes during a live toggle is recommended to ensure the accessibility tree is correctly reflecting the visual state.