Last updated

Excel Integration

MAIDR ships a Microsoft Excel adapter for Office Add-ins. One call in an add-in's task pane, bindExcel(element), reads the charts of the open workbook through Office.js and mounts MAIDR's accessible layer for them in the pane. A reader gets audio sonification, text descriptions, braille output, keyboard navigation and a description modal for a chart drawn natively in Excel: the pane lists every chart in a labelled picker, shows the one the user selects in the grid, and re-reads it when its data changes.

What this adapter is not. It does not make Excel's own chart accessible where it sits in the grid. Excel draws its charts itself, and an add-in cannot add keyboard navigation or a screen-reader reading to them. The adapter reads a chart's data through Office.js into the add-in's task pane, and the reader moves to the pane to read it. It is also not a published add-in: you host the task pane page and its manifest yourself, as for any add-in. A starting point is in examples/excel-addin/. See Limitations before you plan around it.

Quick Start

An add-in is a web page Excel opens in a task pane beside the grid, and a manifest that tells Excel where the page is. Microsoft's Excel add-in quick start covers creating and sideloading one; MAIDR needs only the page below.

1. The task pane page

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Accessible charts</title>
    <script src="https://officeapis.public.onecdn.static.microsoft/1/office.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/maidr/dist/maidr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/maidr/dist/excel.js"></script>
  </head>
  <body>
    <main id="maidr" aria-label="Accessible charts"></main>
    <script>
      Office.onReady(() => {
        maidrExcel.bindExcel(document.getElementById('maidr'));
      });
    </script>
  </body>
</html>

Bind from Office.onReady. bindExcel waits for it as well, because only then has Office.js loaded the Excel API and said which Excel it is running in. It appends one wrapper <div> (with a data-maidr-excel attribute) to the element and changes nothing else. It resolves once the first chart has been read, and it never rejects: anything that stops it reading is said in the pane.

A task pane built with a bundler imports the adapter instead. It is an ES module with React bundled in, because a task pane shares no React with anything else:

npm install maidr
import { bindExcel } from 'maidr/excel';

Office.onReady(async () => {
  const binding = await bindExcel(document.getElementById('maidr')!);
  // binding.charts, binding.show(id), binding.refresh(), binding.dispose()
});

Load office.js from Microsoft's CDN either way, in the page's <head>: Microsoft requires the CDN reference of add-ins published to the Microsoft Marketplace.

2. The manifest

Nothing in the manifest is MAIDR's own. Declare ReadWriteDocument permission although the adapter writes nothing: Office requires it of any add-in that uses the Excel JavaScript API, even one that only reads. What to decide is the requirement set. Reading a chart's data needs ExcelApi 1.12 (ChartSeries.getDimensionValues), and there are two ways to deal with Excel versions that lack it:

ExcelApi 1.12 is in Excel on the web, Excel for Microsoft 365 on Windows from version 2008 and on Mac from 16.40, Excel 2021 and later. ExcelApi 1.15 (Microsoft 365 from version 2202, Excel 2024, Mac 16.58) adds the category cells described under How the Chart Is Read; without it the adapter reads less, not nothing. Microsoft's requirement set table has the full list.

How It Works

The pane holds two things: a native <select> labelled Chart, listing every chart on the workbook's visible worksheets as Sheet - title (Sheet - Chart 2 for a chart without a title), and the figure area beneath it. A keyboard or screen-reader user chooses a chart from the picker without leaving the pane, then presses Tab to reach MAIDR's figure and reads it with the usual keys.

The figure is MAIDR's, around the chart's picture as Excel draws it (Chart.getImage), so a sighted colleague sees the same chart the reader hears. The picture is decorative (alt=""): MAIDR's figure carries the chart's name and instructions, and repeating them would be heard twice.

The pane follows the workbook:

When there is nothing to read, the pane says why in a status message (role="status", focusable with Tab) where the figure would be: a workbook with no charts, a chart whose every value is blank, a chart type MAIDR does not know — one a later Excel adds (the message names it) — an Excel without ExcelApi 1.12, or a page that is not running in Excel at all. If the reader was in the figure when it gave way to a message, focus moves to the message, so they hear why the chart went; when the chart comes back, focus moves back to the figure the same way. A combo chart read without one of its series has a note above the figure (role="status", so it is announced when the chart is shown) naming what was left out.

Supported Chart Types

The chart type comes from Excel (Excel.ChartType), so nothing has to be declared. Every type Excel.ChartType names is read, the preview-only …Ex types included, and the tables below sort them by the MAIDR layer each reads as. Every 3-D, cylinder, cone and pyramid variant reads as the flat chart it is a variant of — 3DColumnClustered, CylinderColStacked, ConeBarClustered and PyramidColStacked100 as clustered or stacked columns and bars, 3DColumn and its kin (each series in a row of its own along the depth axis) as a clustered column chart, 3DLine, 3DPie and the 3DArea family as their flat charts — because depth and bar shape are how the chart is drawn, not what it holds.

Stable chart types

Excel chart Excel.ChartType MAIDR layer
Column ColumnClustered, one series bar
Clustered column ColumnClustered, several series dodged_bar
Stacked column ColumnStacked stacked_bar (bar for one series)
100% stacked column ColumnStacked100 stacked_normalized_bar
Bar BarClustered, one series bar, horizontal
Clustered bar BarClustered, several series dodged_bar, horizontal
Stacked bar BarStacked stacked_bar, horizontal (bar for one series)
100% stacked bar BarStacked100 stacked_normalized_bar, horizontal
Line Line, LineMarkers line, one line per series
Pie Pie, PieExploded pie
Doughnut Doughnut, DoughnutExploded pie, from the first ring
Pie of pie PieOfPie two pie subplots: the pie, and the points split off it
Bar of pie BarOfPie a pie subplot, and a bar subplot of the points split off it
Scatter XYScatter point, one layer per series
Scatter with lines XYScatterLines, XYScatterLinesNoMarkers, XYScatterSmooth, XYScatterSmoothNoMarkers line over numeric x
Bubble Bubble, Bubble3DEffect point, each bubble's size as its z, one layer per series
Stock, open-high-low-close StockOHLC candlestick
Stock, volume-open-high-low-close StockVOHLC candlestick, and a bar layer of the volume
Stock, high-low-close StockHLC candlestick with no open
Stock, volume-high-low-close StockVHLC candlestick with no open, and a bar layer of the volume
Surface and contour Surface, SurfaceWireframe, SurfaceTopView, SurfaceTopViewWireframe heat: the categories across, the series down
Histogram Histogram hist (bar when binned by category)
Pareto Pareto bar, largest first, and a line of the cumulative percentage
Box and whisker Boxwhisker box, a layer per series, a box per category

A 100% stacked chart reads as normalized even with one series, because Excel draws every bar of it full height. A smoothed scatter line is read through its points; the curve Excel draws between them is not data.

Experimental chart types

Excel chart Excel.ChartType MAIDR layer
Area [experimental] Area area
Stacked area [experimental] AreaStacked stacked_area (area for one series)
100% stacked area [experimental] AreaStacked100 stacked_normalized_area
Stacked line [experimental] LineStacked, LineMarkersStacked stacked_area (line for one series)
100% stacked line [experimental] LineStacked100, LineMarkersStacked100 stacked_normalized_area
Radar [experimental] Radar, RadarMarkers, RadarFilled radar, one outline per series
Funnel [experimental] Funnel funnel, horizontal
Waterfall [experimental] Waterfall waterfall
Treemap [experimental] Treemap treemap
Sunburst [experimental] Sunburst sunburst
Map [experimental] RegionMap choropleth

These MAIDR layers are experimental: their readings may change in any release. A stacked line is drawn at each series' running total, which is what a stacked area reads: each band its own series' value, the total derived from them.

Combo charts

In a combo chart every series carries its own type (ChartSeries.chartType, ExcelApi 1.7). The series are grouped by what they are drawn as, and by the value axis they are measured on, and each group becomes one layer of a single subplot: a column series and a line series on the secondary axis are a bar layer and a line layer, and Page Up and Page Down move between them. Each layer's value axis is labelled from its own axis' title. Two layers of one type, such as two lines on different axes, are named after their series so a layer switch tells them apart.

A series of a type MAIDR does not know — one a later Excel adds — or of a type that is a chart of its own, such as a box and whisker series, is left out, and the rest of the chart is read. The pane says above the figure which series it left out, and the console says why.

Charts Excel computes

Some charts draw what Excel computed from the data rather than the data itself — a histogram's bins, a box's quartiles, a Pareto chart's order — and Office.js hands an add-in only the data. The adapter computes them again, as Microsoft documents Excel computing them:

How the Chart Is Read

The adapter never imports Office.js. It reads the Excel and Office objects the page loaded structurally, so any Office.js version is accepted as it is. One read takes a handful of context.sync() round trips, each one batching everything that does not depend on the one before:

  1. the chart (type, name, title and its visibility, worksheet) and its series' names and types;
  2. every series' values, through ChartSeries.getDimensionValues (ExcelApi 1.12) — Categories and Values, XValues and YValues for a scatter, and BubbleSizes too for a bubble chart — with what the chart type is computed from (a histogram's or Pareto chart's binOptions, a box and whisker chart's boxwhiskerOptions, a pie of pie's splitType and splitValue), each series' filtered and axisGroup, a pie's firstSliceAngle, and, for a line, area or scatter chart, how it plots blank cells (Chart.displayBlanksAs, ExcelApi 1.8);
  3. the axis titles, for a chart that has axes, and a surface's series axis title;
  4. with ExcelApi 1.15, where the categories come from (getDimensionDataSourceType and getDimensionDataSourceString), and
  5. that range's displayed text and the header cell before it;
  6. with ExcelApi 1.15, for a bubble chart, the header cell above each series' sizes, found the same way;
  7. the chart's picture.

The first two are the data: when either fails, the pane says it could not read the chart. The rest are labels and a picture, each read in a round trip of its own, so an Excel that declines one costs that one thing, with a console warning, and never the chart.

A surface, and the chart types Excel 2016 added (histogram, Pareto, box and whisker, waterfall, treemap, sunburst, funnel and map), are not asked whether a chart filter hides a series or which value axis it is measured on: Office.js says a surface has no chart filter, and a property a chart type lacks could fail the whole read. Their every series is read.

How values become what MAIDR announces:

A legend in Excel has no title, so a segmented bar announces its series under MAIDR's default name for them (Level is North), a line under its own (Group is Visits), and a radar under its own (Series is North).

Keyboard

Reaching the task pane is Excel's keyboard model. In Excel for Windows and Mac, F6 moves focus between the main areas of the window — the grid, the ribbon, the status bar and an open task pane — and Shift+F6 moves back; in Excel on the web the keys are Ctrl+F6 and Ctrl+Shift+F6. Microsoft's Use a screen reader to explore and navigate Excel describes the main areas. Inside the pane, Tab moves from the Chart picker to MAIDR's figure (or to the pane's message, when there is one). Choosing a chart in the picker shows it; on Windows the arrow keys change a closed picker's choice directly, so each chart is shown as it is reached.

Once the figure is focused, the standard MAIDR shortcuts apply:

Function Key (Windows) Key (Mac)
Move between data points Arrow keys Arrow keys
Replay the current point Space Space
Go to extremes Ctrl + Arrow Cmd + Arrow
Switch between layers (a combo chart's groups, a scatter's series) Page Up / Page Down Page Up / Page Down
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

A pie of pie or bar of pie is two panels, so its figure opens at MAIDR's lobby: the arrow keys choose a panel, Enter goes into it and Escape comes back out, as Multi-Panel Figures describes.

Excel has keyboard shortcuts of its own, and keys it handles before the task pane's page sees them never reach MAIDR. Test the keys your readers rely on in the Excel they use. For the full list of MAIDR keys, see the Keyboard Controls reference.

API Reference

bindExcel(container, options?)

Mounts the pane. Returns a Promise<ExcelBinding> that resolves once the first chart has been read, and never rejects.

Parameter Type Description
container HTMLElement The element the pane goes in. A wrapper is appended to it, and nothing else is touched.
options ExcelBindOptions? See below.

ExcelBindOptions

Option Type Description
excel ExcelHost? The Excel namespace. Default window.Excel.
office OfficeHost? The Office namespace. Default window.Office.
followActiveChart boolean? Show the chart the user selects in the grid. Default true.
refreshDelay number? Milliseconds to wait after a cell change before re-reading. Default 300.
imageWidth number? The width, in pixels, to request the chart's picture at. Default: the container's width at the screen's pixel density.
id string? The figure id, unique on the page. Default maidr-excel-<n>.
labels Partial<ExcelPaneLabels>? Replacements for the pane's wording: picker, choose, loading, noCharts, noData, unsupported (where {type} names the chart type), partial (the note above a combo chart read without some of its series, where {series} names them), unsupportedExcel, noExcel, readFailed and chartGone. DEFAULT_EXCEL_LABELS holds the English defaults.

ExcelBinding

Member Type Description
maidr Maidr | null The figure on show, or null while the pane shows a message.
chart ExcelChartInfo | null The chart on show.
charts readonly ExcelChartInfo[] Every chart the picker offers: { id, name, title?, worksheet, worksheetId, label }.
show (chartId?: string) => Promise<void> Show a chart by id; with no id, the chart on show, or else the active chart, or else the first.
refresh () => Promise<void> Re-read the chart list and the chart on show.
dispose () => Promise<void> Remove every Office.js event handler the pane added, through the request context it was added in, and unmount MAIDR and the wrapper.

readExcelChart(context, chart, options?) and convertExcelChart(snapshot, options?)

The two halves underneath bindExcel, for a pane that mounts <Maidr> itself. readExcelChart takes a request context from Excel.run and a chart from it, and returns an ExcelChartSnapshot: plain data, safe to keep after the context is gone. Pass { categoryCells: true } where ExcelApi 1.15 is supported, and { image: true } or { image: { width } } for the picture. convertExcelChart is pure: no DOM, no Office.js. It takes the snapshot (one read by hand works the same) and returns a MAIDR figure, or null with a console warning when the chart's type has no reading or it holds nothing to navigate.

import { convertExcelChart, readExcelChart } from 'maidr/excel';

const snapshot = await Excel.run(async (context) => {
  const chart = context.workbook.getActiveChartOrNullObject();
  return readExcelChart(context, chart);
});
const maidr = convertExcelChart(snapshot);

listExcelCharts(context) lists the charts bindExcel's picker offers. isSupportedExcelChartType(type) says whether a chart type has a reading — every type Excel.ChartType names does, and Invalid or a type a later Excel adds does not — and excelChartTypeName(type) names it as a message does.

Script tags

The UMD build (dist/excel.js) exposes window.maidrExcel with bindExcel, convertExcelChart, readExcelChart, listExcelCharts, isSupportedExcelChartType, excelChartTypeName and DEFAULT_EXCEL_LABELS.

Type exports

The Excel… and OfficeHost interfaces exported from maidr/excel are minimal structural types. They describe only the parts of Office.js the adapter reads, so nothing here depends on @types/office-js, and the real Excel and Office objects pass as they are.

Limitations

Examples

API Documentation

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