Problem/Motivation
Charts can render an accessible HTML "data table" alongside the chart (the "View data table" option). For pie and doughnut charts with the Chart.js library, that table is malformed:
- The Label column ("My Category") shows generic Slice 1, Slice 2, … instead of the real category names.
- The Value column ("My Value") shows the category name and the number together in a single cell, e.g. Legend 1, 17.
So the one column that should hold a clean number holds "name, value", and the column meant for the name holds a placeholder. This defeats the point of the accessible table (screen-reader users get "Slice 3 — Legend 1, 17" instead of "Legend 1 — 17").
Steps to reproduce
- Build a pie or doughnut chart with the Chart.js library and enable the data table ("View data table" /
data_markup). - View the data table under the chart.
- Observe: rows are labelled
Slice N, and each value cell contains "name, value" instead of just the value.
Root cause
For pie/doughnut, each data point arrives as a [name, value] pair (the same shape behind Issue 1). In ChartTableBuilder::buildStandardTableData(), the single-axis branch treats each point as a scalar and has no separate labels array to draw the row header from:
if ($single_axis) {
$data_points = $series_data[0]['data'] ?? [];
$labels = !empty($series_data[0]['labels']) ? $series_data[0]['labels'] : $categories;
foreach ($data_points as $index => $point) {
// $labels is empty for pie/doughnut → falls back to "Slice N"
$row_label = $labels[$index] ?? $this->t('Slice @n', ['@n' => $index + 1]);
$rows[] = [
['data' => $row_label, 'header' => TRUE, 'scope' => 'row'],
// $point is the whole [name, value] pair → rendered as "name, value"
$this->processDataPoint($point),
];
}
}
Because $series_data[0]['labels'] (and $categories) are empty for pie/doughnut, every row label falls back to Slice N; and because $point is the [name, value] pair rather than a scalar, the value cell renders the name and the number together.
Proposed resolution
When a point is a two-element list [scalar, numeric], split it: use the first element as the row label and the second as the value. This is fully backward-compatible — scalar points (line/bar/column single-axis) are untouched, since the guard only fires on the pie/doughnut pair shape.
foreach ($data_points as $index => $point) {
$row_label = $labels[$index] ?? NULL;
$value = $point;
// Pie/doughnut points arrive as [label, value] with no separate labels
// array; split them so the label and value land in their own columns.
if (is_array($point) && array_is_list($point) && count($point) === 2 && is_scalar($point[0]) && is_numeric($point[1])) {
$row_label ??= $point[0];
$value = $point[1];
}
$row_label ??= $this->t('Slice @n', ['@n' => $index + 1]);
$rows[] = [
['data' => $row_label, 'header' => TRUE, 'scope' => 'row'],
$this->processDataPoint($value),
];
}
After the change, the pie/doughnut data table has the category name in the Label column and the plain number in the Value column; the Slice N fallback still applies only when no label is available at all.
| Comment | File | Size | Author |
|---|---|---|---|
| charts-chartjs-accessible-table-pie-doughnut-label-value.patch | 1.08 KB | jrochate |
Issue fork charts-3611409
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
andileco commentedYour patch didn't properly handle NULL (point) values. Please try this. I also need to check around more, but at least the tests pass.
Comment #5
jrochate commentedI have tested on my use case (pie and doughnut) and it works great.
I have tested with one result, OK. Also tried with no label, and got Slice N as label.
Also tested against other type of charts, and they still shows the table correctly.
At least on my multivalued 2-dim charts, is working fine.
Thanks.
Comment #7
andileco commented