Skip to content

Commit

Permalink
feat(a11y): add alt text for all chart types (opensearch-project#1118)
Browse files Browse the repository at this point in the history
  • Loading branch information
rshen91 committed May 14, 2021
1 parent b7a68ea commit e1c7489
Show file tree
Hide file tree
Showing 25 changed files with 376 additions and 88 deletions.
3 changes: 2 additions & 1 deletion packages/osd-charts/playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@

import React from 'react';

import { Chart, AreaSeries, LineSeries, BarSeries, ScaleType } from '../src';
import { Chart, AreaSeries, LineSeries, BarSeries, ScaleType, Settings } from '../src';

export class Playground extends React.Component {
render() {
return (
<div className="App">
<Chart size={[500, 200]}>
<Settings ariaLabel="This is a custom aria-label" ariaLabelledBy="labeled by here" />
<AreaSeries
id="lines"
name="test2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ import React, { MouseEvent, RefObject } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';

import { ScreenReaderSummary } from '../../../../components/accessibility';
import { onChartRendered } from '../../../../state/actions/chart';
import { GlobalChartState } from '../../../../state/chart_state';
import {
A11ySettings,
DEFAULT_A11Y_SETTINGS,
getA11ySettingsSelector,
} from '../../../../state/selectors/get_accessibility_config';
import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized';
import { Dimensions } from '../../../../utils/dimensions';
import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
Expand All @@ -33,6 +39,7 @@ interface ReactiveChartStateProps {
initialized: boolean;
geometries: ShapeViewModel;
chartContainerDimensions: Dimensions;
a11ySettings: A11ySettings;
}

interface ReactiveChartDispatchProps {
Expand Down Expand Up @@ -104,23 +111,30 @@ class Component extends React.Component<Props> {
initialized,
chartContainerDimensions: { width, height },
forwardStageRef,
a11ySettings,
} = this.props;
if (!initialized || width === 0 || height === 0) {
return null;
}

return (
<canvas
ref={forwardStageRef}
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
onMouseMove={this.handleMouseMove.bind(this)}
style={{
width,
height,
}}
/>
<figure aria-labelledby={a11ySettings.labelId} aria-describedby={a11ySettings.descriptionId}>
<canvas
ref={forwardStageRef}
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
onMouseMove={this.handleMouseMove.bind(this)}
style={{
width,
height,
}}
// eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role
role="presentation"
>
<ScreenReaderSummary />
</canvas>
</figure>
);
}

Expand Down Expand Up @@ -157,6 +171,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
left: 0,
top: 0,
},
a11ySettings: DEFAULT_A11Y_SETTINGS,
};

const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
Expand All @@ -167,6 +182,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
initialized: true,
geometries: geometries(state),
chartContainerDimensions: state.parentDimensions,
a11ySettings: getA11ySettingsSelector(state),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { LegendItemLabel } from '../../../state/selectors/get_legend_items_label
import { DebugState } from '../../../state/types';
import { Dimensions } from '../../../utils/dimensions';
import { Goal } from '../renderer/canvas/connected_component';
import { getChartTypeDescriptionSelector } from './selectors/get_chart_type_description';
import { getSpecOrNull } from './selectors/goal_spec';
import { isTooltipVisibleSelector } from './selectors/is_tooltip_visible';
import { createOnElementClickCaller } from './selectors/on_element_click_caller';
Expand Down Expand Up @@ -120,6 +121,10 @@ export class GoalState implements InternalChartState {
this.onElementClickCaller(globalState);
}

getChartTypeDescription(globalState: GlobalChartState) {
return getChartTypeDescriptionSelector(globalState);
}

// TODO
getProjectionContainerArea(): Dimensions {
return { width: 0, height: 0, top: 0, left: 0 };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { getSpecOrNull } from './goal_spec';

/** @internal */
export const getChartTypeDescriptionSelector = createCachedSelector([getSpecOrNull], (spec) => {
return `${spec?.subtype ?? 'goal'} chart`;
})(getChartIdSelector);
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ import React, { RefObject } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';

import { ScreenReaderSummary } from '../../../../components/accessibility';
import { onChartRendered } from '../../../../state/actions/chart';
import { GlobalChartState } from '../../../../state/chart_state';
import {
A11ySettings,
DEFAULT_A11Y_SETTINGS,
getA11ySettingsSelector,
} from '../../../../state/selectors/get_accessibility_config';
import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized';
import { Dimensions } from '../../../../utils/dimensions';
import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
Expand All @@ -34,6 +40,7 @@ interface ReactiveChartStateProps {
initialized: boolean;
geometries: ShapeViewModel;
chartContainerDimensions: Dimensions;
a11ySettings: A11ySettings;
}

interface ReactiveChartDispatchProps {
Expand Down Expand Up @@ -97,26 +104,34 @@ class Component extends React.Component<Props> {
}
}

// eslint-disable-next-line @typescript-eslint/member-ordering
render() {
const {
initialized,
chartContainerDimensions: { width, height },
forwardStageRef,
a11ySettings,
} = this.props;
if (!initialized || width === 0 || height === 0) {
return null;
}
return (
<canvas
ref={forwardStageRef}
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
style={{
width,
height,
}}
/>
<figure aria-labelledby={a11ySettings.labelId} aria-describedby={a11ySettings.descriptionId}>
<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"
>
<ScreenReaderSummary />
</canvas>
</figure>
);
}
}
Expand All @@ -138,6 +153,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
left: 0,
top: 0,
},
a11ySettings: DEFAULT_A11Y_SETTINGS,
};

const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
Expand All @@ -148,6 +164,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
initialized: true,
geometries: geometries(state),
chartContainerDimensions: getHeatmapContainerSizeSelector(state),
a11ySettings: getA11ySettingsSelector(state),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ export class HeatmapState implements InternalChartState {
return getDebugStateSelector(globalState);
}

getChartTypeDescription() {
return 'Heatmap chart';
}

eventCallbacks(globalState: GlobalChartState) {
this.onElementOverCaller(globalState);
this.onElementOutCaller(globalState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ import React, { MouseEvent, RefObject } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';

import { ScreenReaderSummary } from '../../../../components/accessibility';
import { clearCanvas } from '../../../../renderers/canvas';
import { onChartRendered } from '../../../../state/actions/chart';
import { ChartId, GlobalChartState } from '../../../../state/chart_state';
import {
A11ySettings,
DEFAULT_A11Y_SETTINGS,
getA11ySettingsSelector,
} from '../../../../state/selectors/get_accessibility_config';
import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized';
Expand Down Expand Up @@ -59,6 +65,7 @@ interface ReactiveChartStateProps {
multiGeometries: ShapeViewModel[];
chartContainerDimensions: Dimensions;
chartId: ChartId;
a11ySettings: A11ySettings;
}

interface ReactiveChartDispatchProps {
Expand Down Expand Up @@ -141,23 +148,29 @@ class PartitionComponent extends React.Component<PartitionProps> {
forwardStageRef,
initialized,
chartContainerDimensions: { width, height },
a11ySettings,
} = this.props;
if (!initialized || width === 0 || height === 0) {
return null;
}

return (
<canvas
ref={forwardStageRef}
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
onMouseMove={this.handleMouseMove.bind(this)}
style={{
width,
height,
}}
/>
<figure aria-labelledby={a11ySettings.labelId} aria-describedby={a11ySettings.descriptionId}>
<canvas
ref={forwardStageRef}
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
onMouseMove={this.handleMouseMove.bind(this)}
style={{
width,
height,
}}
// eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role
role="presentation"
>
<ScreenReaderSummary />
</canvas>
</figure>
);
}

Expand Down Expand Up @@ -205,6 +218,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
left: 0,
top: 0,
},
a11ySettings: DEFAULT_A11Y_SETTINGS,
};

const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
Expand All @@ -219,6 +233,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
chartContainerDimensions: getChartContainerDimensionsSelector(state),
geometriesFoci: partitionDrilldownFocus(state),
chartId: getChartIdSelector(state),
a11ySettings: getA11ySettingsSelector(state),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { DebugState } from '../../../state/types';
import { Dimensions } from '../../../utils/dimensions';
import { render } from '../renderer/dom/layered_partition_chart';
import { computeLegendSelector } from './selectors/compute_legend';
import { getChartTypeDescriptionSelector } from './selectors/get_chart_type_description';
import { getDebugStateSelector } from './selectors/get_debug_state';
import { getLegendItemsExtra } from './selectors/get_legend_items_extra';
import { getLegendItemsLabels } from './selectors/get_legend_items_labels';
Expand Down Expand Up @@ -134,4 +135,8 @@ export class PartitionState implements InternalChartState {
getDebugState(state: GlobalChartState): DebugState {
return getDebugStateSelector(state);
}

getChartTypeDescription(state: GlobalChartState): string {
return getChartTypeDescriptionSelector(state);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { getPartitionSpec } from './partition_spec';

/** @internal */
export const getChartTypeDescriptionSelector = createCachedSelector([getPartitionSpec], (partitionSpec): string => {
return `${partitionSpec?.config.partitionLayout} chart` ?? 'Partition chart';
})(getChartIdSelector);
Loading

0 comments on commit e1c7489

Please sign in to comment.