Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
128 changes: 128 additions & 0 deletions docs/data/charts/accessibility/KeyboardNavigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as React from 'react';
import Stack from '@mui/material/Stack';
import Button from '@mui/material/Button';
import Select from '@mui/material/Select';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import MenuItem from '@mui/material/MenuItem';
import { ScatterChart } from '@mui/x-charts/ScatterChart';
import { BarChart } from '@mui/x-charts/BarChart';
import { LineChart } from '@mui/x-charts/LineChart';
import { PieChart } from '@mui/x-charts/PieChart';
import { data } from './randomData';

const scatterSeries = [
{
label: 'Series A',
data: data.map((v) => ({ x: v.x1, y: v.y1, id: v.id })),
},
{
label: 'Series B',
data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
},
];

const series = [
{ label: 'Series A', data: data.map((p) => p.y1) },
{ label: 'Series B', data: data.map((p) => p.y2) },
];

export default function KeyboardNavigation() {
const [chartType, setChartType] = React.useState('line');
const svgRef = React.useRef(null);

const handleChange = (event) => setChartType(event.target.value);

return (
<Stack width="100%" sx={{ display: 'block' }}>
<Stack
width="100%"
direction="row"
gap={2}
justifyContent="center"
sx={{ mb: 1 }}
>
<FormControl sx={{ minWidth: 200 }}>
<InputLabel id="chart-type-label">Chart Type</InputLabel>
<Select
labelId="chart-type-label"
id="chart-type-select"
value={chartType}
label="Chart Type"
onChange={handleChange}
>
<MenuItem value="scatter">Scatter</MenuItem>
<MenuItem value="line">Line</MenuItem>
<MenuItem value="bar">Bar</MenuItem>
<MenuItem value="pie">Pie</MenuItem>
</Select>
</FormControl>
<Button onClick={() => svgRef.current?.focus()} variant="contained">
Focus chart
</Button>
</Stack>
<Chart key={chartType} svgRef={svgRef} type={chartType} />
</Stack>
);
}

function Chart({ svgRef, type }) {
switch (type) {
case 'scatter':
return (
<ScatterChart
ref={svgRef}
enableKeyboardNavigation
height={300}
series={scatterSeries}
/>
);

case 'line':
return (
<LineChart
ref={svgRef}
enableKeyboardNavigation
height={300}
xAxis={[{ data: data.map((p) => p.x1).toSorted((a, b) => a - b) }]}
series={series}
/>
);

case 'bar':
return (
<BarChart
ref={svgRef}
enableKeyboardNavigation
height={300}
xAxis={[
{ data: data.map((p) => Math.round(p.x1)).toSorted((a, b) => a - b) },
]}
series={series}
/>
);

case 'pie':
return (
<PieChart
ref={svgRef}
enableKeyboardNavigation
series={[
{
arcLabel: 'value',
data: [
{ id: 0, value: 10, label: 'series A' },
{ id: 1, value: 15, label: 'series B' },
{ id: 2, value: 20, label: 'series C' },
],
},
]}
height={300}
hideLegend={false}
/>
);

default:
throw new Error(`Unknown chart type: ${type}`);
}
}
133 changes: 133 additions & 0 deletions docs/data/charts/accessibility/KeyboardNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import * as React from 'react';
import Stack from '@mui/material/Stack';
import Button from '@mui/material/Button';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import MenuItem from '@mui/material/MenuItem';
import { ScatterChart } from '@mui/x-charts/ScatterChart';
import { BarChart } from '@mui/x-charts/BarChart';
import { LineChart } from '@mui/x-charts/LineChart';
import { PieChart } from '@mui/x-charts/PieChart';
import { data } from './randomData';

const scatterSeries = [
{
label: 'Series A',
data: data.map((v) => ({ x: v.x1, y: v.y1, id: v.id })),
},
{
label: 'Series B',
data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
},
];
const series = [
{ label: 'Series A', data: data.map((p) => p.y1) },
{ label: 'Series B', data: data.map((p) => p.y2) },
];

type ChartType = 'scatter' | 'line' | 'bar' | 'pie';

export default function KeyboardNavigation() {
const [chartType, setChartType] = React.useState<ChartType>('line');
const svgRef = React.useRef<SVGSVGElement>(null);

const handleChange = (event: SelectChangeEvent) =>
setChartType(event.target.value as ChartType);

return (
<Stack width="100%" sx={{ display: 'block' }}>
<Stack
width="100%"
direction="row"
gap={2}
justifyContent="center"
sx={{ mb: 1 }}
>
<FormControl sx={{ minWidth: 200 }}>
<InputLabel id="chart-type-label">Chart Type</InputLabel>
<Select
labelId="chart-type-label"
id="chart-type-select"
value={chartType}
label="Chart Type"
onChange={handleChange}
>
<MenuItem value="scatter">Scatter</MenuItem>
<MenuItem value="line">Line</MenuItem>
<MenuItem value="bar">Bar</MenuItem>
<MenuItem value="pie">Pie</MenuItem>
</Select>
</FormControl>
<Button onClick={() => svgRef.current?.focus()} variant="contained">
Focus chart
</Button>
</Stack>
<Chart key={chartType} svgRef={svgRef} type={chartType} />
</Stack>
);
}

function Chart<T extends ChartType = ChartType>({
svgRef,
type,
}: {
svgRef: React.RefObject<SVGSVGElement | null>;
type: T;
}) {
switch (type) {
case 'scatter':
return (
<ScatterChart
ref={svgRef}
enableKeyboardNavigation
height={300}
series={scatterSeries}
/>
);
case 'line':
return (
<LineChart
ref={svgRef}
enableKeyboardNavigation
height={300}
xAxis={[{ data: data.map((p) => p.x1).toSorted((a, b) => a - b) }]}
series={series}
/>
);
case 'bar':
return (
<BarChart
ref={svgRef}
enableKeyboardNavigation
height={300}
xAxis={[
{ data: data.map((p) => Math.round(p.x1)).toSorted((a, b) => a - b) },
]}
series={series}
/>
);
case 'pie':
return (
<PieChart
ref={svgRef}
enableKeyboardNavigation
series={[
{
arcLabel: 'value',
data: [
{ id: 0, value: 10, label: 'series A' },
{ id: 1, value: 15, label: 'series B' },
{ id: 2, value: 20, label: 'series C' },
],
},
]}
height={300}
hideLegend={false}
/>
);

default:
throw new Error(`Unknown chart type: ${type}`);
}
}
82 changes: 82 additions & 0 deletions docs/data/charts/accessibility/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
productId: x-charts
title: Charts - Accessibility
packageName: '@mui/x-charts'
---

# Accessibility

<p class="description">Learn how the Charts implement accessibility features and guidelines, including keyboard navigation that follows international standards.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p class="description">Learn how the Charts implement accessibility features and guidelines, including keyboard navigation that follows international standards.</p>
<p class="description">Learn how Charts implement accessibility features and guidelines, including keyboard navigation that follows international standards.</p>


:::info
A common misconception about accessibility is to only consider blind people and the screen reader.
But there are other disability to consider, like:

- **Color blindness**, making it hard to distinguish different series, or low contrast elements.
- **Motion disability**, making it hard to open the tooltip on a given item.
- **Cognitive disability**, making it hard to focus your attention on some details.
- **Vestibular dysfunction**, making you uncomfortable with animations.

:::

## Guidelines

Common conformance guidelines for accessibility include:

- Globally accepted standard: [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/)
- US:
- [ADA](https://www.ada.gov/) - US Department of Justice

Check warning on line 28 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.We] Try to avoid using first-person plural like 'US'. Raw Output: {"message": "[Google.We] Try to avoid using first-person plural like 'US'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 28, "column": 35}}}, "severity": "WARNING"}
- [Section 508](https://www.section508.gov/) - US federal agencies

Check warning on line 29 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.We] Try to avoid using first-person plural like 'US'. Raw Output: {"message": "[Google.We] Try to avoid using first-person plural like 'US'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 29, "column": 50}}}, "severity": "WARNING"}
- Europe: [EAA](https://employment-social-affairs.ec.europa.eu/policies-and-activities/social-protection-social-inclusion/persons-disabilities/union-equality-strategy-rights-persons-disabilities-2021-2030/european-accessibility-act_en) (European Accessibility Act)

WCAG 2.1 has three levels of conformance: A, AA, and AAA.
Level AA exceeds the basic criteria for accessibility and is a common target for most organizations, so this is what this library aims to support.

The WAI-ARIA Authoring Practices includes examples on [Tooltip](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/).

## Animation

Some charts have animations when rendering or when data updates.
For users with vestibular motion disorders those animations can be problematic.
By default animations are toggled based on the [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media feature.

<!--
## Screen reader compatibility

Date and Time Pickers use ARIA roles and robust focus management across the interactive elements to convey the necessary information to users, being optimized for use with assistive technologies.
-->

## Keyboard support

:::warning
This feature is under development.
The way keyboard interaction is visualized will evolve.

Check warning on line 53 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.Will] Avoid using 'will'. Raw Output: {"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 53, "column": 44}}}, "severity": "WARNING"}

For example the element highlight, or tooltip will be impacted by the feature.

Check warning on line 55 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.Will] Avoid using 'will'. Raw Output: {"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 55, "column": 47}}}, "severity": "WARNING"}
Those modifications will not be considered as breaking changes and so be added during minor or patch versions.

Check warning on line 56 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.Will] Avoid using 'will'. Raw Output: {"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 56, "column": 21}}}, "severity": "WARNING"}
:::

Set `enableKeyboardNavigation` to `true` to enable the keyboard navigation on your charts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a global enable/disable toggle for this? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a sentence about how to use theme provider to do so

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a demo?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

You can also enable it globally using [theme default props](/material-ui/customization/theme-components/#theme-default-props)

```js
components: {
MuiChartDataProvider: {
defaultProps: {
enableKeyboardNavigation: true
},
},
}
```

{{"demo": "KeyboardNavigation.js"}}

This feature is currently supported by line, bar, pie, scatter, and sparkline charts.

This makes the SVG component focusable thanks to [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex).
When focused, the chart highlights a value item that can be modified with arrow navigation.

| Keys | Description |
| --------------------------------------------------------------------: | :---------------------------- |
| <kbd class="key">Arrow Left</kbd>, <kbd class="key">Arrow Right</kbd> | Moves focus inside the series |
| <kbd class="key">Arrow Up</kbd>, <kbd class="key">Arrow Down</kbd> | Move focus between series |
Loading
Loading