Charts
Five charts of plain series: <cae-column-chart> (vertical bars, stacked or
side by side), <cae-line-chart>, <cae-heatmap>, <cae-state-grid> (a state
per row and column) and <cae-bar-chart> (horizontal bars). Register them with
@contraagent/elements/charts/define; the types and the arithmetic they share
are in @contraagent/elements/charts.
Reference: <cae-column-chart>, <cae-line-chart>, <cae-heatmap>, <cae-bar-chart>
// Charts: a CI pipeline's minutes by day and stage (stacked columns), its queue// time (lines), its builds by weekday and hour (a heatmap), how its nightly builds// ended (a state grid) and its test funnel (horizontal bars). The host gives each// chart its unit's format.import '@contraagent/elements/charts/define';import type { ChartSeries } from '@contraagent/elements/charts';
const days = ['10-08', '10-09', '10-10', '10-11', '10-12', '10-13', '10-14'];const minutes: ChartSeries[] = [ { name: 'build', values: [42, 38, 51, 12, 9, 47, 44] }, { name: 'test', values: [65, 70, 58, 20, null, 61, 66] }, { name: 'deploy', values: [8, 8, 9, 2, 2, 10, 8] },];
export function mount(host: HTMLElement): void { const columns = document.createElement('cae-column-chart'); columns.label = 'Minutes by day'; columns.x = days; columns.series = minutes; columns.stacked = true; columns.format = (v) => `${v} min`;
const lines = document.createElement('cae-line-chart'); lines.label = 'Queue time'; lines.x = days; lines.series = [ { name: 'median', values: [1.2, 1.4, 0.9, 0.3, 0.2, 1.8, 1.1] }, { name: 'slowest', values: [6, 9, 4, 1, 1, 12, 7] }, ]; lines.format = (v) => `${v} min`;
const heat = document.createElement('cae-heatmap'); heat.label = 'Builds by weekday and hour'; heat.x = ['9', '10', '11', '12', '13', '14', '15', '16', '17']; heat.y = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; // A count per weekday and hour; Wednesday at 13 has no record (null: an empty cell). heat.values = heat.y.map((_, r) => heat.x.map((_, c) => (r === 2 && c === 4 ? null : (r * 7 + c * 3) % 9))); heat.strings = { scale: (low, high) => `${low} to ${high} builds` };
const bars = document.createElement('cae-bar-chart'); bars.label = 'Tests this week'; bars.bars = [ { label: 'written', value: 1240 }, { label: 'run', value: 1180 }, { label: 'passed', value: 1162 }, { label: 'flaky', value: 18, tone: 'attention' }, ];
const nightly = document.createElement('cae-state-grid'); nightly.label = 'Nightly builds by weekday and hour'; nightly.x = ['0', '1', '2', '3']; nightly.y = ['Mon', 'Tue']; const passed = { label: 'passed', tone: 'done' as const }; // A state per weekday and hour: its word, its tone, and a fact for the tooltip; null: no build. nightly.cells = [ [passed, { label: 'failed', tone: 'failed', tip: 'build 4127' }, passed, null], [passed, passed, { label: 'skipped', tone: 'muted' }, passed], ];
host.replaceChildren(columns, lines, heat, nightly, bars);}Series and values
Section titled “Series and values”A chart takes labels (x) and series, each a name and a value per label; a
null value is a value the series has not: no bar, a break in a line, an empty
cell. A heatmap takes its rows (y), its columns (x) and values[row][column];
a state grid the same rows and columns and cells[row][column], each a state’s
label, its tone and an optional tip (null: no cell); a bar chart takes
bars, each a label, a value and an optional tone.
/** One series: a value per label of the chart's `x`, in that order; null where it has none. */export interface ChartSeries { name: string; values: readonly (number | null)[];}
/** One horizontal bar: its label and value; `tone` gives it a status colour. */export interface ChartBar { label: string; value: number; tone?: Tone;}
/** A number in words (`$1.20`, `42%`, `2m 05s`): the host's format for the chart's unit. */export type ValueText = (value: number) => string;format writes a value in words ($1.20, 42%, 2m 05s): on the axis, in the
tooltip and in the table. It is the host’s, since the chart does not know the
unit.
Colour
Section titled “Colour”The series take the series colours (--ca-series-1 to --ca-series-6) in
their order, so a series keeps its colour whatever its values and its rank. A
chart never makes up a seventh colour: past six series, a column chart sums the
sixth and later into one series, strings.other, in the neutral --ca-meter;
a line chart draws them in that colour and names each in the legend and the
tooltip. A heatmap uses one hue, light to dark in five steps between its lowest
and highest value. A state grid colours a cell by its tone, so a failure and
what needs attention stand out and a cell that ended well stays quiet; the
colour is never alone: a cell wide enough holds its tone’s mark (✓ done,
✕ failed, ◌ attention; a done cell’s mark is quieter), the legend names each
state once, failures and what needs attention first, and the tooltip and the
table say the state in words. A bar
with a tone takes that status colour; a status colour is never a series.
Reading it
Section titled “Reading it”Two or more series have a legend; one series has none, since the chart’s title
names it. Pointing at a label, or moving to it with the arrow keys once the
chart has the focus (up and down by a row in a heatmap and a state grid, Home and End to the
ends, Escape to close), shows its values in a tooltip. Every chart has a switch
to a table of the same values (strings.showTable, strings.showChart), which
is also how a screen reader reads it. A bar chart writes each value beside its
bar.
height sets a column or line chart’s height in px (200 by default; the rows of
a heatmap and of a state grid are 22px each). The parts are plot, table, switch, legend and
empty.