* 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:
Albert Juhé Lluveras 2018-09-25 11:42:08 +02:00 committed by GitHub
parent d9e47518f9
commit 1900bb0917
4 changed files with 73 additions and 36 deletions

View File

@ -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 }

View File

@ -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,

View File

@ -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',
};

View File

@ -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 ) );
};