Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(a11y): add data table for screen readers (sunburst, treemap, icicle, flame) #1155

Merged
merged 63 commits into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
ebbee47
feat: add partition data table for screen readers
rshen91 May 10, 2021
d70b995
feat: add percentage row
rshen91 May 11, 2021
b7a030d
fix: update chart api
rshen91 May 11, 2021
8340b51
feat: add screen reader data data structure
rshen91 May 13, 2021
863d6dc
feat: add screen reader data table to partitions
rshen91 May 13, 2021
547c7af
feat: refactor utillity function and format data values
rshen91 May 13, 2021
2acc45e
fix: remove icicle and flame from getscreenreaderData check
rshen91 May 14, 2021
fcd3fc8
test: update tests
rshen91 May 14, 2021
15132c7
fix: add catch for large data charts
rshen91 May 17, 2021
51a8be3
fix: fix conditional within td tag to display value.valueText
rshen91 May 18, 2021
b9b7dfd
refactor: add code review clean up
rshen91 May 19, 2021
7cb0d70
fix: update per code review
rshen91 May 19, 2021
7e69878
test: fix tests
rshen91 May 19, 2021
d070a6c
feat: add incremental table rows
rshen91 May 24, 2021
cdef1c2
fix: fix tr table issues
rshen91 May 24, 2021
6f74271
style: clean up code
rshen91 May 24, 2021
99a6d61
fix: fix conditional tfoot for show more cells
rshen91 May 25, 2021
a14cc62
fix: clean up code
rshen91 May 25, 2021
060d4ff
fix: generate more data on button click
rshen91 May 25, 2021
77fce3a
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 May 25, 2021
8763b91
refactor: improve button functionality and code review changes
rshen91 May 26, 2021
46cb755
fix: clean up code
rshen91 May 26, 2021
df537b4
test: add selector unit test
rshen91 May 26, 2021
691b89d
test: add data table tests
rshen91 May 27, 2021
e5f141e
fix: update configMaxCount for flame charts
rshen91 May 27, 2021
d39ab17
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 May 28, 2021
4dd1136
feat: some code review changes
rshen91 May 28, 2021
e253fb3
feat: add some focus managemetn
rshen91 May 28, 2021
71050c0
fix: code review feedback
rshen91 May 28, 2021
04edce2
fix: update tests
rshen91 May 28, 2021
aebfe90
refactor: add parent name to data table
rshen91 Jun 1, 2021
e654f11
feat: code review changes
rshen91 Jun 1, 2021
97b5b57
fix: make sure to not show button at end of data
rshen91 Jun 2, 2021
6c984bb
fix: refactor renderTableContent
rshen91 Jun 2, 2021
e1cf63b
refactor: trying to fix focus management after handleMoreData
rshen91 Jun 3, 2021
554c3f2
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 3, 2021
12dd739
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 3, 2021
654668a
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 3, 2021
adb7bbe
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 4, 2021
fa0c61c
feat: add conditional logic for moreThanOneLayer
rshen91 Jun 4, 2021
9fba9c1
test: update nested pie slices
rshen91 Jun 4, 2021
204abef
refactor: remove formatter from component
rshen91 Jun 4, 2021
793b81f
fix: clean up legend labels for partitions
rshen91 Jun 4, 2021
a65028a
feat: improve focus management
rshen91 Jun 4, 2021
ef443b0
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 4, 2021
17568c6
fix: update valueText formatter
rshen91 Jun 4, 2021
49165d0
test: update tests
rshen91 Jun 6, 2021
dd1ba26
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 7, 2021
b180214
refactor: update table generation
markov00 Jun 7, 2021
42b4f0e
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 7, 2021
3cd61db
refactor: small storybook changes and refactor legend labels
rshen91 Jun 7, 2021
be561f9
fix: getLegendItemsLabels uses all small multiple panel data for the …
monfera Jun 8, 2021
228bc32
chore: removed superseded function
monfera Jun 8, 2021
c3a361d
feat: add sort and update tests
rshen91 Jun 8, 2021
d31fad8
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 8, 2021
5646771
Merge remote-tracking branch 'origin/sunburst-a11y-slices' into sunbu…
rshen91 Jun 8, 2021
3e5cd36
feat: update table pagination
rshen91 Jun 8, 2021
b0b02fc
fix: code review
rshen91 Jun 8, 2021
82e8219
feat: add colSpan
rshen91 Jun 8, 2021
5dabaea
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 8, 2021
f929208
fix: merge commit
rshen91 Jun 9, 2021
b019245
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 9, 2021
f98dee7
Merge remote-tracking branch 'upstream/master' into sunburst-a11y-slices
rshen91 Jun 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,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' | 'ariaUseDefaultSummary' | 'ariaLabelHeadingLevel';
export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'debug' | 'tooltip' | 'theme' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents' | 'showLegend' | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth' | 'ariaUseDefaultSummary' | 'ariaLabelHeadingLevel' | 'ariaTableCaption';

// @public (undocumented)
export const DEPTH_KEY = "depth";
Expand Down Expand Up @@ -1762,6 +1762,7 @@ export interface SettingsSpec extends Spec, LegendSpec {
ariaLabel?: string;
ariaLabelHeadingLevel: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p';
ariaLabelledBy?: string;
ariaTableCaption?: string;
ariaUseDefaultSummary: boolean;
baseTheme?: Theme;
brushAxis?: BrushAxis;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function ratioValueGetter(node: ShapeTreeNode): number {

/** @public */
export const VALUE_GETTERS = Object.freeze({ percent: percentValueGetter, ratio: ratioValueGetter } as const);

/** @public */
export type ValueGetterName = keyof typeof VALUE_GETTERS;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ function flatSlicesNames(
return [];
}

for (let i = 0; i < tree.length; i++) {
const branch = tree[i];
const arrayNode = branch[1];
const key = branch[0];

for (const [key, arrayNode] of tree) {
// format the key with the layer formatter
const layer = layers[depth - 1];
const formatter = layer?.nodeLabel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ describe('Retain hierarchy even with arbitrary names', () => {
MockStore.addSpecs(
[
MockGlobalSpec.settings({ showLegend: true }),
MockSeriesSpec.sunburst({ ...specJSON, data: [{ cat1: 'A', cat2: 'A', val: 1 }] }),
MockSeriesSpec.sunburst({
...specJSON,
data: [{ cat1: 'A', cat2: 'A', val: 1, percentage: '100%', valueText: 1 }],
}),
],
store,
);
Expand All @@ -94,7 +97,10 @@ describe('Retain hierarchy even with arbitrary names', () => {
MockStore.addSpecs(
[
MockGlobalSpec.settings({ showLegend: true }),
MockSeriesSpec.sunburst({ ...specJSON, data: [{ cat1: 'C', cat2: 'B', val: 1 }] }),
MockSeriesSpec.sunburst({
...specJSON,
data: [{ cat1: 'C', cat2: 'B', val: 1, parentName: 'A', percentage: '100%', valueText: '1' }],
}),
],
store,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';

import { ScreenReaderSummary } from '../../../../components/accessibility';
import { ScreenReaderPartitionTable } from '../../../../components/accessibility/partitions_data_table';
import { clearCanvas } from '../../../../renderers/canvas';
import { SettingsSpec } from '../../../../specs/settings';
import { onChartRendered } from '../../../../state/actions/chart';
import { ChartId, GlobalChartState } from '../../../../state/chart_state';
import {
Expand All @@ -33,6 +35,7 @@ import {
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';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { Dimensions } from '../../../../utils/dimensions';
import { MODEL_KEY } from '../../layout/config';
import {
Expand Down Expand Up @@ -66,6 +69,7 @@ interface ReactiveChartStateProps {
chartContainerDimensions: Dimensions;
chartId: ChartId;
a11ySettings: A11ySettings;
debug: SettingsSpec['debug'];
}

interface ReactiveChartDispatchProps {
Expand Down Expand Up @@ -149,6 +153,7 @@ class PartitionComponent extends React.Component<PartitionProps> {
initialized,
chartContainerDimensions: { width, height },
a11ySettings,
debug,
} = this.props;
if (!initialized || width === 0 || height === 0) {
return null;
Expand All @@ -169,7 +174,9 @@ class PartitionComponent extends React.Component<PartitionProps> {
role="presentation"
>
<ScreenReaderSummary />
{!debug && <ScreenReaderPartitionTable />}
</canvas>
{debug && <ScreenReaderPartitionTable />}
</figure>
);
}
Expand Down Expand Up @@ -219,6 +226,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
top: 0,
},
a11ySettings: DEFAULT_A11Y_SETTINGS,
debug: false,
};

const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
Expand All @@ -234,6 +242,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
geometriesFoci: partitionDrilldownFocus(state),
chartId: getChartIdSelector(state),
a11ySettings: getA11ySettingsSelector(state),
debug: getSettingsSpecSelector(state).debug,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,3 @@ export const partitionDrilldownFocus = createCachedSelector(
return { currentFocusX0, currentFocusX1, prevFocusX0, prevFocusX1, smAccessorValue, index, innerIndex };
}),
)((state) => state.chartId);

/** @internal */
export const partitionGeometries = createCachedSelector(
[partitionMultiGeometries],
(multiGeometries: ShapeViewModel[]) => {
return [
multiGeometries.length > 0 // singleton!
? multiGeometries[0]
: nullShapeViewModel(),
];
},
)(getChartIdSelector);
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ import { getTrees } from './tree';
export const getLegendItemsLabels = createCachedSelector(
[getPartitionSpecs, getSettingsSpecSelector, getTrees],
(specs, { legendMaxDepth, showLegend }, trees): LegendItemLabel[] =>
specs.flatMap((spec) => (showLegend ? getLegendLabels(spec.layers, trees[0].tree, legendMaxDepth) : [])), // singleton! wrt inner small multiples
specs.flatMap((spec) =>
showLegend ? trees.flatMap(({ tree }) => getLegendLabels(spec.layers, tree, legendMaxDepth)) : [],
),
)(getChartIdSelector);
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 { MockSeriesSpec } from '../../../../mocks/specs/specs';
import { MockStore } from '../../../../mocks/store';
import { GlobalChartState } from '../../../../state/chart_state';
import { PrimitiveValue } from '../../layout/utils/group_by_rollup';
import { getScreenReaderDataSelector } from './get_screen_reader_data';

describe('Get screen reader data', () => {
type TestDatum = [string, string, string, number];
const spec1 = MockSeriesSpec.sunburst({
data: [
['aaa', 'aa', '1', 1],
['aaa', 'aa', '3', 1],
['aaa', 'bb', '4', 1],
],
valueAccessor: (d: TestDatum) => d[3],
layers: [
{
groupByRollup: (datum: TestDatum) => datum[0],
nodeLabel: (d: PrimitiveValue) => String(d),
},
{
groupByRollup: (datum: TestDatum) => datum[1],
nodeLabel: (d: PrimitiveValue) => String(d),
},
{
groupByRollup: (datum: TestDatum) => datum[2],
nodeLabel: (d: PrimitiveValue) => String(d),
},
],
});

const specNoSlice = MockSeriesSpec.sunburst({
data: [],
valueAccessor: (d: TestDatum) => d[3],
layers: [
{
groupByRollup: (datum: TestDatum) => datum[0],
nodeLabel: (d: PrimitiveValue) => String(d),
},
{
groupByRollup: (datum: TestDatum) => datum[1],
nodeLabel: (d: PrimitiveValue) => String(d),
},
{
groupByRollup: (datum: TestDatum) => datum[2],
nodeLabel: (d: PrimitiveValue) => String(d),
},
],
});
let store: Store<GlobalChartState>;

beforeEach(() => {
store = MockStore.default();
});

it('should test defaults', () => {
MockStore.addSpecs([spec1], store);
const expected = getScreenReaderDataSelector(store.getState());
expect(expected).toEqual({
data: [
{ depth: 1, label: 'aaa', panelTitle: '', parentName: 'none', percentage: '100%', value: 3, valueText: '3' },
{ depth: 2, label: 'aa', panelTitle: '', parentName: 'aaa', percentage: '67%', value: 2, valueText: '2' },
{ depth: 3, label: '1', panelTitle: '', parentName: 'aa', percentage: '33%', value: 1, valueText: '1' },
{ depth: 3, label: '3', panelTitle: '', parentName: 'aa', percentage: '33%', value: 1, valueText: '1' },
{ depth: 2, label: 'bb', panelTitle: '', parentName: 'aaa', percentage: '33%', value: 1, valueText: '1' },
{ depth: 3, label: '4', panelTitle: '', parentName: 'bb', percentage: '33%', value: 1, valueText: '1' },
],
hasMultipleLayers: true,
isSmallMultiple: false,
});
});
it('should compute screen reader data for no slices in pie', () => {
MockStore.addSpecs([specNoSlice], store);
const expected = getScreenReaderDataSelector(store.getState());
expect(expected).toEqual({
data: [],
hasMultipleLayers: true,
isSmallMultiple: false,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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 { ShapeViewModel } from '../../layout/types/viewmodel_types';
import { STATISTICS_KEY } from '../../layout/utils/group_by_rollup';
import { PartitionSpec } from '../../specs';
import { partitionMultiGeometries } from './geometries';
import { getPartitionSpecs } from './get_partition_specs';

/** @internal */
export interface PartitionSectionData {
panelTitle?: string;
label: string;
parentName: string | undefined;
depth: number;
percentage: string;
value: number;
valueText: string;
}

/** @internal */
export interface PartitionData {
hasMultipleLayers: boolean;
isSmallMultiple: boolean;
data: PartitionSectionData[];
}

/**
* @internal
*/
const getScreenReaderDataForPartitions = (
[{ valueFormatter }]: PartitionSpec[],
shapeViewModels: ShapeViewModel[],
): PartitionSectionData[] => {
return shapeViewModels.flatMap(({ quadViewModel, layers, panelTitle }) =>
quadViewModel.map(({ depth, value, dataName, parent, path }) => {
const label = layers[depth - 1]?.nodeLabel?.(dataName) ?? dataName;
const parentValue = path.length > 1 ? path[path.length - 2].value : undefined;
const parentName =
depth > 1 && parentValue ? layers[depth - 2]?.nodeLabel?.(parentValue) ?? path[path.length - 1].value : 'none';

return {
panelTitle,
depth,
label,
parentName,
percentage: `${Math.round((value / parent[STATISTICS_KEY].globalAggregate) * 100)}%`,
value,
valueText: valueFormatter ? valueFormatter(value) : `${value}`,
};
}),
);
};

/** @internal */
export const getScreenReaderDataSelector = createCachedSelector(
[getPartitionSpecs, partitionMultiGeometries],
(specs, shapeViewModel): PartitionData => {
if (specs.length === 0) {
return {
hasMultipleLayers: false,
isSmallMultiple: false,
data: [],
};
}
return {
hasMultipleLayers: specs[0].layers.length > 1,
isSmallMultiple: shapeViewModel.length > 1,
data: getScreenReaderDataForPartitions(specs, shapeViewModel),
};
},
)(getChartIdSelector);
Loading