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 }
|
title={ selectedChart.label }
|
||||||
interval={ currentInterval }
|
interval={ currentInterval }
|
||||||
allowedIntervals={ allowedIntervals }
|
allowedIntervals={ allowedIntervals }
|
||||||
|
mode="time-comparison"
|
||||||
pointLabelFormat={ formats.pointLabelFormat }
|
pointLabelFormat={ formats.pointLabelFormat }
|
||||||
tooltipTitle={ selectedChart.label }
|
tooltipTitle={ selectedChart.label }
|
||||||
xFormat={ formats.xFormat }
|
xFormat={ formats.xFormat }
|
||||||
|
|
|
@ -100,6 +100,7 @@ class D3Chart extends Component {
|
||||||
height,
|
height,
|
||||||
layout,
|
layout,
|
||||||
margin,
|
margin,
|
||||||
|
mode,
|
||||||
orderedKeys,
|
orderedKeys,
|
||||||
pointLabelFormat,
|
pointLabelFormat,
|
||||||
tooltipFormat,
|
tooltipFormat,
|
||||||
|
@ -132,6 +133,7 @@ class D3Chart extends Component {
|
||||||
line: getLine( xLineScale, yScale ),
|
line: getLine( xLineScale, yScale ),
|
||||||
lineData,
|
lineData,
|
||||||
margin,
|
margin,
|
||||||
|
mode,
|
||||||
orderedKeys: newOrderedKeys,
|
orderedKeys: newOrderedKeys,
|
||||||
pointLabelFormat,
|
pointLabelFormat,
|
||||||
parseDate,
|
parseDate,
|
||||||
|
@ -221,6 +223,11 @@ D3Chart.propTypes = {
|
||||||
right: PropTypes.number,
|
right: PropTypes.number,
|
||||||
top: 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.
|
* The list of labels for this chart.
|
||||||
*/
|
*/
|
||||||
|
@ -267,6 +274,7 @@ D3Chart.defaultProps = {
|
||||||
top: 20,
|
top: 20,
|
||||||
},
|
},
|
||||||
layout: 'standard',
|
layout: 'standard',
|
||||||
|
mode: 'item-comparison',
|
||||||
tooltipFormat: '%B %d, %Y',
|
tooltipFormat: '%B %d, %Y',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
width: 600,
|
width: 600,
|
||||||
|
|
|
@ -188,6 +188,7 @@ class Chart extends Component {
|
||||||
const {
|
const {
|
||||||
dateParser,
|
dateParser,
|
||||||
layout,
|
layout,
|
||||||
|
mode,
|
||||||
pointLabelFormat,
|
pointLabelFormat,
|
||||||
title,
|
title,
|
||||||
tooltipFormat,
|
tooltipFormat,
|
||||||
|
@ -263,6 +264,7 @@ class Chart extends Component {
|
||||||
dateParser={ dateParser }
|
dateParser={ dateParser }
|
||||||
height={ 300 }
|
height={ 300 }
|
||||||
margin={ margin }
|
margin={ margin }
|
||||||
|
mode={ mode }
|
||||||
orderedKeys={ orderedKeys }
|
orderedKeys={ orderedKeys }
|
||||||
pointLabelFormat={ pointLabelFormat }
|
pointLabelFormat={ pointLabelFormat }
|
||||||
tooltipFormat={ tooltipFormat }
|
tooltipFormat={ tooltipFormat }
|
||||||
|
@ -320,6 +322,11 @@ Chart.propTypes = {
|
||||||
* to the left or 'compact' has the legend below
|
* to the left or 'compact' has the legend below
|
||||||
*/
|
*/
|
||||||
layout: PropTypes.oneOf( [ 'standard', 'comparison', 'compact' ] ),
|
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.
|
* A title describing this chart.
|
||||||
*/
|
*/
|
||||||
|
@ -350,6 +357,7 @@ Chart.defaultProps = {
|
||||||
x2Format: '%b %Y',
|
x2Format: '%b %Y',
|
||||||
yFormat: '$.3s',
|
yFormat: '$.3s',
|
||||||
layout: 'standard',
|
layout: 'standard',
|
||||||
|
mode: 'item-comparison',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
interval: 'day',
|
interval: 'day',
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { findIndex, get } from 'lodash';
|
import { findIndex, get } from 'lodash';
|
||||||
import { max as d3Max } from 'd3-array';
|
import { max as d3Max } from 'd3-array';
|
||||||
import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft } from 'd3-axis';
|
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 { event as d3Event, mouse as d3Mouse, select as d3Select } from 'd3-selection';
|
||||||
import { line as d3Line } from 'd3-shape';
|
import { line as d3Line } from 'd3-shape';
|
||||||
import { format as formatDate } from '@wordpress/date';
|
import { format as formatDate } from '@wordpress/date';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
|
@ -84,6 +84,7 @@ export const getLineData = ( data, orderedKeys ) =>
|
||||||
values: data.map( d => ( {
|
values: data.map( d => ( {
|
||||||
date: d.date,
|
date: d.date,
|
||||||
focus: row.focus,
|
focus: row.focus,
|
||||||
|
label: get( d, [ row.key, 'label' ], '' ),
|
||||||
value: get( d, [ row.key, 'value' ], 0 ),
|
value: get( d, [ row.key, 'value' ], 0 ),
|
||||||
visible: row.visible,
|
visible: row.visible,
|
||||||
} ) ),
|
} ) ),
|
||||||
|
@ -314,6 +315,7 @@ export const drawAxis = ( node, params ) => {
|
||||||
node
|
node
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
.attr( 'class', 'axis' )
|
.attr( 'class', 'axis' )
|
||||||
|
.attr( 'aria-hidden', 'true' )
|
||||||
.attr( 'transform', `translate(0,${ params.height })` )
|
.attr( 'transform', `translate(0,${ params.height })` )
|
||||||
.call(
|
.call(
|
||||||
d3AxisBottom( xScale )
|
d3AxisBottom( xScale )
|
||||||
|
@ -324,6 +326,7 @@ export const drawAxis = ( node, params ) => {
|
||||||
node
|
node
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
.attr( 'class', 'axis axis-month' )
|
.attr( 'class', 'axis axis-month' )
|
||||||
|
.attr( 'aria-hidden', 'true' )
|
||||||
.attr( 'transform', `translate(3, ${ params.height + 20 })` )
|
.attr( 'transform', `translate(3, ${ params.height + 20 })` )
|
||||||
.call(
|
.call(
|
||||||
d3AxisBottom( xScale )
|
d3AxisBottom( xScale )
|
||||||
|
@ -371,6 +374,7 @@ export const drawAxis = ( node, params ) => {
|
||||||
node
|
node
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
.attr( 'class', 'axis y-axis' )
|
.attr( 'class', 'axis y-axis' )
|
||||||
|
.attr( 'aria-hidden', 'true' )
|
||||||
.attr( 'transform', 'translate(-50, 0)' )
|
.attr( 'transform', 'translate(-50, 0)' )
|
||||||
.attr( 'text-anchor', 'start' )
|
.attr( 'text-anchor', 'start' )
|
||||||
.call(
|
.call(
|
||||||
|
@ -428,29 +432,29 @@ const showTooltip = ( node, params, d, position ) => {
|
||||||
` );
|
` );
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseOverBarChart = ( d, i, nodes, node, data, params, position ) => {
|
const handleMouseOverBarChart = ( date, parentNode, node, data, params, position ) => {
|
||||||
d3Select( nodes[ i ].parentNode )
|
d3Select( parentNode )
|
||||||
.select( '.barfocus' )
|
.select( '.barfocus' )
|
||||||
.attr( 'opacity', '0.1' );
|
.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 ) => {
|
const handleMouseOutBarChart = ( parentNode, params ) => {
|
||||||
d3Select( nodes[ i ].parentNode )
|
d3Select( parentNode )
|
||||||
.select( '.barfocus' )
|
.select( '.barfocus' )
|
||||||
.attr( 'opacity', '0' );
|
.attr( 'opacity', '0' );
|
||||||
params.tooltip.style( 'display', 'none' );
|
params.tooltip.style( 'display', 'none' );
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseOverLineChart = ( d, i, nodes, node, data, params, position ) => {
|
const handleMouseOverLineChart = ( date, parentNode, node, data, params, position ) => {
|
||||||
d3Select( nodes[ i ].parentNode )
|
d3Select( parentNode )
|
||||||
.select( '.focus-grid' )
|
.select( '.focus-grid' )
|
||||||
.attr( 'opacity', '1' );
|
.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 ) => {
|
const handleMouseOutLineChart = ( parentNode, params ) => {
|
||||||
d3Select( nodes[ i ].parentNode )
|
d3Select( parentNode )
|
||||||
.select( '.focus-grid' )
|
.select( '.focus-grid' )
|
||||||
.attr( 'opacity', '0' );
|
.attr( 'opacity', '0' );
|
||||||
params.tooltip.style( 'display', 'none' );
|
params.tooltip.style( 'display', 'none' );
|
||||||
|
@ -470,7 +474,9 @@ export const drawLines = ( node, data, params ) => {
|
||||||
.data( params.lineData.filter( d => d.visible ) )
|
.data( params.lineData.filter( d => d.visible ) )
|
||||||
.enter()
|
.enter()
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
.attr( 'class', 'line-g' );
|
.attr( 'class', 'line-g' )
|
||||||
|
.attr( 'role', 'region' )
|
||||||
|
.attr( 'aria-label', d => d.key );
|
||||||
|
|
||||||
series
|
series
|
||||||
.append( 'path' )
|
.append( 'path' )
|
||||||
|
@ -501,10 +507,18 @@ export const drawLines = ( node, data, params ) => {
|
||||||
} )
|
} )
|
||||||
.attr( 'cx', d => params.xLineScale( new Date( d.date ) ) )
|
.attr( 'cx', d => params.xLineScale( new Date( d.date ) ) )
|
||||||
.attr( 'cy', d => params.yScale( d.value ) )
|
.attr( 'cy', d => params.yScale( d.value ) )
|
||||||
.on( 'mouseover', ( d, i, nodes ) =>
|
.attr( 'tabindex', '0' )
|
||||||
handleMouseOverLineChart( d, i, nodes, node, data, params )
|
.attr( 'aria-label', d => {
|
||||||
)
|
const label = d.label
|
||||||
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( d, i, nodes, params ) );
|
? 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
|
const focus = node
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
|
@ -532,15 +546,10 @@ export const drawLines = ( node, data, params ) => {
|
||||||
.attr( 'width', d => d.width )
|
.attr( 'width', d => d.width )
|
||||||
.attr( 'height', params.height )
|
.attr( 'height', params.height )
|
||||||
.attr( 'opacity', 0 )
|
.attr( 'opacity', 0 )
|
||||||
.attr( 'tabindex', '0' )
|
|
||||||
.on( 'mouseover', ( d, i, nodes ) =>
|
.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 ) => {
|
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( nodes[ i ].parentNode, params ) );
|
||||||
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 ) );
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const drawBars = ( node, data, params ) => {
|
export const drawBars = ( node, data, params ) => {
|
||||||
|
@ -552,7 +561,15 @@ export const drawBars = ( node, data, params ) => {
|
||||||
.enter()
|
.enter()
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
.attr( 'transform', d => `translate(${ params.xScale( d.date ) },0)` )
|
.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
|
barGroup
|
||||||
.append( 'rect' )
|
.append( 'rect' )
|
||||||
|
@ -569,8 +586,10 @@ export const drawBars = ( node, data, params ) => {
|
||||||
params.orderedKeys.filter( row => row.visible ).map( row => ( {
|
params.orderedKeys.filter( row => row.visible ).map( row => ( {
|
||||||
key: row.key,
|
key: row.key,
|
||||||
focus: row.focus,
|
focus: row.focus,
|
||||||
value: d[ row.key ].value,
|
value: get( d, [ row.key, 'value' ], 0 ),
|
||||||
|
label: get( d, [ row.key, 'label' ], '' ),
|
||||||
visible: row.visible,
|
visible: row.visible,
|
||||||
|
date: d.date,
|
||||||
} ) )
|
} ) )
|
||||||
)
|
)
|
||||||
.enter()
|
.enter()
|
||||||
|
@ -581,14 +600,20 @@ export const drawBars = ( node, data, params ) => {
|
||||||
.attr( 'width', params.xGroupScale.bandwidth() )
|
.attr( 'width', params.xGroupScale.bandwidth() )
|
||||||
.attr( 'height', d => params.height - params.yScale( d.value ) )
|
.attr( 'height', d => params.height - params.yScale( d.value ) )
|
||||||
.attr( 'fill', d => getColor( d.key, params ) )
|
.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 => {
|
.style( 'opacity', d => {
|
||||||
const opacity = d.focus ? 1 : 0.1;
|
const opacity = d.focus ? 1 : 0.1;
|
||||||
return d.visible ? opacity : 0;
|
return d.visible ? opacity : 0;
|
||||||
} )
|
} )
|
||||||
.on( 'mouseover', ( d, i, nodes ) =>
|
.on( 'focus', ( d, i, nodes ) => {
|
||||||
handleMouseOverBarChart( d, i, nodes, node, data, params )
|
const position = calculatePositionInChart( d3Event.target, node.node() );
|
||||||
)
|
handleMouseOverBarChart( d.date, nodes[ i ].parentNode, node, data, params, position );
|
||||||
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutBarChart( d, i, nodes, params ) );
|
} )
|
||||||
|
.on( 'blur', ( d, i, nodes ) => handleMouseOutBarChart( nodes[ i ].parentNode, params ) );
|
||||||
|
|
||||||
barGroup
|
barGroup
|
||||||
.append( 'rect' )
|
.append( 'rect' )
|
||||||
|
@ -598,13 +623,8 @@ export const drawBars = ( node, data, params ) => {
|
||||||
.attr( 'width', params.xGroupScale.range()[ 1 ] )
|
.attr( 'width', params.xGroupScale.range()[ 1 ] )
|
||||||
.attr( 'height', params.height )
|
.attr( 'height', params.height )
|
||||||
.attr( 'opacity', '0' )
|
.attr( 'opacity', '0' )
|
||||||
.attr( 'tabindex', '0' )
|
|
||||||
.on( 'mouseover', ( d, i, nodes ) =>
|
.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 ) => {
|
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutBarChart( nodes[ i ].parentNode, params ) );
|
||||||
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 ) );
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue