From 948b5bf9871303d1742f1b9a17a3bd4006ce2fea Mon Sep 17 00:00:00 2001 From: krkshitij <110246001+krkshitij@users.noreply.github.com> Date: Mon, 24 Apr 2023 11:44:03 +0530 Subject: [PATCH] Make line chart screen reader accessible (#27632) * make line chart screen reader accessible * add change file * add function --- ...-2cf73f57-767c-4339-bfb6-152caf82ad6c.json | 7 ++ .../components/LineChart/LineChart.base.tsx | 61 +++++++++++---- .../__snapshots__/LineChart.test.tsx.snap | 77 +++++++++++-------- 3 files changed, 97 insertions(+), 48 deletions(-) create mode 100644 change/@fluentui-react-charting-2cf73f57-767c-4339-bfb6-152caf82ad6c.json diff --git a/change/@fluentui-react-charting-2cf73f57-767c-4339-bfb6-152caf82ad6c.json b/change/@fluentui-react-charting-2cf73f57-767c-4339-bfb6-152caf82ad6c.json new file mode 100644 index 00000000000000..ec5ce7878a6747 --- /dev/null +++ b/change/@fluentui-react-charting-2cf73f57-767c-4339-bfb6-152caf82ad6c.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Make line chart screen reader accessible", + "packageName": "@fluentui/react-charting", + "email": "kumarkshitij@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-charting/src/components/LineChart/LineChart.base.tsx b/packages/react-charting/src/components/LineChart/LineChart.base.tsx index 86a5db5dbcd80a..9d9d55a360231b 100644 --- a/packages/react-charting/src/components/LineChart/LineChart.base.tsx +++ b/packages/react-charting/src/components/LineChart/LineChart.base.tsx @@ -568,6 +568,15 @@ export class LineChartBase extends React.Component { + this._refCallback(e!, circleId); + }} + onFocus={() => this._handleFocus(circleId, x1, xAxisCalloutData, circleId, xAxisCalloutAccessibilityData)} + onBlur={this._handleMouseOut} + {...this._getClickHandler(this._points[i].data[0].onDataPointClick)} />, ); } @@ -576,7 +585,7 @@ export class LineChartBase extends React.Component a.startIndex - b.startIndex) ?? []; // Use path rendering technique for larger datasets to optimize performance. - if (this.props.optimizeLargeData!) { + if (this.props.optimizeLargeData && this._points[i].data.length > 1) { const line = d3Line() // eslint-disable-next-line @typescript-eslint/no-explicit-any .x((d: any) => this._xAxisScale(d[0])) @@ -634,8 +643,12 @@ export class LineChartBase extends React.Component, ); } else { @@ -650,6 +663,10 @@ export class LineChartBase extends React.Component, ); } @@ -664,7 +681,6 @@ export class LineChartBase extends React.Component, ); - } else { + } else if (!this.props.optimizeLargeData) { for (let j = 1; j < this._points[i].data.length; j++) { const gapResult = this._checkInGap(j, gaps, gapIndex); const isInGap = gapResult.isInGap; @@ -726,11 +742,13 @@ export class LineChartBase extends React.Component this._handleFocus(lineId, x1, xAxisCalloutData, circleId, xAxisCalloutAccessibilityData)} onBlur={this._handleMouseOut} - onClick={this._onDataPointClick.bind(this, this._points[i].data[j - 1].onDataPointClick)} + {...this._getClickHandler(this._points[i].data[j - 1].onDataPointClick)} opacity={isLegendSelected && !currentPointHidden ? 1 : 0.01} fill={this._getPointFill(lineColor, circleId, j, false)} stroke={lineColor} strokeWidth={strokeWidth} + role="img" + aria-label={this._getAriaLabel(i, j - 1)} />, ); if (j + 1 === this._points[i].data.length) { @@ -778,11 +796,13 @@ export class LineChartBase extends React.Component, ); /* eslint-enable react/jsx-no-bind */ @@ -847,7 +867,7 @@ export class LineChartBase extends React.Component, ); } @@ -1103,7 +1123,6 @@ export class LineChartBase extends React.Component `translate(${_this._xAxisScale(x)}, 0)`) .attr('visibility', 'visibility'); @@ -1173,16 +1192,18 @@ export class LineChartBase extends React.Component void) => { + /** + * Screen readers announce an element as clickable if the onClick attribute is set. + * This function sets the attribute only when a click event handler is provided. + */ + private _getClickHandler = (func?: () => void): { onClick?: () => void } => { if (func) { - func(); + return { + onClick: func, + }; } - }; - private _onDataPointClick = (func: () => void) => { - if (func) { - func(); - } + return {}; }; private _handleMouseOut = () => { @@ -1326,4 +1347,14 @@ export class LineChartBase extends React.Component { return colorFillBar.applyPattern ? 1 : 0.4; }; + + private _getAriaLabel = (lineIndex: number, pointIndex: number): string => { + const line = this._points[lineIndex]; + const point = line.data[pointIndex]; + const formattedDate = point.x instanceof Date ? point.x.toLocaleString() : point.x; + const xValue = point.xAxisCalloutData || formattedDate; + const legend = line.legend; + const yValue = point.yAxisCalloutData || point.y; + return point.callOutAccessibilityData?.ariaLabel || `${xValue}. ${legend}, ${yValue}.`; + }; } diff --git a/packages/react-charting/src/components/LineChart/__snapshots__/LineChart.test.tsx.snap b/packages/react-charting/src/components/LineChart/__snapshots__/LineChart.test.tsx.snap index 80ca284639e148..4afb2190e3cc48 100644 --- a/packages/react-charting/src/components/LineChart/__snapshots__/LineChart.test.tsx.snap +++ b/packages/react-charting/src/components/LineChart/__snapshots__/LineChart.test.tsx.snap @@ -113,7 +113,6 @@ exports[`LineChart - mouse events Should render callout correctly on mouseover 1 @@ -633,7 +634,6 @@ exports[`LineChart - mouse events Should render customized callout on mouseover @@ -1051,7 +1053,6 @@ exports[`LineChart - mouse events Should render customized callout per stack on @@ -1468,7 +1471,6 @@ exports[`LineChart snapShot testing Should render with default colors when line @@ -1801,7 +1805,6 @@ exports[`LineChart snapShot testing renders LineChart correctly 1`] = ` @@ -2134,7 +2139,6 @@ exports[`LineChart snapShot testing renders enabledLegendsWrapLines correctly 1` @@ -2447,7 +2453,6 @@ exports[`LineChart snapShot testing renders hideLegend correctly 1`] = ` @@ -2620,7 +2627,6 @@ exports[`LineChart snapShot testing renders hideTooltip correctly 1`] = ` @@ -2953,7 +2961,6 @@ exports[`LineChart snapShot testing renders showXAxisLablesTooltip correctly 1`] @@ -3286,7 +3295,6 @@ exports[`LineChart snapShot testing renders wrapXAxisLables correctly 1`] = ` @@ -3619,7 +3629,6 @@ exports[`LineChart snapShot testing renders yAxisTickFormat correctly 1`] = `