Skip to content

Commit

Permalink
feat(a11y): allow user to pass custom description for screen readers (o…
Browse files Browse the repository at this point in the history
  • Loading branch information
rshen91 authored Apr 14, 2021
1 parent d94ba32 commit a0020ba
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 18 deletions.
4 changes: 3 additions & 1 deletion packages/osd-charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ export const DEFAULT_TOOLTIP_SNAP = true;
export const DEFAULT_TOOLTIP_TYPE: "vertical";

// @public (undocumented)
export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'debug' | 'tooltip' | 'theme' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents' | 'showLegend' | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth';
export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'debug' | 'tooltip' | 'theme' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents' | 'showLegend' | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth' | 'description' | 'useDefaultSummary';

// @public (undocumented)
export const DEPTH_KEY = "depth";
Expand Down Expand Up @@ -1739,6 +1739,7 @@ export interface SettingsSpec extends Spec, LegendSpec {
debug: boolean;
// @alpha
debugState?: boolean;
description?: string;
// @alpha
externalPointerEvents: ExternalPointerEventsSettings;
hideDuplicateAxes: boolean;
Expand Down Expand Up @@ -1769,6 +1770,7 @@ export interface SettingsSpec extends Spec, LegendSpec {
roundHistogramBrushValues?: boolean;
theme?: PartialTheme | PartialTheme[];
tooltip: TooltipSettings;
useDefaultSummary: boolean;
// (undocumented)
xDomain?: CustomXDomain;
}
Expand Down
10 changes: 10 additions & 0 deletions packages/osd-charts/integration/page_objects/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,16 @@ class CommonPage {
});
return accessibilitySnapshot;
}

/**
* Get HTML for element to test aria labels etc
*/
// eslint-disable-next-line class-methods-use-this
async getElementHTML(url: string) {
await this.loadElementFromURL(url);
// https://github.com/puppeteer/puppeteer/issues/406#issuecomment-323555639
return await page.evaluate(() => new XMLSerializer().serializeToString(document));
}
}

export const common = new CommonPage();
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { LegendItem } from '../../../../common/legend';
import { onChartRendered } from '../../../../state/actions/chart';
import { GlobalChartState } from '../../../../state/chart_state';
import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation';
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized';
Expand Down Expand Up @@ -78,6 +79,9 @@ export interface ReactiveChartStateProps {
annotationSpecs: AnnotationSpec[];
panelGeoms: PanelGeoms;
seriesTypes: Set<SeriesType>;
description?: string;
useDefaultSummary: boolean;
chartId: string;
}

interface ReactiveChartDispatchProps {
Expand Down Expand Up @@ -155,6 +159,9 @@ class XYChartComponent extends React.Component<XYChartProps> {
isChartEmpty,
chartContainerDimensions: { width, height },
seriesTypes,
description,
useDefaultSummary,
chartId,
} = this.props;

if (!initialized || isChartEmpty) {
Expand All @@ -164,7 +171,7 @@ class XYChartComponent extends React.Component<XYChartProps> {

const chartSeriesTypes =
seriesTypes.size > 1 ? `Mixed chart: ${[...seriesTypes].join(' and ')} chart` : `${[...seriesTypes]} chart`;

const chartIdDescription = `${chartId}--description`;
return (
<figure>
<canvas
Expand All @@ -178,11 +185,19 @@ class XYChartComponent extends React.Component<XYChartProps> {
}}
// eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role
role="presentation"
{...(description ? { 'aria-describedby': chartIdDescription } : {})}
>
<dl className="echScreen-reader">
<dt>Chart type</dt>
<dd>{chartSeriesTypes}</dd>
</dl>
{(description || useDefaultSummary) && (
<div className="echScreenReaderOnly">
{description && <p id={chartIdDescription}>{description}</p>}
{useDefaultSummary && (
<dl>
<dt>Chart type</dt>
<dd>{chartSeriesTypes}</dd>
</dl>
)}
</div>
)}
</canvas>
</figure>
);
Expand Down Expand Up @@ -237,6 +252,9 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
annotationSpecs: [],
panelGeoms: [],
seriesTypes: new Set(),
description: undefined,
useDefaultSummary: true,
chartId: '',
};

const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
Expand All @@ -245,11 +263,12 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
}

const { geometries, geometriesIndex } = computeSeriesGeometriesSelector(state);
const { debug, description, useDefaultSummary } = getSettingsSpecSelector(state);

return {
initialized: true,
isChartEmpty: isChartEmptySelector(state),
debug: getSettingsSpecSelector(state).debug,
debug,
geometries,
geometriesIndex,
theme: getChartThemeSelector(state),
Expand All @@ -266,6 +285,9 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
annotationSpecs: getAnnotationSpecsSelector(state),
panelGeoms: computePanelsSelectors(state),
seriesTypes: getSeriesTypes(state),
description,
useDefaultSummary,
chartId: getChartIdSelector(state),
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { Store } from 'redux';

import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs';
import { MockStore } from '../../../mocks/store/store';
import { GlobalChartState } from '../../../state/chart_state';
import { getSettingsSpecSelector } from '../../../state/selectors/get_settings_specs';

describe('custom description for screen readers', () => {
let store: Store<GlobalChartState>;
beforeEach(() => {
store = MockStore.default();
MockStore.addSpecs(
[
MockSeriesSpec.bar({
data: [
{ x: 1, y: 10 },
{ x: 2, y: 5 },
],
}),
MockGlobalSpec.settings(),
],
store,
);
});
it('should test defaults', () => {
const state = store.getState();
const { description, useDefaultSummary } = getSettingsSpecSelector(state);
expect(description).toBeUndefined();
expect(useDefaultSummary).toBeTrue();
});
it('should allow user to set a custom description for chart', () => {
MockStore.addSpecs(
[
MockGlobalSpec.settings({
description: 'This is sample Kibana data',
}),
],
store,
);
const state = store.getState();
const { description } = getSettingsSpecSelector(state);
expect(description).toBe('This is sample Kibana data');
});
it('should be able to disable generated descriptions', () => {
MockStore.addSpecs(
[
MockGlobalSpec.settings({
useDefaultSummary: false,
}),
],
store,
);
const state = store.getState();
const { useDefaultSummary } = getSettingsSpecSelector(state);
expect(useDefaultSummary).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ exports[`Chart should render the legend name test 1`] = `
<Connect(SpecsParserComponent)>
<SpecsParserComponent specParsed={[Function (anonymous)]} specUnmounted={[Function (anonymous)]}>
<Connect(SpecInstance) debug={true} rendering=\\"svg\\" showLegend={true}>
<SpecInstance debug={true} rendering=\\"svg\\" showLegend={true} upsertSpec={[Function (anonymous)]} removeSpec={[Function (anonymous)]} id=\\"__global__settings___\\" chartType=\\"global\\" specType=\\"settings\\" rotation={0} animateData={true} resizeDebounce={10} tooltip={{...}} externalPointerEvents={{...}} hideDuplicateAxes={false} baseTheme={{...}} brushAxis=\\"x\\" minBrushDelta={2} showLegendExtra={false} legendMaxDepth={Infinity} legendPosition=\\"right\\" />
<SpecInstance debug={true} rendering=\\"svg\\" showLegend={true} upsertSpec={[Function (anonymous)]} removeSpec={[Function (anonymous)]} id=\\"__global__settings___\\" chartType=\\"global\\" specType=\\"settings\\" rotation={0} animateData={true} resizeDebounce={10} tooltip={{...}} externalPointerEvents={{...}} hideDuplicateAxes={false} baseTheme={{...}} brushAxis=\\"x\\" minBrushDelta={2} useDefaultSummary={true} showLegendExtra={false} legendMaxDepth={Infinity} legendPosition=\\"right\\" />
</Connect(SpecInstance)>
<Connect(SpecInstance) id=\\"test\\" data={{...}}>
<SpecInstance id=\\"test\\" data={{...}} upsertSpec={[Function (anonymous)]} removeSpec={[Function (anonymous)]} chartType=\\"xy_axis\\" specType=\\"series\\" seriesType=\\"bar\\" groupId=\\"__global__\\" xScaleType=\\"ordinal\\" yScaleType=\\"linear\\" xAccessor=\\"x\\" yAccessors={{...}} yScaleToDataExtent={false} hideInLegend={false} enableHistogramMode={false} />
Expand All @@ -72,17 +72,19 @@ exports[`Chart should render the legend name test 1`] = `
</Crosshair>
</Connect(Crosshair)>
<Connect(XYChart) forwardStageRef={{...}}>
<XYChart forwardStageRef={{...}} initialized={true} isChartEmpty={false} debug={true} geometries={{...}} geometriesIndex={{...}} theme={{...}} chartContainerDimensions={{...}} highlightedLegendItem={[undefined]} rotation={0} renderingArea={{...}} chartTransform={{...}} axesSpecs={{...}} perPanelAxisGeoms={{...}} perPanelGridLines={{...}} axesStyles={{...}} annotationDimensions={{...}} annotationSpecs={{...}} panelGeoms={{...}} seriesTypes={{...}} onChartRendered={[Function (anonymous)]}>
<XYChart forwardStageRef={{...}} initialized={true} isChartEmpty={false} debug={true} geometries={{...}} geometriesIndex={{...}} theme={{...}} chartContainerDimensions={{...}} highlightedLegendItem={[undefined]} rotation={0} renderingArea={{...}} chartTransform={{...}} axesSpecs={{...}} perPanelAxisGeoms={{...}} perPanelGridLines={{...}} axesStyles={{...}} annotationDimensions={{...}} annotationSpecs={{...}} panelGeoms={{...}} seriesTypes={{...}} description={[undefined]} useDefaultSummary={true} chartId=\\"chart1\\" onChartRendered={[Function (anonymous)]}>
<figure>
<canvas className=\\"echCanvasRenderer\\" width={150} height={200} style={{...}} role=\\"presentation\\">
<dl className=\\"echScreen-reader\\">
<dt>
Chart type
</dt>
<dd>
bar chart
</dd>
</dl>
<div className=\\"echScreenReaderOnly\\">
<dl>
<dt>
Chart type
</dt>
<dd>
bar chart
</dd>
</dl>
</div>
</canvas>
</figure>
</XYChart>
Expand Down
1 change: 1 addition & 0 deletions packages/osd-charts/src/specs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export const DEFAULT_SETTINGS_SPEC: SettingsSpec = {
baseTheme: LIGHT_THEME,
brushAxis: BrushAxis.X,
minBrushDelta: 2,
useDefaultSummary: true,

...DEFAULT_LEGEND_CONFIG,
};
13 changes: 12 additions & 1 deletion packages/osd-charts/src/specs/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,15 @@ export interface SettingsSpec extends Spec, LegendSpec {
* Render component for no results UI
*/
noResults?: ComponentType | ReactChild;
/**
* User can provide a custom description to be read by a screen reader about their chart
*/
description?: string;
/**
* Disable the automated charts series types from being provided for screen readers
* @defaultValue true
*/
useDefaultSummary: boolean;
}

/**
Expand Down Expand Up @@ -608,7 +617,9 @@ export type DefaultSettingsProps =
| 'showLegend'
| 'showLegendExtra'
| 'legendPosition'
| 'legendMaxDepth';
| 'legendMaxDepth'
| 'description'
| 'useDefaultSummary';

/** @public */
export type SettingsSpecProps = Partial<Omit<SettingsSpec, 'chartType' | 'specType' | 'id'>>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { boolean, text } from '@storybook/addon-knobs';
import React from 'react';

import { AreaSeries, Chart, ScaleType, Settings } from '../../src';
import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana';

export const Example = () => {
const automatedSeries = boolean('Use the default generated series types of charts for screen readers', true);
const customDescriptionForScreenReaders = text('custom description for screen readers', '');
return (
<Chart className="story-chart">
<Settings description={customDescriptionForScreenReaders} useDefaultSummary={automatedSeries} />
<AreaSeries
id="area"
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data}
/>
</Chart>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export { Example as chromePathBugFix } from './2_chrome_path_bug_fix';
export { Example as noAxesAnnotationBugFix } from './3_no_axes_annotation';
export { Example as filterZerosInLogFitDomain } from './4_filter_zero_values_log';
export { Example as legendScrollBarSizing } from './5_legend_scroll_bar_sizing';
export { Example as addCustomDescription } from './6_a11y_custom_description';

0 comments on commit a0020ba

Please sign in to comment.