Alternative Representations for Charts
A visual chart is one projection of a dataset, tuned for one sense; a user who cannot see it needs a different projection of the same numbers, not an apology. This guide covers how to render a single data model as several equivalent representations — a prose summary, an accessible table, an audio rendering, and a downloadable export — so meaning survives the loss of sight.
It is a companion within the accessible interactive data visualization overview, sitting alongside the semantic, keyboard, and colour layers. Where those layers make the visual chart perceivable and operable, this section is about supplying genuinely parallel channels: outputs a blind user reaches directly, rather than a described version of a picture they cannot use. The organising principle throughout is that all four representations read from one source of truth, so they can never disagree about what the data says.
Concept overview: one dataset, four parallel channels
The failure this guide prevents is treating the visual chart as the canonical artefact and every other representation as a lossy caption of it. That framing produces a one-line alt text for a forty-point series and calls it done. The durable pattern inverts the dependency: the data model is canonical, and the visual chart, the text summary, the data table, and the audio rendering are four peers projected from it. Each is complete on its own terms, and each serves a different user need and assistive-technology path.
When to use what
No single alternative representation serves every user or every dataset. A blind analyst extracting exact figures wants the table; a screen-reader user skimming a dashboard wants the one-sentence gist first; a user exploring the shape of a signal benefits from audio. Match the representation to the user need and to the effort you can spend, using the table as a starting point.
| Representation | Best for the user who needs | Assistive path | Effort | Notes |
|---|---|---|---|---|
| Text summary | the gist — direction, extremes, outliers | any screen reader; also sighted skimmers | Low | Generate it; never hand-write per chart |
| Accessible data table | exact values and row-by-row comparison | screen-reader table navigation | Low–medium | The reliable baseline every chart should have |
| Sonification (audio) | the shape of a trend over an ordered axis | headphones; no AT required | Medium | Great for time series; weak for exact values |
| Downloadable export (CSV) | offline analysis in their own tools | spreadsheet, Braille display, custom scripts | Low | Cheap to add; hands control to the user |
| Tactile / printed export | a physical trace of the curve | swell paper, embosser | High | Specialist; usually an on-request deliverable |
Two rules fall out of the table. First, the text summary and the data table are the baseline — they are cheap, they need no special hardware, and between them they answer both “what does this say in one breath?” and “what is the exact number for March?”. Ship those for every chart before reaching for anything else. Second, sonification and tactile output are additive, not substitutive: audio conveys shape beautifully and exact values poorly, so it complements the table rather than replacing it. Never make audio the only non-visual channel.
Core concepts: derive every channel from one series
The engineering core is a single normalised data structure that each representation consumes. Deriving the summary, the table rows, the CSV, and the audio pitch sequence from the same Series guarantees they agree, and it means a data change updates all four channels through one code path.
interface Point {
x: number; // ordered axis position (time, index, category ordinal)
xLabel: string; // human, locale-aware axis label ("Mar 2026")
y: number; // the measured value
yFormatted: string; // pre-formatted, locale-aware value ("4.2M")
}
interface Series {
name: string;
unit: string;
points: Point[]; // assumed sorted by x ascending
}
// Shared derived facts every textual/audio channel reuses.
interface SeriesFacts {
min: Point;
max: Point;
first: Point;
last: Point;
direction: "rising" | "falling" | "flat";
changePct: number; // signed percentage change first -> last
}
// PERF: compute once per data update, not per representation and not per frame.
function deriveFacts(s: Series): SeriesFacts {
const pts: Point[] = s.points;
let min: Point = pts[0];
let max: Point = pts[0];
for (const p of pts) {
if (p.y < min.y) min = p;
if (p.y > max.y) max = p;
}
const first: Point = pts[0];
const last: Point = pts[pts.length - 1];
const changePct: number = first.y === 0 ? 0 : ((last.y - first.y) / Math.abs(first.y)) * 100;
const direction: SeriesFacts["direction"] =
Math.abs(changePct) < 1 ? "flat" : changePct > 0 ? "rising" : "falling";
// A11Y: these facts feed the summary sentence, the table caption, and the
// audio announcement so all three describe the data identically.
return { min, max, first, last, direction, changePct };
}
The value of centralising deriveFacts is that it removes the most common accessibility bug in multi-channel charts: three representations that quietly disagree because each recomputed “the peak” with slightly different rounding or tie-breaking. The mechanics of turning these facts into a readable paragraph are the subject of generating a text summary of a chart trend; the audio mapping is covered in sonifying a line chart with the Web Audio API; and the export-and-inline-table path is offering a downloadable data table fallback.
Implementation pattern
// One orchestrator wires all channels to a single Series.
interface ChannelHandles {
describedById: string; // id of the visually hidden summary element
tableEl: HTMLTableElement;
downloadCsv: () => void;
playAudio: (() => void) | null; // null when the axis is not ordered
}
function buildRepresentations(root: HTMLElement, series: Series): ChannelHandles {
const facts: SeriesFacts = deriveFacts(series);
// A11Y: the summary lives in the DOM as text (not an image), so it is
// translatable, selectable, and reflowable — unlike pixels on a canvas.
const summaryId = `sum-${series.name.replace(/\s+/g, "-").toLowerCase()}`;
return {
describedById: summaryId,
tableEl: renderTable(root, series), // see the data-table companion page
downloadCsv: () => downloadSeriesCsv(series),
playAudio: series.points.length > 1 ? () => sonify(series, facts) : null,
};
}
Performance and memory
The derivation work is linear and belongs at data-update time, not in the render loop. deriveFacts is a single O(n) pass; the summary sentence is O(1) once you have the facts; the table is O(n) DOM nodes built once and reused across updates via row mutation rather than teardown. None of these should run inside requestAnimationFrame. The one channel with an ongoing cost is audio, and its cost is a scheduling concern rather than a per-frame one: schedule oscillator envelopes ahead of time on the Web Audio clock instead of firing a timer per note, so playback stays glitch-free without occupying the main thread.
Memory discipline mirrors the rest of the accessibility work. A visually hidden summary node, a data table, and a set of controls are all real DOM that must be removed on chart teardown, or you leak detached nodes exactly as an un-cleaned live region does — the same failure class described in memory management in heavy charts. Create each channel’s nodes once, mutate their content on data change, and remove everything on unmount. For audio specifically, close or suspend the AudioContext when the chart unmounts; leaving contexts open is a common leak that also drains battery.
The CSV export deserves a note on scale. Building a multi-megabyte string in memory to hand to a download blob is fine up to tens of thousands of rows, but for very large series prefer streaming the serialisation or generating the file server-side, rather than blocking the main thread stringifying a million rows synchronously. For the datasets most interactive charts actually hold — hundreds to a few thousand points — a synchronous join is perfectly adequate.
Accessibility checklist
Troubleshooting
Symptom: the summary and the chart disagree about the peak. Root cause: the summary was hand-written or computed separately from the chart’s data. Fix: generate it from the same deriveFacts output the chart consumes, so both cite the identical point.
Symptom: a screen reader reads the whole data table before the user knows what the chart is. Root cause: no short summary precedes the detailed table. Fix: put the generated one-sentence gist first (via aria-describedby), and let the table be the on-demand detail.
Symptom: audio plays automatically and startles or annoys users. Root cause: playback fires on load or on hover. Fix: require an explicit play control, honour prefers-reduced-motion, and never start audio without a user gesture — browsers block it anyway.
Symptom: the CSV export opens with mangled numbers or broken characters. Root cause: raw values without quoting, or a missing UTF-8 byte-order mark. Fix: escape fields containing commas/quotes and prefix the blob with a BOM so spreadsheets detect the encoding.
Frequently Asked Questions
Do I need all four representations for every chart?
No. The text summary and the accessible data table are the baseline to ship for every chart, because they are cheap and need no special hardware. Sonification and tactile output are additive — reach for audio when the shape of an ordered signal matters, and for tactile export only on request. The rule is that a non-visual user must always have at least one complete channel, and two is comfortably robust.
Is a text summary enough on its own without the data table?
Not usually. A summary conveys the gist — direction, extremes, an outlier — but it deliberately omits most values so it stays readable. A user who needs the exact figure for a specific point still needs the table. Treat the summary as the headline and the table as the full record; ship both.
Why derive every channel from one data model instead of generating them independently?
Because independent generation is how representations drift apart: one rounds differently, another breaks ties on the peak differently, and a screen-reader user hears numbers the chart never shows. A single normalised Series with shared derived facts guarantees the summary, table, export, and audio all describe the same data, and it means one data change updates every channel through one code path.
Related
- Accessible interactive data visualization — the overview that frames the semantic, keyboard, colour, and alternative-channel layers.
- Generating a text summary of a chart trend — turning derived facts into a prose gist.
- Sonifying a line chart with the Web Audio API — mapping values to pitch over time.
- Offering a downloadable data table fallback — CSV export plus an inline table.
- Exposing chart data as an accessible data table — the screen-reader semantics of the table channel.