Sonifying Time-Series Charts

Permalink to "Sonifying Time-Series Charts"

Sonification turns data into sound. For a time-series chart, the simplest mapping plays each point as a short tone whose pitch rises with the value, one after another along the x axis. A two-second sweep conveys the shape of a line — a steady climb, a dip in March, a spike, a plateau — faster than reading twelve values, and in a way that text summaries struggle to match for irregular patterns.

It is a supplement, not an alternative on its own: users cannot recover exact values from pitch, and some users cannot hear it or do not want it. This page covers a Web Audio implementation, the controls it needs, and where it fits among other alternatives. It belongs to data visualization & chart alternatives.

Spec reference

Permalink to "Spec reference"

The Web Audio API provides AudioContext, OscillatorNode and GainNode, enough to schedule short tones precisely. Browsers require a user gesture before an AudioContext can produce sound, which conveniently enforces the most important accessibility rule: no autoplay.

WCAG does not require sonification. The relevant criteria constrain it: SC 1.4.2 Audio Control (audio that plays for more than 3 seconds must be pausable or have volume control independent of the system) — satisfied by starting only on request and providing pause; SC 2.1.1 Keyboard for the controls; SC 1.1.1, which the text summary and table satisfy, not the sound. There is no ARIA role for sonification; the controls are ordinary buttons.

Monthly orders as pitch, selected months Bar chart of monthly order values for eight months, each labelled with the frequency it maps to under the logarithmic 220 to 880 hertz mapping in the code below. Monthly orders as pitch, selected monthsJan820 orders — 250 HzFeb910 orders — 303 HzMar760 orders — dip — 220 HzApr980 orders — 352 HzJun1100 orders — 454 HzSep1240 orders — 612 HzNov1410 orders — peak — 880 HzDec1300 orders — 696 Hz
Computed with the pitch function below: the lowest month is 220 Hz, the highest 880 Hz, and the rest fall between.

When sonification helps — and when not to

Permalink to "When sonification helps — and when not to"

It helps for trend-heavy data where shape matters more than exact values: time series, sparklines, a sensor stream, a price history. Screen reader users who have used audio graphs describe them as a fast way to decide whether a chart is worth exploring point by point.

It does not help for categorical comparisons (a bar chart of regions), for precise values, or for users who are deaf or hard of hearing, in noisy environments, or who simply prefer text. It is always an addition to the text summary and data table, never a replacement.

The misapplication to name is playing the sonification automatically when the chart receives focus. It competes with the screen reader’s own speech, cannot be stopped quickly by users who did not expect it, and — past three seconds — fails SC 1.4.2.

Annotated code example

Permalink to "Annotated code example"
<div class="sonify" role="group" aria-label="Listen to Orders per month">
  <!-- SC 1.4.2: user-initiated; the button toggles play and pause -->
  <button type="button" id="play" aria-pressed="false">Play as sound</button>
  <label for="speed">Speed</label>
  <select id="speed"><option value="0.25">Slow</option><option value="0.15" selected>Normal</option><option value="0.08">Fast</option></select>
  <p class="hint" id="sonify-hint">Low pitch is fewer orders, high pitch is more. Plays January to December.</p>
</div>
let ctx, playing = false, stopAt = 0;
const LOW = 220, HIGH = 880;                     // two octaves, comfortable range

function pitch(v, min, max) {                    // log mapping sounds more even
  const t = (v - min) / (max - min || 1);
  return LOW * Math.pow(HIGH / LOW, t);
}

async function play(values, step) {
  ctx ??= new AudioContext();                    // created on the click: no autoplay
  await ctx.resume();
  const min = Math.min(...values), max = Math.max(...values);
  const t0 = ctx.currentTime + 0.1;
  values.forEach((v, i) => {
    const osc = ctx.createOscillator(), gain = ctx.createGain();
    osc.type = 'triangle';                       // softer than square or sawtooth
    osc.frequency.value = pitch(v, min, max);
    gain.gain.setValueAtTime(0, t0 + i * step);
    gain.gain.linearRampToValueAtTime(0.2, t0 + i * step + 0.02);   // no clicks, not loud
    gain.gain.linearRampToValueAtTime(0, t0 + i * step + step * 0.9);
    osc.connect(gain).connect(ctx.destination);
    osc.start(t0 + i * step); osc.stop(t0 + (i + 1) * step);
  });
  stopAt = t0 + values.length * step;
  // Announce the frame of reference before and after (SC 1.1.1 companion)
  announce(`Playing ${values.length} months. Starts at ${fmt(values[0])}.`);
  setTimeout(() => { if (playing) finish(values); }, (stopAt - ctx.currentTime) * 1000);
}

function finish(values) {
  playing = false;
  document.getElementById('play').setAttribute('aria-pressed', 'false');
  announce(`Ended at ${fmt(values.at(-1))}. Highest ${fmt(Math.max(...values))} in November.`);
}

document.getElementById('play').addEventListener('click', async (e) => {
  const btn = e.currentTarget;
  if (playing) { await ctx.suspend(); playing = false; btn.setAttribute('aria-pressed', 'false'); return; }
  playing = true; btn.setAttribute('aria-pressed', 'true');
  play(orders, Number(document.getElementById('speed').value));
});

Pair playback with point stepping: when a user arrows through the chart as in adding keyboard navigation to SVG line charts, play that point’s tone alongside its announced value. Users then connect pitch to numbers, which makes the full sweep more meaningful.

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Event Expected behaviour Announcement
Tab to Play Button focused “Play as sound, toggle button, not pressed”
Enter Tones play January to December “Playing 12 months. Starts at 820.”
Enter again Playback pauses “pressed” → “not pressed”
Playback ends Button resets “Ended at 1,300. Highest 1,410 in November.”
Arrow through chart points Each point’s tone plays with its value “March: 760 orders” + tone
Sonification that helps and that hurts Comparison of a sonification that autoplays and replaces text against one that is user-started, controllable and supplementary. Sonification that helps and that hurts✗ HurtsStarts on focus or page loadCompetes with screen reader speechNo pause, fixed speedReplaces the text summary✓ HelpsStarts from a labelled buttonPlay and pause, speed choiceAnnounces start, end and peak in wordsSits beside summary and table
Sound is a fast way to hear shape — only when users choose it and can stop it.

Integration context

Permalink to "Integration context"

The text summary remains the primary alternative; see generating accessible text alternatives for D3 charts. Sonification is especially effective for sparklines on KPI cards, where there is no room for a full table and a one-second sweep says “rising” or “volatile” — see accessible KPI cards and sparklines.

Multi-series charts can play series one after another, or use stereo panning to place series left and right; keep the default to one series at a time, as simultaneous tones are hard to separate.

What a listener hears Timeline of a sonification session: pressing Play, hearing the start value announced, the twelve tones, and the end summary announced. What a listener hearsPress Playtoggle becomes pressedSpoken"Starts at 820."Tones12 notes, about 2 secondsSpoken"Ended at 1,300. Highest 1,410."Explorearrow through points with tonesone sonification session
Words frame the sound — the listener always knows where the sweep starts, ends and peaks.

Gotchas

Permalink to "Gotchas"

Volume. Keep gain low and ramp every note to avoid clicks. Users may have their screen reader loud; a sudden full-scale tone is painful.

Zero-range data. A flat series maps every value to the same pitch; say “flat at 820” instead of playing a drone.

Background tabs. AudioContext keeps playing in background tabs. Suspend it on visibilitychange, as with live announcements in pausing live regions when the tab is hidden.

Design system notes

Permalink to "Design system notes"

Offer sonification as an opt-in feature of the chart component — a sonify flag that adds the control group and wires point stepping to tones — with one shared audio module that enforces the pitch range, gain ceiling and suspend-on-hidden behaviour. Keep it off by default in dashboards with many charts, and let users enable it globally in their preferences.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
What is chart sonification?

Representing data as sound — typically playing each point of a line chart as a tone whose pitch rises with its value, in order along the x axis — so listeners hear the shape of the data.

Does sonification make a chart WCAG compliant?

No. WCAG is met by text alternatives such as a summary and a data table. Sonification is an optional supplement that helps some users grasp trends quickly.

Can a sonification play automatically?

It should not. Start it only when the user asks, provide pause, and remember that audio longer than three seconds must be pausable under SC 1.4.2.

Which charts are good candidates for sonification?

Time series and other ordered data where the shape matters — trends, peaks, dips, volatility. It adds little to categorical comparisons or where exact values matter.

Permalink to "Related"

← Back to Data Visualization & Chart Alternatives