Skip to content

Commit

Permalink
feat(a11y): improve chart figure (opensearch-project#1104)
Browse files Browse the repository at this point in the history
  • Loading branch information
rshen91 authored Apr 12, 2021
1 parent 72c0d1b commit 373ea72
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 32 deletions.
35 changes: 19 additions & 16 deletions packages/osd-charts/.playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,32 @@

import React from 'react';

import { Chart, BarSeries, ScaleType, LineAnnotation, AnnotationDomainTypes, LineAnnotationDatum } from '../src';
import { Chart, AreaSeries, LineSeries, BarSeries, ScaleType } from '../src';

function generateAnnotationData(values: any[]): LineAnnotationDatum[] {
return values.map((value, index) => ({ dataValue: value, details: `detail-${index}` }));
}
export class Playground extends React.Component {
render() {
return (
<div className="App">
<Chart size={[500, 200]}>
<LineAnnotation
domainType={AnnotationDomainTypes.XDomain}
id="ann"
dataValues={[{ dataValue: 'bags' }]}
marker={<div style={{ background: 'red' }}>hello</div>}
// markerPosition="top"
<AreaSeries
id="lines"
name="test2"
data={[
{ x: 'trousers', y: 300, val: 1232 },
{ x: 'watches', y: 20, val: 1232 },
{ x: 'bags', y: 700, val: 1232 },
{ x: 'cocktail dresses', y: 804, val: 1232 },
]}
/>
<LineAnnotation
domainType={AnnotationDomainTypes.YDomain}
id="ann1"
dataValues={generateAnnotationData([30])}
marker={<div style={{ background: 'yellow' }}>Horizontal</div>}
// markerPosition="right"
<LineSeries
id="lines2"
name="test"
data={[
{ x: 'trousers', y: 300, val: 1232 },
{ x: 'watches', y: 20, val: 1232 },
{ x: 'bags', y: 700, val: 1232 },
{ x: 'cocktail dresses', y: 804, val: 1232 },
]}
/>
<BarSeries
id="bars"
Expand Down
45 changes: 45 additions & 0 deletions packages/osd-charts/integration/tests/accessibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 { common } from '../page_objects/common';

describe('Accessibility tree', () => {
it('should include the series types if one type of series', async () => {
const tree = await common.testAccessibilityTree(
'http://localhost:9001/iframe.html?id=annotations-lines--x-continuous-domain',
'.echCanvasRenderer',
);
// the legend has bars and lines as value.descriptions not value.name
const hasTextOfChartTypes = tree.children.filter((value) => {
return value.name === 'bar chart';
});
expect(hasTextOfChartTypes[0].name).toBe('bar chart');
});
it('should include the series types if multiple types of series', async () => {
const tree = await common.testAccessibilityTree(
'http://localhost:9001/iframe.html?id=mixed-charts--bars-and-lines',
'.echCanvasRenderer',
);
// the legend has bars and lines as value.descriptions not value.name
const hasTextOfChartTypes = tree.children.filter((value) => {
return value.name === 'Mixed chart: bar and line chart';
});
expect(hasTextOfChartTypes[0].name).toBe('Mixed chart: bar and line chart');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@ import {
import { computeSeriesGeometriesSelector } from '../../state/selectors/compute_series_geometries';
import { getAxesStylesSelector } from '../../state/selectors/get_axis_styles';
import { getHighlightedSeriesSelector } from '../../state/selectors/get_highlighted_series';
import { getSeriesTypes } from '../../state/selectors/get_series_types';
import { getAnnotationSpecsSelector, getAxisSpecsSelector } from '../../state/selectors/get_specs';
import { isChartEmptySelector } from '../../state/selectors/is_chart_empty';
import { Geometries, Transform } from '../../state/utils/types';
import { LinesGrid } from '../../utils/grid_lines';
import { IndexedGeometryMap } from '../../utils/indexed_geometry_map';
import { AxisSpec, AnnotationSpec } from '../../utils/specs';
import { AxisSpec, AnnotationSpec, SeriesType } from '../../utils/specs';
import { renderXYChartCanvas2d } from './renderers';

/** @internal */
Expand All @@ -76,6 +77,7 @@ export interface ReactiveChartStateProps {
annotationDimensions: Map<AnnotationId, AnnotationDimensions>;
annotationSpecs: AnnotationSpec[];
panelGeoms: PanelGeoms;
seriesTypes: Set<SeriesType>;
}

interface ReactiveChartDispatchProps {
Expand Down Expand Up @@ -152,27 +154,37 @@ class XYChartComponent extends React.Component<XYChartProps> {
initialized,
isChartEmpty,
chartContainerDimensions: { width, height },
seriesTypes,
} = this.props;

if (!initialized || isChartEmpty) {
this.ctx = null;
return null;
}

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

return (
<canvas
ref={forwardStageRef}
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
style={{
width,
height,
}}
aria-label="Chart"
// eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role
role="img"
/>
<figure>
<canvas
ref={forwardStageRef}
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
style={{
width,
height,
}}
// eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role
role="presentation"
>
<dl className="echScreen-reader">
<dt>Chart type</dt>
<dd>{chartSeriesTypes}</dd>
</dl>
</canvas>
</figure>
);
}
}
Expand Down Expand Up @@ -224,6 +236,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
annotationDimensions: new Map(),
annotationSpecs: [],
panelGeoms: [],
seriesTypes: new Set(),
};

const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
Expand Down Expand Up @@ -252,6 +265,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
annotationDimensions: computeAnnotationDimensionsSelector(state),
annotationSpecs: getAnnotationSpecsSelector(state),
panelGeoms: computePanelsSelectors(state),
seriesTypes: getSeriesTypes(state),
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import 'highlighter';
@import 'crosshair';
@import 'screen_reader';
@import 'annotations/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.echScreenReaderOnly {
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 createCachedSelector from 're-reselect';

import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { SeriesType } from '../../utils/specs';
import { getSeriesSpecsSelector } from './get_specs';

/** @internal */
export const getSeriesTypes = createCachedSelector(
[getSeriesSpecsSelector],
(specs): Set<SeriesType> => {
const seriesTypes = new Set<SeriesType>();
specs.forEach((value) => seriesTypes.add(value.seriesType));
return seriesTypes;
},
)(getChartIdSelector);
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +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={{...}} onChartRendered={[Function (anonymous)]}>
<canvas className=\\"echCanvasRenderer\\" width={150} height={200} style={{...}} aria-label=\\"Chart\\" role=\\"img\\" />
<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)]}>
<figure>
<canvas className=\\"echCanvasRenderer\\" width={150} height={200} style={{...}} role=\\"presentation\\">
<dl className=\\"echScreen-reader\\">
<dt>
Chart type
</dt>
<dd>
bar chart
</dd>
</dl>
</canvas>
</figure>
</XYChart>
</Connect(XYChart)>
<Connect(Tooltip) getChartContainerRef={[Function (anonymous)]}>
Expand Down

0 comments on commit 373ea72

Please sign in to comment.