Skip to content

Commit

Permalink
feat(chart): Add default aria-label and ability to override it.
Browse files Browse the repository at this point in the history
Add default aria-label which describes the series in the chart.
Add ability to override the default aria-label via an @input.
  • Loading branch information
raymond-shanley-dynatrace committed Sep 19, 2022
1 parent a949607 commit f045ad2
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 2 deletions.
3 changes: 3 additions & 0 deletions libs/barista-components/chart/src/chart.html
Expand Up @@ -10,7 +10,10 @@
<div
class="dt-chart-pane"
#container
role="img"
[style.visibility]="_isLoading ? 'hidden' : 'visible'"
tabindex="0"
[attr.aria-label]="ariaLabel"
></div>

<dt-loading-distractor
Expand Down
20 changes: 19 additions & 1 deletion libs/barista-components/chart/src/chart.ts
Expand Up @@ -103,7 +103,11 @@ import {
import { DtChartRange } from './range/range';
import { DtChartTimestamp } from './timestamp/timestamp';
import { DtChartTooltip } from './tooltip/chart-tooltip';
import { getPlotBackgroundInfo, retainSeriesVisibility } from './utils';
import {
getPlotBackgroundInfo,
retainSeriesVisibility,
createChartAriaLabel,
} from './utils';
import { DtChartFocusTarget } from './chart-focus-anchor';
import { DtChartBase } from './chart-base';
const HIGHCHARTS_PLOT_BACKGROUND = '.highcharts-plot-background';
Expand Down Expand Up @@ -255,6 +259,17 @@ export class DtChart
/** The loading text of the loading distractor. */
@Input('loading-text') loadingText: string;

@Input('aria-label')
get ariaLabel(): string {
if (this._ariaLabel) {
return this._ariaLabel;
}
return this._fallbackAriaLabel;
}
set ariaLabel(value: string) {
this._ariaLabel = value;
}

/** Eventemitter that fires every time the chart is updated */
@Output() readonly updated: EventEmitter<void> = new EventEmitter();

Expand Down Expand Up @@ -313,6 +328,8 @@ export class DtChart

_focusTargets = new Set<DtChartFocusTarget>();

private _ariaLabel: string;
private _fallbackAriaLabel: string;
private _series?: Observable<DtChartSeries[]> | DtChartSeries[];
private _currentSeries?: DtChartSeries[];
private _currentOptions: DtChartOptions;
Expand Down Expand Up @@ -537,6 +554,7 @@ export class DtChart
this._highchartsOptions = highchartsOptions;
this._updateColorOptions();
this._updateChart();
this._fallbackAriaLabel = createChartAriaLabel(this._currentSeries);
this._changeDetectorRef.markForCheck();
}

Expand Down
Expand Up @@ -130,6 +130,7 @@ export const _DtChartSelectionAreaMixinBase = mixinViewportBoundaries<
host: {
class: 'dt-chart-selection-area dt-no-pointer-events',
'[attr.tabindex]': '0',
'[attr.aria-label]': '"Chart selection area"',
},
})
export class DtChartSelectionArea
Expand Down
125 changes: 124 additions & 1 deletion libs/barista-components/chart/src/utils.spec.ts
Expand Up @@ -26,7 +26,8 @@ import {
} from '@angular/cdk/keycodes';

import { createKeyboardEvent } from '@dynatrace/testing/browser';
import { getKeyboardNavigationOffset } from './utils';
import { DtChartSeries } from './chart.interface';
import { getKeyboardNavigationOffset, createChartAriaLabel } from './utils';

describe('DtChart utils', () => {
describe('getKeyboardNavigationOffset', () => {
Expand Down Expand Up @@ -66,4 +67,126 @@ describe('DtChart utils', () => {
expect(getKeyboardNavigationOffset(event)).toBe(10);
});
});

describe('createChartAriaLabel', () => {
it('should return message for undefined series', () => {
// given
const series = undefined;

// when
const result = createChartAriaLabel(series);

// then
expect(result).toEqual('Showing empty chart.');
});

it('should return message for series array of length 0', () => {
// given
const series = [];

// when
const result = createChartAriaLabel(series);

// then
expect(result).toEqual('Showing empty chart.');
});

it('should return message for series array of length 1', () => {
// given
const series: DtChartSeries[] = [
{
name: 'CPU usage',
type: 'line',
data: [],
color: '#92d9f8',
},
];

// when
const result = createChartAriaLabel(series);

// then
expect(result).toEqual(
"Showing 1 series. Series 'CPU usage' of type 'line'.",
);
});

it('should return message for series array of length 2', () => {
// given
const series: DtChartSeries[] = [
{
name: 'CPU usage',
type: 'line',
data: [],
color: '#92d9f8',
},
{
name: 'Number of process group instances',
type: 'column',
yAxis: 1,
data: [],
color: '#006bba',
},
];

// when
const result = createChartAriaLabel(series);

// then
expect(result).toEqual(
"Showing 2 series. Series 'CPU usage' of type 'line', series 'Number of process group instances' of type 'column'.",
);
});

it('should return message for series array of length 3', () => {
// given
const series: DtChartSeries[] = [
{
name: 'CPU usage',
type: 'line',
data: [],
color: '#92d9f8',
},
{
name: 'Number of process group instances',
type: 'column',
yAxis: 1,
data: [],
color: '#006bba',
},
{
name: 'Another series',
type: 'bar',
yAxis: 1,
data: [],
color: '#006bbc',
},
];

// when
const result = createChartAriaLabel(series);

// then
expect(result).toEqual(
"Showing 3 series. Series 'CPU usage' of type 'line', series 'Number of process group instances' of type 'column', series 'Another series' of type 'bar'.",
);
});

it('should return message for series with no name', () => {
// given
const series: DtChartSeries[] = [
{
type: 'line',
data: [],
color: '#92d9f8',
},
];

// when
const result = createChartAriaLabel(series);

// then
expect(result).toEqual("Showing 1 series. Series of type 'line'.");
});
});
});
28 changes: 28 additions & 0 deletions libs/barista-components/chart/src/utils.ts
Expand Up @@ -247,3 +247,31 @@ export function retainSeriesVisibility(
return singleSeries;
};
}

/**
* Creates a string which can be used as an aria-label for a chart.
*
* @param series series for which you want to generate an aria-label
* @returns a string which can be used as an aria-label for a chart
*/
export function createChartAriaLabel(
series: DtChartSeries[] | undefined,
): string {
const seriesCount = series ? series.length : 0;
if (series && seriesCount > 0) {
let seriesSummary = '';
for (let i = 0; i < seriesCount; i++) {
const current = series[i];
const seriesWord = i === 0 ? 'Series' : 'series';
const optionalName = current.name ? `'${current.name}' ` : '';
seriesSummary += `${seriesWord} ${optionalName}of type '${current.type}'`;
if (seriesCount > 1 && i < seriesCount - 1) {
seriesSummary += ', ';
} else if (i === seriesCount - 1) {
seriesSummary += '.';
}
}
return `Showing ${seriesCount} series. ${seriesSummary}`;
}
return `Showing empty chart.`;
}

0 comments on commit f045ad2

Please sign in to comment.