Improve charts accessibility (https://github.com/woocommerce/woocommerce-admin/pull/421)
* Add aria roles to chart elements * Make individual points (in line charts) and individual bar (in bar charts) focusable * Remove methods which are never used * Reduce the number of parameters required by functions that display/hide the tooltip * Use tooltipFormat for accessibility dates * Rename 'formatVoiceDate' function to 'getTooltipDate' * Use string literals for aria-label * Remove table role which was no longer needed * Add aria-hidden to X-axis in charts * Remove 'key' from points/bar aria-label in charts * Set different ARIA properties depending on chart mode (time or item comparison) * Label should default to an empty string instead of a 0 * Use date format from params instead of hardcoded
This commit is contained in:
parent
d9e47518f9
commit
1900bb0917
|
@ -332,6 +332,7 @@ export class RevenueReport extends Component {
|
|||
title={ selectedChart.label }
|
||||
interval={ currentInterval }
|
||||
allowedIntervals={ allowedIntervals }
|
||||
mode="time-comparison"
|
||||
pointLabelFormat={ formats.pointLabelFormat }
|
||||
tooltipTitle={ selectedChart.label }
|
||||
xFormat={ formats.xFormat }
|
||||
|
|
|
@ -100,6 +100,7 @@ class D3Chart extends Component {
|
|||
height,
|
||||
layout,
|
||||
margin,
|
||||
mode,
|
||||
orderedKeys,
|
||||
pointLabelFormat,
|
||||
tooltipFormat,
|
||||
|
@ -132,6 +133,7 @@ class D3Chart extends Component {
|
|||
line: getLine( xLineScale, yScale ),
|
||||
lineData,
|
||||
margin,
|
||||
mode,
|
||||
orderedKeys: newOrderedKeys,
|
||||
pointLabelFormat,
|
||||
parseDate,
|
||||
|
@ -221,6 +223,11 @@ D3Chart.propTypes = {
|
|||
right: PropTypes.number,
|
||||
top: PropTypes.number,
|
||||
} ),
|
||||
/**
|
||||
* `items-comparison` (default) or `time-comparison`, this is used to generate correct
|
||||
* ARIA properties.
|
||||
*/
|
||||
mode: PropTypes.oneOf( [ 'item-comparison', 'time-comparison' ] ),
|
||||
/**
|
||||
* The list of labels for this chart.
|
||||
*/
|
||||
|
@ -267,6 +274,7 @@ D3Chart.defaultProps = {
|
|||
top: 20,
|
||||
},
|
||||
layout: 'standard',
|
||||
mode: 'item-comparison',
|
||||
tooltipFormat: '%B %d, %Y',
|
||||
type: 'line',
|
||||
width: 600,
|
||||
|
|
|
@ -188,6 +188,7 @@ class Chart extends Component {
|
|||
const {
|
||||
dateParser,
|
||||
layout,
|
||||
mode,
|
||||
pointLabelFormat,
|
||||
title,
|
||||
tooltipFormat,
|
||||
|
@ -263,6 +264,7 @@ class Chart extends Component {
|
|||
dateParser={ dateParser }
|
||||
height={ 300 }
|
||||
margin={ margin }
|
||||
mode={ mode }
|
||||
orderedKeys={ orderedKeys }
|
||||
pointLabelFormat={ pointLabelFormat }
|
||||
tooltipFormat={ tooltipFormat }
|
||||
|
@ -320,6 +322,11 @@ Chart.propTypes = {
|
|||
* to the left or 'compact' has the legend below
|
||||
*/
|
||||
layout: PropTypes.oneOf( [ 'standard', 'comparison', 'compact' ] ),
|
||||
/**
|
||||
* `item-comparison` (default) or `time-comparison`, this is used to generate correct
|
||||
* ARIA properties.
|
||||
*/
|
||||
mode: PropTypes.oneOf( [ 'item-comparison', 'time-comparison' ] ),
|
||||
/**
|
||||
* A title describing this chart.
|
||||
*/
|
||||
|
@ -350,6 +357,7 @@ Chart.defaultProps = {
|
|||
x2Format: '%b %Y',
|
||||
yFormat: '$.3s',
|
||||
layout: 'standard',
|
||||
mode: 'item-comparison',
|
||||
type: 'line',
|
||||
interval: 'day',
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import { findIndex, get } from 'lodash';
|
||||
import { max as d3Max } from 'd3-array';
|
||||
import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft } from 'd3-axis';
|
||||
|
@ -16,6 +15,7 @@ import {
|
|||
import { event as d3Event, mouse as d3Mouse, select as d3Select } from 'd3-selection';
|
||||
import { line as d3Line } from 'd3-shape';
|
||||
import { format as formatDate } from '@wordpress/date';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
@ -84,6 +84,7 @@ export const getLineData = ( data, orderedKeys ) =>
|
|||
values: data.map( d => ( {
|
||||
date: d.date,
|
||||
focus: row.focus,
|
||||
label: get( d, [ row.key, 'label' ], '' ),
|
||||
value: get( d, [ row.key, 'value' ], 0 ),
|
||||
visible: row.visible,
|
||||
} ) ),
|
||||
|
@ -314,6 +315,7 @@ export const drawAxis = ( node, params ) => {
|
|||
node
|
||||
.append( 'g' )
|
||||
.attr( 'class', 'axis' )
|
||||
.attr( 'aria-hidden', 'true' )
|
||||
.attr( 'transform', `translate(0,${ params.height })` )
|
||||
.call(
|
||||
d3AxisBottom( xScale )
|
||||
|
@ -324,6 +326,7 @@ export const drawAxis = ( node, params ) => {
|
|||
node
|
||||
.append( 'g' )
|
||||
.attr( 'class', 'axis axis-month' )
|
||||
.attr( 'aria-hidden', 'true' )
|
||||
.attr( 'transform', `translate(3, ${ params.height + 20 })` )
|
||||
.call(
|
||||
d3AxisBottom( xScale )
|
||||
|
@ -371,6 +374,7 @@ export const drawAxis = ( node, params ) => {
|
|||
node
|
||||
.append( 'g' )
|
||||
.attr( 'class', 'axis y-axis' )
|
||||
.attr( 'aria-hidden', 'true' )
|
||||
.attr( 'transform', 'translate(-50, 0)' )
|
||||
.attr( 'text-anchor', 'start' )
|
||||
.call(
|
||||
|
@ -428,29 +432,29 @@ const showTooltip = ( node, params, d, position ) => {
|
|||
` );
|
||||
};
|
||||
|
||||
const handleMouseOverBarChart = ( d, i, nodes, node, data, params, position ) => {
|
||||
d3Select( nodes[ i ].parentNode )
|
||||
const handleMouseOverBarChart = ( date, parentNode, node, data, params, position ) => {
|
||||
d3Select( parentNode )
|
||||
.select( '.barfocus' )
|
||||
.attr( 'opacity', '0.1' );
|
||||
showTooltip( node, params, d, position );
|
||||
showTooltip( node, params, data.find( e => e.date === date ), position );
|
||||
};
|
||||
|
||||
const handleMouseOutBarChart = ( d, i, nodes, params ) => {
|
||||
d3Select( nodes[ i ].parentNode )
|
||||
const handleMouseOutBarChart = ( parentNode, params ) => {
|
||||
d3Select( parentNode )
|
||||
.select( '.barfocus' )
|
||||
.attr( 'opacity', '0' );
|
||||
params.tooltip.style( 'display', 'none' );
|
||||
};
|
||||
|
||||
const handleMouseOverLineChart = ( d, i, nodes, node, data, params, position ) => {
|
||||
d3Select( nodes[ i ].parentNode )
|
||||
const handleMouseOverLineChart = ( date, parentNode, node, data, params, position ) => {
|
||||
d3Select( parentNode )
|
||||
.select( '.focus-grid' )
|
||||
.attr( 'opacity', '1' );
|
||||
showTooltip( node, params, data.find( e => e.date === d.date ), position );
|
||||
showTooltip( node, params, data.find( e => e.date === date ), position );
|
||||
};
|
||||
|
||||
const handleMouseOutLineChart = ( d, i, nodes, params ) => {
|
||||
d3Select( nodes[ i ].parentNode )
|
||||
const handleMouseOutLineChart = ( parentNode, params ) => {
|
||||
d3Select( parentNode )
|
||||
.select( '.focus-grid' )
|
||||
.attr( 'opacity', '0' );
|
||||
params.tooltip.style( 'display', 'none' );
|
||||
|
@ -470,7 +474,9 @@ export const drawLines = ( node, data, params ) => {
|
|||
.data( params.lineData.filter( d => d.visible ) )
|
||||
.enter()
|
||||
.append( 'g' )
|
||||
.attr( 'class', 'line-g' );
|
||||
.attr( 'class', 'line-g' )
|
||||
.attr( 'role', 'region' )
|
||||
.attr( 'aria-label', d => d.key );
|
||||
|
||||
series
|
||||
.append( 'path' )
|
||||
|
@ -501,10 +507,18 @@ export const drawLines = ( node, data, params ) => {
|
|||
} )
|
||||
.attr( 'cx', d => params.xLineScale( new Date( d.date ) ) )
|
||||
.attr( 'cy', d => params.yScale( d.value ) )
|
||||
.on( 'mouseover', ( d, i, nodes ) =>
|
||||
handleMouseOverLineChart( d, i, nodes, node, data, params )
|
||||
)
|
||||
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( d, i, nodes, params ) );
|
||||
.attr( 'tabindex', '0' )
|
||||
.attr( 'aria-label', d => {
|
||||
const label = d.label
|
||||
? d.label
|
||||
: params.tooltipFormat( d.date instanceof Date ? d.date : new Date( d.date ) );
|
||||
return `${ label } ${ formatCurrency( d.value ) }`;
|
||||
} )
|
||||
.on( 'focus', ( d, i, nodes ) => {
|
||||
const position = calculatePositionInChart( d3Event.target, node.node() );
|
||||
handleMouseOverLineChart( d.date, nodes[ i ].parentNode, node, data, params, position );
|
||||
} )
|
||||
.on( 'blur', ( d, i, nodes ) => handleMouseOutLineChart( nodes[ i ].parentNode, params ) );
|
||||
|
||||
const focus = node
|
||||
.append( 'g' )
|
||||
|
@ -532,15 +546,10 @@ export const drawLines = ( node, data, params ) => {
|
|||
.attr( 'width', d => d.width )
|
||||
.attr( 'height', params.height )
|
||||
.attr( 'opacity', 0 )
|
||||
.attr( 'tabindex', '0' )
|
||||
.on( 'mouseover', ( d, i, nodes ) =>
|
||||
handleMouseOverLineChart( d, i, nodes, node, data, params )
|
||||
handleMouseOverLineChart( d.date, nodes[ i ].parentNode, node, data, params )
|
||||
)
|
||||
.on( 'focus', ( d, i, nodes ) => {
|
||||
const position = calculatePositionInChart( d3Event.target, node.node() );
|
||||
handleMouseOverLineChart( d, i, nodes, node, data, params, position );
|
||||
} )
|
||||
.on( 'mouseout blur', ( d, i, nodes ) => handleMouseOutLineChart( d, i, nodes, params ) );
|
||||
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( nodes[ i ].parentNode, params ) );
|
||||
};
|
||||
|
||||
export const drawBars = ( node, data, params ) => {
|
||||
|
@ -552,7 +561,15 @@ export const drawBars = ( node, data, params ) => {
|
|||
.enter()
|
||||
.append( 'g' )
|
||||
.attr( 'transform', d => `translate(${ params.xScale( d.date ) },0)` )
|
||||
.attr( 'class', 'bargroup' );
|
||||
.attr( 'class', 'bargroup' )
|
||||
.attr( 'role', 'region' )
|
||||
.attr(
|
||||
'aria-label',
|
||||
d =>
|
||||
params.mode === 'item-comparison'
|
||||
? params.tooltipFormat( d.date instanceof Date ? d.date : new Date( d.date ) )
|
||||
: null
|
||||
);
|
||||
|
||||
barGroup
|
||||
.append( 'rect' )
|
||||
|
@ -569,8 +586,10 @@ export const drawBars = ( node, data, params ) => {
|
|||
params.orderedKeys.filter( row => row.visible ).map( row => ( {
|
||||
key: row.key,
|
||||
focus: row.focus,
|
||||
value: d[ row.key ].value,
|
||||
value: get( d, [ row.key, 'value' ], 0 ),
|
||||
label: get( d, [ row.key, 'label' ], '' ),
|
||||
visible: row.visible,
|
||||
date: d.date,
|
||||
} ) )
|
||||
)
|
||||
.enter()
|
||||
|
@ -581,14 +600,20 @@ export const drawBars = ( node, data, params ) => {
|
|||
.attr( 'width', params.xGroupScale.bandwidth() )
|
||||
.attr( 'height', d => params.height - params.yScale( d.value ) )
|
||||
.attr( 'fill', d => getColor( d.key, params ) )
|
||||
.attr( 'tabindex', '0' )
|
||||
.attr( 'aria-label', d => {
|
||||
const label = params.mode === 'time-comparison' && d.label ? d.label : d.key;
|
||||
return `${ label } ${ formatCurrency( d.value ) }`;
|
||||
} )
|
||||
.style( 'opacity', d => {
|
||||
const opacity = d.focus ? 1 : 0.1;
|
||||
return d.visible ? opacity : 0;
|
||||
} )
|
||||
.on( 'mouseover', ( d, i, nodes ) =>
|
||||
handleMouseOverBarChart( d, i, nodes, node, data, params )
|
||||
)
|
||||
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutBarChart( d, i, nodes, params ) );
|
||||
.on( 'focus', ( d, i, nodes ) => {
|
||||
const position = calculatePositionInChart( d3Event.target, node.node() );
|
||||
handleMouseOverBarChart( d.date, nodes[ i ].parentNode, node, data, params, position );
|
||||
} )
|
||||
.on( 'blur', ( d, i, nodes ) => handleMouseOutBarChart( nodes[ i ].parentNode, params ) );
|
||||
|
||||
barGroup
|
||||
.append( 'rect' )
|
||||
|
@ -598,13 +623,8 @@ export const drawBars = ( node, data, params ) => {
|
|||
.attr( 'width', params.xGroupScale.range()[ 1 ] )
|
||||
.attr( 'height', params.height )
|
||||
.attr( 'opacity', '0' )
|
||||
.attr( 'tabindex', '0' )
|
||||
.on( 'mouseover', ( d, i, nodes ) =>
|
||||
handleMouseOverBarChart( d, i, nodes, node, data, params )
|
||||
handleMouseOverBarChart( d.date, nodes[ i ].parentNode, node, data, params )
|
||||
)
|
||||
.on( 'focus', ( d, i, nodes ) => {
|
||||
const position = calculatePositionInChart( d3Event.target, node.node() );
|
||||
handleMouseOverBarChart( d, i, nodes, node, data, params, position );
|
||||
} )
|
||||
.on( 'mouseout blur', ( d, i, nodes ) => handleMouseOutBarChart( d, i, nodes, params ) );
|
||||
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutBarChart( nodes[ i ].parentNode, params ) );
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue