AnyChart Integration

MAIDR makes AnyChart charts accessible through audio sonification, text descriptions, braille output, and keyboard navigation. The adapter exposes a one-line binder you call after the chart has been drawn.

Quick Start

Load AnyChart, MAIDR's core runtime, and the AnyChart adapter, then call bindAnyChart():

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My AnyChart Chart</title>
    <!-- 1. Load AnyChart -->
    <script src="https://cdn.anychart.com/releases/8.13.0/js/anychart-base.min.js"></script>
    <!-- 2. Load the MAIDR core runtime -->
    <script src="https://cdn.jsdelivr.net/npm/maidr/dist/maidr.js"></script>
  </head>
  <body>
    <div id="container" style="width: 700px; height: 500px"></div>

    <!-- 3. Load the AnyChart adapter and bind your chart -->
    <script type="module">
      import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

      document.addEventListener('DOMContentLoaded', () => {
        const chart = anychart.bar([
          ['Mon', 20], ['Tue', 14], ['Wed', 23], ['Thu', 25], ['Fri', 22],
        ]);
        chart.title('Tips by Day');
        chart.xAxis().title('Day');
        chart.yAxis().title('Count');
        chart.container('container').draw();

        // One line — extracts data, sets maidr-data, fires init event.
        bindAnyChart(chart, {
          id: 'tips-by-day',
          title: 'Tips by Day',
          axes: { x: 'Day', y: 'Count' },
        });
      });
    </script>
  </body>
</html>

Once the page loads, click on the chart (or Tab to it) and MAIDR activates with:

How It Works

The adapter is a small wrapper around AnyChart's public API:

  1. Inspection — walks the chart's series via chart.getSeriesCount() / chart.getSeriesAt(i) to identify series types
  2. Extraction — iterates each series with series.getIterator() and converts the rows into MAIDR's accessibility schema
  3. Binding — writes a maidr-data attribute onto the chart container and dispatches the maidr:bindchart event so MAIDR initializes the chart

AnyChart must be loaded separately — the adapter does not bundle the AnyChart library. Always call bindAnyChart() after chart.draw(); the series data and SVG container only become available once the chart is rendered.

Supported Chart Types

MAIDR Type AnyChart Series / Chart Example
Bar bar, column Bar chart
Horizontal bar any of the above inside anychart.bar() — see below Bar chart
Line line, spline Line chart
Area area, spline-area Area chart
Stacked / Normalized Area the same, with yScale().stackMode('value' | 'percent') Area chart
Step step-line, step-area Step plot
Scatter scatter, marker, bubble Scatter plot
Dot Plot marker, on a chart whose x scale is ordinal Dot plot
Lollipop stick Lollipop chart
Dumbbell range-column, range-bar Dumbbell chart
Diverging Bar two or more bar / column series, with diverging: true Diverging bars
Box Plot box Box plot
Heatmap heatmap, heat Heatmap
Candlestick candlestick, ohlc Candlestick
Pie pie (a doughnut is a pie with innerRadius()) Pie chart
Funnel anychart.funnel(), anychart.pyramid() Funnel chart
Word Cloud anychart.tagCloud() Tag cloud
Sankey anychart.sankey() Sankey diagram
Waterfall anychart.waterfall() Waterfall chart
Radar anychart.radar(), and a polar line / marker series Radar chart
Polar Area anychart.polar() with a column / area series Radar chart
Mosaic anychart.mekko(), anychart.mosaic(), anychart.barmekko() Marimekko chart
Choropleth a choropleth series on anychart.map() Choropleth map
Gantt anychart.ganttProject(), anychart.ganttResource() Gantt chart
Sunburst anychart.sunburst() Sunburst chart
Pack anychart.circlePacking() Circle packing

step-area is the one series that still loses its fill: MAIDR has no stepped area trace, so it keeps its staircase and maps to a step trace. A console warning is emitted when that downgrade occurs.

Notes on chart-type detection:

Code Examples

Bar Chart

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  const chart = anychart.bar([
    ['Sat', 87], ['Sun', 76], ['Thu', 62], ['Fri', 19],
  ]);
  chart.title('The Number of Tips by Day');
  chart.xAxis().title('Day');
  chart.yAxis().title('Count');
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'tips-bar',
    title: 'The Number of Tips by Day',
    axes: { x: 'Day', y: 'Count' },
  });
</script>

Multi-dataset Bar Charts

Adding multiple bar series to the same chart produces a dodged (grouped) bar trace in MAIDR — each series becomes its own row of bars, navigable with up / down arrows:

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  const chart = anychart.bar();
  chart.bar([['Mon', 20], ['Tue', 14], ['Wed', 23]]).name('Lunch');
  chart.bar([['Mon', 12], ['Tue', 19], ['Wed', 15]]).name('Dinner');
  chart.title('Tips by Meal');
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'tips-grouped',
    title: 'Tips by Meal',
    axes: { x: 'Day', y: 'Count' },
  });
</script>

Each chart.bar(...) call adds one series. The adapter walks every series via chart.getSeriesCount() / chart.getSeriesAt(i) and emits one MAIDR layer per series. Series names (set via .name('Lunch')) become the layer titles announced to screen readers.

anychart.bar() is the sideways chart and anychart.column() the upright one, and MAIDR announces each as it is drawn — AnyChart names the two arrangements as separate chart types rather than as an option on one. Every mark inside a bar chart follows: a marker series is a Cleveland dot plot, a stick a sideways lollipop, a range-bar a sideways dumbbell. The bar family's magnitude then travels in the point's x, and the two axis titles are swapped with the axes they name, since chart.xAxis() is the category axis whichever way the chart is drawn. anychart.barmekko() is not affected: despite the name it draws its categories across the page.

Line Chart

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  const chart = anychart.line([
    ['Mon', 4.2], ['Tue', 5.1], ['Wed', 6.3], ['Thu', 5.8],
    ['Fri', 7.4], ['Sat', 8.9], ['Sun', 7.7],
  ]);
  chart.title('Average Daily Sales');
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'sales-line',
    title: 'Average Daily Sales',
    axes: { x: 'Day', y: 'Sales (thousands)' },
  });
</script>

Scatter Plot

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  const chart = anychart.scatter();
  chart.marker([
    { x: 1.1, value: 2.3 }, { x: 2.4, value: 3.9 },
    { x: 3.0, value: 5.2 }, { x: 4.5, value: 4.4 },
    { x: 5.2, value: 6.8 }, { x: 6.7, value: 7.1 },
  ]);
  chart.title('Sample Scatter Plot');
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'sample-scatter',
    title: 'Sample Scatter Plot',
    axes: { x: 'X Value', y: 'Y Value' },
  });
</script>

Box Plot

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  // Rows: [x, lowest, q1, median, q3, highest]
  const chart = anychart.box([
    ['Set A', 760, 801, 848, 895, 965],
    ['Set B', 733, 853, 939, 980, 1080],
    ['Set C', 714, 762, 817, 870, 918],
  ]);
  chart.title('Distribution Summary');
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'dist-box',
    title: 'Distribution Summary',
    axes: { x: 'Group', y: 'Value' },
  });
</script>

Note: AnyChart's iterator API does not expose outlier arrays. The MAIDR representation reports only the five-number summary (min, Q1, median, Q3, max).

Heatmap

<!-- Heatmap is a separate AnyChart module on top of anychart-base -->
<script src="https://cdn.anychart.com/releases/8.13.0/js/anychart-heatmap.min.js"></script>

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  const data = [
    { x: 'Math', y: 'GPT-4', heat: 92 },
    { x: 'Math', y: 'Claude', heat: 89 },
    { x: 'Code', y: 'GPT-4', heat: 88 },
    { x: 'Code', y: 'Claude', heat: 91 },
  ];

  const chart = anychart.heatMap(data);
  chart.title('Model Scores by Task');
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'scores-heat',
    title: 'Model Scores by Task',
    axes: { x: 'Task', y: 'Model' },
  });
</script>

Candlestick

<!-- Candlestick is provided by the financial / stock module -->
<script src="https://cdn.anychart.com/releases/8.13.0/js/anychart-stock.min.js"></script>

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  // Rows: [x, open, high, low, close]
  const chart = anychart.candlestick([
    ['2024-01-01', 100, 110,  95, 108],
    ['2024-01-02', 108, 115, 105, 112],
    ['2024-01-03', 112, 113, 100, 102],
    ['2024-01-04', 102, 108,  98, 106],
  ]);
  chart.title('Daily OHLC Prices');
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'ohlc-candle',
    title: 'Daily OHLC Prices',
    axes: { x: 'Date', y: 'Price (USD)' },
  });
</script>

Pie Chart

<div id="container" style="width: 700px; height: 400px"></div>
<script type="module">
  import { bindAnyChart } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  const chart = anychart.pie([
    ['Apples', 30], ['Bananas', 50], ['Cherries', 20], ['Dates', 12],
  ]);
  chart.title('Fruit Sales by Variety');
  // chart.innerRadius('40%');  // makes it a doughnut; nothing else changes
  chart.container('container').draw();

  bindAnyChart(chart, {
    id: 'fruit-pie',
    title: 'Fruit Sales by Variety',
    axes: { x: 'Fruit', y: 'Units sold' },
  });
</script>

Left and Right move between slices; Up and Down are out of bounds, since a pie is a single row. Each slice announces its label, its value, and its share of the whole — "Fruit is Apples, Units sold is 30, Percentage is 26.8%". The binder stamps a data-maidr-anychart-pie-slice attribute on each rendered wedge in data order, so highlighting needs no manual selectors entry.

Binder Options

bindAnyChart(chart, {
  id?: string;                                     // defaults to container element's id
  title?: string;                                  // defaults to chart.title().text()
  axes?: { x?: string; y?: string };               // override axis labels
  selectors?: Array<string | string[] | undefined>; // per-series CSS selectors for SVG highlighting
});

The selectors Option

For bar, line, scatter, box, heatmap, and candlestick charts the adapter auto-discovers the SVG elements it needs to highlight (see How Highlighting Works (Advanced) below). For charts where the heuristics fall short — or to override them — pass explicit CSS selectors:

// 1. Per-series selectors. Each array entry maps to a series by index.
bindAnyChart(chart, {
  selectors: ['.series-0 rect', '.series-1 rect'],
});

// 2. Skip highlighting for a specific series with `undefined`.
bindAnyChart(chart, {
  selectors: ['.series-0 rect', undefined], // series 1 gets no highlight
});

// 3. Single-element array — applied to every series.
bindAnyChart(chart, {
  selectors: ['.chart rect'],
});

// 4. Per-point selectors via nested string[] (one entry per data point).
bindAnyChart(chart, {
  selectors: [['#bar-mon', '#bar-tue', '#bar-wed']],
});

Selectors are resolved against the chart container, so they may be scoped relatively (e.g. '.series-0 rect' rather than '#container .series-0 rect').

Multi-Panel Figures

AnyChart has no native facet/small-multiples concept — the idiom is one chart instance per container. bindAnyCharts() groups several drawn charts into ONE multi-panel MAIDR figure: users navigate between panels with arrow keys, press Enter to drill into a panel, and Esc to return to panel navigation.

<!-- Keep all panel containers inside ONE wrapper element. -->
<div id="dashboard">
  <div id="panel-q1"></div>
  <div id="panel-q2"></div>
  <div id="panel-q3"></div>
  <div id="panel-q4"></div>
</div>
<script type="module">
  import { bindAnyCharts } from 'https://cdn.jsdelivr.net/npm/maidr/dist/anychart.mjs';

  // q1…q4 are ordinary drawn AnyChart instances, one per container.
  // A 2D array maps 1:1 onto the subplot grid (visual reading order,
  // top-left panel first). Ragged rows are fine; empty rows are not.
  bindAnyCharts(
    [
      [q1, q2],
      [q3, q4],
    ],
    { id: 'sales-by-quarter', title: 'Sales by Quarter' },
  );
</script>

A flat array works too — arrange it with options.layout:

// Chunk row-major into 2 columns → [[q1, q2], [q3, q4]].
bindAnyCharts([q1, q2, q3, q4], { layout: { columns: 2 } });

// Or derive the grid from each container's on-page position:
// containers are clustered into rows by their top edge and sorted
// left-to-right within each row.
bindAnyCharts([q1, q2, q3, q4], { layout: 'auto' });

How it fits together:

anyChartsToMaidr(charts, options) is the matching data-only converter (same relationship as anyChartToMaidr() to bindAnyChart()): it returns the combined Maidr | null without touching the DOM. Note that its default selectors refer to panel attributes that only bindAnyCharts() stamps, and that you should pass a stable id if you need deterministic output.

Known limitation: because AnyChart SVGs contain no per-panel g[id^="axes_"] groups, there is no visual panel-outline highlight while navigating between panels (panel navigation, text, audio, and per-point highlighting inside each panel are unaffected).

See examples/anychart/multipanel.html for a complete 2×2 dashboard.

How Highlighting Works (Advanced)

AnyChart's SVG output uses opaque, internally-generated ids (ac_path_*, ac_rect_*, ac_layer_*) and does not expose stable CSS classes. To still provide reliable per-point highlighting without forcing every consumer to write selectors, bindAnyChart() stamps stable data-maidr-anychart-* attributes onto the relevant SVG elements after the chart renders:

Chart type Attribute Value format
Bar / column / diverging data-maidr-anychart-bar "<seriesIndex>-<pointIndex>"
Line / area / spline / step / stick data-maidr-anychart-line-point "<seriesIndex>-<pointIndex>"
Scatter / marker / bubble / dot plot data-maidr-anychart-scatter-point "<seriesIndex>-<pointIndex>"
Dumbbell (range-column / range-bar) data-maidr-anychart-pair "<seriesIndex>-<pairIndex>"
Box plot data-maidr-anychart-box "<seriesIndex>-<pointIndex>"
Heatmap data-maidr-anychart-heatmap-cell "<rowIndex>-<colIndex>"
Candlestick / OHLC data-maidr-anychart-candlestick-cell "<seriesIndex>-<pointIndex>"
Pie data-maidr-anychart-pie-slice "<seriesIndex>-<sliceIndex>"
Funnel / pyramid data-maidr-anychart-funnel-stage "<seriesIndex>-<stageIndex>"
Tag cloud data-maidr-anychart-word "<seriesIndex>-<termIndex>"
Sankey data-maidr-anychart-flow "<seriesIndex>-<flowIndex>"
Waterfall data-maidr-anychart-waterfall-step "<seriesIndex>-<stepIndex>"
Radar / polar data-maidr-anychart-spoke "<seriesIndex>-<spokeIndex>"
Marimekko data-maidr-anychart-tile "<seriesIndex>-<categoryIndex>"
Choropleth data-maidr-anychart-region "<seriesIndex>-<regionIndex>"
Gantt data-maidr-anychart-task-bar "<laneIndex>-<intervalIndex>"
Sunburst data-maidr-anychart-sunburst-node "<nodeIndex>", depth-first
Pack data-maidr-anychart-pack-node "<nodeIndex>", depth-first, siblings largest first

The adapter's generated selectors then target those attributes (e.g. [data-maidr-anychart-bar="0-3"]), which keeps highlighting stable across re-renders.

Layer discrimination

For heatmap and candlestick charts, the stamping logic must find the correct <g> layer holding the data paths. AnyChart applies a clip-path="url(#ac_clip_*)" attribute to series-data layers so that rendering is clipped to the plot area; chart-level layers (axes, gridlines, background) are intentionally unclipped so they can paint outside the plot area (axis labels, ticks, title space).

The adapter uses this clip-path presence as the primary discriminator when picking the series layer. Without it, the chosen layer can end up being the axes/background group — whose children outnumber the actual data paths — producing off-by-one highlighting (the first highlight covers the background and the last data point is missed). Defense-in-depth path filters also skip:

Two families cannot be found by counting at all:

If you want to bypass auto-stamping entirely, pass explicit selectors — they always take precedence over the generated data-maidr-anychart-* selectors.

Keyboard Controls

Once a chart is focused, use standard MAIDR keyboard shortcuts:

Function Key (Windows) Key (Mac)
Move between data points Arrow keys Arrow keys
Go to extremes Ctrl + Arrow Cmd + Arrow
Toggle Sonification S S
Toggle Braille Mode B B
Toggle Text Mode T T
Toggle Review Mode R R
Auto-play Ctrl + Shift + Arrow Cmd + Shift + Arrow
Stop Auto-play Ctrl Cmd

For the full list, see the Keyboard Controls reference.

Integration Comparison

Feature Vanilla JS (CDN) React Component AnyChart Adapter
Setup maidr-data attribute with JSON data prop on <Maidr> bindAnyChart(chart, opts) after draw()
Data source Manual JSON schema Manual JSON schema Auto-extracted from AnyChart series
SVG selectors Manual CSS selectors Manual CSS selectors Optional selectors option
Configuration Required Required Minimal — id, title, axes only
Chart types All MAIDR types All MAIDR types 14 AnyChart families
Dynamic charts Manual init React lifecycle Re-call bindAnyChart() after redraw

npm Installation (Optional)

For bundler-based projects:

npm install maidr anychart
import anychart from 'anychart';
import 'maidr'; // loads the core runtime (registers maidr:bindchart listener)
import { bindAnyChart } from 'maidr/anychart';

const chart = anychart.bar([['Mon', 20], ['Tue', 14], ['Wed', 23]]);
chart.container('container').draw();

bindAnyChart(chart, {
  id: 'my-chart',
  title: 'Tips by Day',
  axes: { x: 'Day', y: 'Count' },
});

Both maidr and maidr/anychart ship ESM modules with TypeScript declarations.

Advanced: Manual Data Extraction

In addition to the one-line bindAnyChart() binder, the adapter exports a lower-level anyChartToMaidr() function that returns the generated MAIDR JSON without touching the DOM or dispatching any events:

import { anyChartToMaidr } from 'maidr/anychart';

const chart = anychart.bar([['Mon', 20], ['Tue', 14], ['Wed', 23]]);
chart.container('container').draw();

const maidrData = anyChartToMaidr(chart, {
  id: 'tips-bar',
  title: 'Tips by Day',
  axes: { x: 'Day', y: 'Count' },
});
// maidrData is a Maidr JSON object, or null if no convertible series found.

Useful when you want to:

anyChartToMaidr() accepts the same options as bindAnyChart() (id, title, axes, selectors) and returns the same Maidr | null shape that the binder produces internally — the binder just additionally writes the JSON to the container's maidr-data attribute and fires the maidr:bindchart event.

API Documentation

For the complete TypeScript API reference, see the API Documentation.