Prototype Library

Chart

A thin, accessibility-corrected wrapper over AG Charts Community. It resolves every mark colour from tokens in JS (a canvas cannot read CSS), honours prefers-reduced-motion and forced colors on the library’s behalf, and gives the chart a real accessible name instead of the generated “chart, N series”.

Inherited wc import '@esa/ecology/esa-chart';

Preview

A chart ships with its data as a grid — see Text alternative below. The pairing is not decorative; it is what makes the chart conformant.

<esa-chart label="Rainfall by month"></esa-chart>
<!-- data and series are set as PROPERTIES, not attributes -->
<script>
  chart.data = [{ month: 'Jan', rain: 20 }, /* … */];
  chart.series = [{ type: 'bar', xKey: 'month', yKey: 'rain', yName: 'Rainfall (mm)' }];
</script>

Multiple series

With more than one series a legend appears, and each series takes the next colour from the data-viz categorical family in fixed order — never cycled, never re-assigned when a filter changes the series count. Line and scatter series also take a rotating marker shape, so identity is never carried by hue alone.

chart.series = [
  { type: 'bar',  xKey: 'month', yKey: 'rain', yName: 'Rainfall (mm)' },
  { type: 'line', xKey: 'month', yKey: 'temp', yName: 'Temperature (C)' },
];

API

PropTypeDefaultDescription
label string '' The chart's accessible name. Required — without it AG Charts announces a generated name like “chart, 2 series”, which names the shape of the thing rather than what it shows.
data unknown[] [] Row objects, passed to AG Charts untouched.
series Array<Record<string, unknown>> [] Series definitions. Marker shapes are layered in automatically so series differ by form as well as hue.
options Record<string, unknown> {} Escape hatch merged over everything the component computes — use it to reach any raw AG Charts option.
color-scale 'categorical' | 'sequential' | 'diverging' 'categorical' Which tier-2 data-viz family to read: categorical (identity), sequential (magnitude), or diverging (polarity). Property: `colorScale`.
described-by string | undefined — ID of prose describing the chart’s takeaway. Resolved in the light DOM and applied by element reflection, because an IDREF cannot cross a shadow boundary. Property: `describedBy`.
height string '320px' Plot height. The chart fills its container’s width.

Installing

ag-charts-community is an optional peer dependency, loaded by dynamic import on first render. A spoke that never charts never pays the ~2.8 MB, and @esa/ecology’s own dependency list stays at lit alone.

npm install ag-charts-community

AG Charts Community is MIT and free in production. Integrated charts — where a user selects cells in a grid and charts them — require the AG Grid + AG Charts Enterprise Bundle, which is a separate licence from AG Grid Enterprise alone.

Accessibility

AG Charts was scored in a real browser against the five questions worth asking of any charting library. It passes the two that are hardest to retrofit. The rest is what this wrapper exists to fix.

QuestionAG Charts aloneWhat esa-chart does
SVG or canvas? Canvas — but with a synchronized proxy DOM of real elements carrying the semantics, and the canvas itself marked aria-hidden. Nothing needed. This is why AG Charts is usable at all where most canvas libraries are not.
Default role and name? Passes — role="figure" unprompted — but the generated name is the generic "chart, N series", and there is no option to change it. Requires label and writes it onto the proxy root. Warns at runtime when absent rather than shipping the generic name silently.
Text or data-table alternative? None. No table, no figcaption, no offscreen text. Values are reachable only by traversing the chart. Not solved in the component — solved by pairing it with a grid.
Keyboard access to data points? Passes, on by default. Arrows walk the data, Home/End jump to first and last, and focus is not trapped. Nothing needed. This is the strongest of the five answers.
Respects prefers-reduced-motion? No. The string appears zero times in the library; every matchMedia call is for HiDPI scaling. Reads the query itself and sets animation.enabled, re-deciding when the preference changes mid-session.

Three more, found by measuring rather than reading the docs

  • Legend hit targets render 16px tall — under the 24px raw floor, and reported rather than patched. That is not a violation: SC 2.5.8’s spacing exception passes an undersized target whose 24px circle clears its neighbours’, and the default legend spacing earns it — axe reports zero target-size violations here. Forcing it would mean 24px legend markers, since marker.size is the only option that moves the box (item.paddingY is not an option at all; AG Charts accepts and ignores it). The exception is layout-dependent: a legend packed tight enough loses it, so if npm run a11y ever reports target-size on a chart, the lever is options.legend.item.marker.size.
  • Forced colors cannot reach a canvas. It is a bitmap — no token, no @media block and no assurance profile touches it. The only route in is to re-theme from JS with the system keywords, which this component does. It deliberately discards the data-viz palette in that mode: the user chose those colours, and overriding them would itself be SC 1.4.8. Marks stay apart by shape.
  • Series told apart by colour alone is SC 1.4.1, Level A. Line, area and scatter series get a rotating marker shape. Bars cannot carry a pattern in Community — for those the answer is direct labels or the paired grid.

What is still yours to do

  • Give every chart a real label. The component warns, but it cannot invent one.
  • Ship the paired grid. See below.
  • Nothing automated proves a screen reader announces any of this correctly. Structure is provable; announcement is not. That needs NVDA/Firefox and VoiceOver/Safari.

Text alternative — pair it with a grid

AG Charts ships no data-table view, and the chart alone does not satisfy the text-alternative requirement. The answer is a composition rather than a prop: esa-grid already solves tabular semantics, and a visible “view as data” gives sighted keyboard users a real affordance instead of an offscreen table only a screen reader can find.

<esa-chart label="Rainfall by month" .data={rows} .series={series} />

<esa-collapsible summary="View as data">
  <esa-grid columnDefs={cols} rowData={rows} />
</esa-collapsible>

There is a second reason, and it is not about screen readers. Three slots of the categorical palette sit under 3:1 against a light surface. The data-viz validator calls that acceptable only with relief — visible labels or a table view. The paired grid is that relief. Drop it and the palette stops being legal, not merely unfriendly.

Theming — why a canvas needs a bridge

A grid re-themes for free because it is DOM. A chart does not: measured, overriding the library’s own --ag-charts-* custom properties changes getComputedStyle and leaves the canvas painting its original colours. Those ~60 properties style the DOM chrome — tooltip, legend, menus, focus ring — and about a third of the theming surface is therefore ordinary CSS. Every mark, though, is resolved in JS and handed over as a literal.

Which means a canvas does not repaint when the cascade changes. The component watches data-theme, data-scheme and data-a11y-assurance on <html> and re-resolves. Without that, switching theme leaves the chart painted in the old brand with no error anywhere.

Series colours come from the tier-2 --color-background-dataviz-* family. Until it lands, the component falls back to a built-in ramp and warns in the console — a fallback that works silently is how a token never gets wired.