Accessibility, export and printing

Generate a text description with the aria option, add decal patterns so colour is not the only channel, export images and print at the right resolution.

The aria option

import * as echarts from 'echarts/core';
import { AriaComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

echarts.use([AriaComponent, CanvasRenderer]);

const option = {
  // Turning aria on makes ECharts generate a text description from the
  // option itself and attach it to the container as aria-label.
  aria: {
    enabled: true,
    decal: { show: true },            // pattern fills, not just colour
    label: {
      // Overrides the generated description entirely. Use it when the
      // generated text reads badly for your data.
      description: 'Revenue by quarter, 2026. Q1 820, Q2 932, Q3 901, Q4 1290.',
      general: { withTitle: 'This chart shows {title}. {desc}' }
    }
  },
  title: { text: 'Revenue by quarter' },
  xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] },
  yAxis: { type: 'value' },
  series: [{ type: 'bar', data: [820, 932, 901, 1290], name: 'Revenue' }]
};

// The generated description is a summary, not a data table. For anything a
// user must read precisely, provide a real table in the page and link to it.
const chart = echarts.init(el);
chart.setOption(option);
el.setAttribute('role', 'img');
el.setAttribute('aria-describedby', 'revenue-table-caption');
TechniqueWhat it achievesLimit
aria: { enabled: true }A generated text description on the containerSummarises; it is not a data table
aria.label.descriptionFull control over the wordingYou must keep it in sync with the data
aria.decal: { show: true }Pattern fills per seriesOnly helps if the patterns are distinguishable
A visible data tableExact values for everyoneDuplicates the chart in markup
A hidden table with visually-hiddenScreen readers get the numbersNot visible to sighted keyboard users
role="img" plus a descriptionTells assistive tech it is one graphicCards and canvases are otherwise unlabelled
Keyboard-accessible neighboursFilters and tooltips reachable by TabECharts internals are not keyboard navigable
⚠️
A canvas chart is invisible to assistive technology. The aria option adds a text summary to the container, which is a real improvement, but it does not make the data explorable. If the values matter to a decision, publish them in a table — either visible or behind a "show data" disclosure.

Decal patterns and focus

const option = {
  aria: { enabled: true, decal: { show: true } },

  // Decals can also be defined per series for full control
  series: [
    {
      type: 'bar',
      name: 'Revenue',
      data: [820, 932, 901, 1290],
      itemStyle: {
        decal: {
          symbol: 'rect',
          symbolSize: 1,
          color: 'rgba(255, 255, 255, 0.5)',
          dashArrayX: [1, 0],
          dashArrayY: [2, 5],
          rotation: Math.PI / 6
        }
      }
    },
    {
      type: 'bar',
      name: 'Cost',
      data: [410, 480, 455, 610],
      itemStyle: {
        decal: {
          symbol: 'circle',
          symbolSize: 0.6,
          color: 'rgba(255, 255, 255, 0.6)',
          dashArrayX: [1, 0],
          dashArrayY: [1, 0]
        }
      }
    }
  ]
};
/* Give the chart container a visible focus style and a sensible role. */
.chart-container:focus-visible {
  outline: 2px solid var(--brand, #6d28d9);
  outline-offset: 2px;
}

/* Never hide a chart from assistive tech with aria-hidden when it carries data. */
.chart-container { position: relative; }

/* Respect reduced motion: ECharts listens to its own option, not to CSS. */
@media (prefers-reduced-motion: reduce) {
  /* Nothing to do in CSS — set animation: false in the option instead. */
}
// Honour the reduced-motion preference in the option itself.
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

function animationOption(matches) {
  return matches
    ? { animation: false }
    : { animation: true, animationDuration: 400, animationEasing: 'cubicOut' };
}

chart.setOption({
  ...baseOption,
  ...animationOption(prefersReducedMotion.matches)
});

prefersReducedMotion.addEventListener('change', (event) => {
  chart.setOption(animationOption(event.matches));
});

Export and printing

// A PNG of the current chart, at twice the pixel density for print.
const png = chart.getDataURL({
  type: 'png',
  pixelRatio: 2,
  backgroundColor: '#ffffff',        // transparent by default; set it for print
  excludeComponents: ['toolbox']     // keep the toolbar out of the image
});

// An SVG string, when vector output matters.
const svg = chart.renderToSVGString();

// Building a downloadable link without a server round trip
function download(url, filename) {
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  link.click();
}

download(png, 'revenue-q4.png');

// Several charts on one dashboard, combined into a single image:
// getConnectedDataURL stitches the charts that were connected with
// echarts.connect(groupId) into one output.
import { connect } from 'echarts/core';
connect('dashboard');               // every chart that called chart.group = 'dashboard'
const combined = chart.getConnectedDataURL({
  type: 'png', pixelRatio: 2, backgroundColor: '#ffffff'
});
GoalAPINote
Crisp PNG for printgetDataURL({ pixelRatio: 2 })Also set backgroundColor so it is not transparent
Vector outputrenderToSVGString()Requires the SVG renderer
Save from the UIThe toolbox saveAsImage featureConfigure the filename and pixel ratio
Whole dashboard as one imagegetConnectedDataURLCharts must be in the same group
A PDF reportExport each chart, then compose server-sideDo the layout where you control the page size
A print stylesheetCSS @media printA canvas prints at its rendered size; increase the ratio instead
Copy the underlying datachart.getOption().seriesThe option, not the transformed data
// A print path that produces a usable page rather than a screenshot.
window.addEventListener('beforeprint', () => {
  // Re-render at a higher device pixel ratio so the printed output is sharp.
  chart.setOption({}, false);
  chart.resize({ width: 900, height: 500 });
});

window.addEventListener('afterprint', () => {
  chart.resize();                       // back to the container's size
});

// If the chart is off-screen when the user prints, it may never have rendered.
// Force a render before the print dialog opens:
function ensureRendered(chart) {
  const el = chart.getDom();
  if (el.clientWidth === 0) return;
  chart.resize();
}
  • A canvas exports at the size it is rendered. Raising pixelRatio is the only way to get more detail from a small chart.
  • Exclude the toolbox from exports. A downloaded chart with a save button in the corner looks like a mistake.
  • Set backgroundColor explicitly. The default is transparent, which turns black in some viewers and makes a JPEG unusable.
  • For a report containing several charts, export each one and compose the document in the tool that controls the page — do not try to lay out a PDF inside a canvas.

FAQ

Does the aria option make my chart accessible?
It makes it announced rather than invisible, which is a real improvement, but the description is a summary. If the exact values matter, publish them in a table and link the chart to it with aria-describedby.
Why is my exported image transparent or black?
The default background is transparent, and some image tools render transparency as black. Pass backgroundColor: '#ffffff' to getDataURL, and raise pixelRatio if the output looks soft.

Styling, themes and dark mode Tooltips, legends, labels and visual maps

Last refreshed 2026-09-18.