Skip to content

Mark matrix

<cae-mark-matrix> shows marks in a table: a row per series (its label, then a value), a column per item, the columns under nested groups. Register it with @contraagent/elements/matrix/define; the types are in @contraagent/elements/matrix.

docs/examples/matrix.ts
// A mark matrix: a hand-written week of nightly checks, a row per service, a column
// per check under a group per night. The host sorts the rows and keeps the selection.
import '@contraagent/elements/matrix/define';
import type { MarkMatrixInput, MatrixRowInput } from '@contraagent/elements/matrix';
import type { StateStyles } from '@contraagent/elements/primitives';
const states: StateStyles = {
pass: { label: 'pass', tone: 'done', shape: 'round', rank: 0 },
fail: { label: 'fail', tone: 'failed', shape: 'square', rank: 2 },
skip: { label: 'skip', tone: 'muted', shape: 'round', rank: 1 },
};
const nights = ['Mon', 'Tue'];
const checks = ['lint', 'unit'];
const results: Record<string, string[]> = { api: ['pass', 'pass', 'pass', 'fail'], web: ['pass', 'skip', 'pass', 'pass'], worker: ['fail', 'fail', 'pass', 'pass'] };
const rows: MatrixRowInput[] = Object.entries(results).map(([service, rs]) => ({
id: service,
label: service,
value: rs.filter((r) => r === 'fail').length,
strong: rs.includes('fail'),
cells: rs.map((state, i) => {
const at = `${nights[Math.floor(i / 2)]} ${checks[i % 2]}`;
return { id: `${service}|${i}`, state, label: `${service} | ${at} | ${state}`, tip: `${service}, ${at}` };
}),
}));
const byFailures = (rs: readonly MatrixRowInput[]) => [...rs].sort((a, b) => Number(b.value) - Number(a.value));
const matrix = (sort: string): MarkMatrixInput => ({
caption: 'Nightly checks',
label: 'Nightly checks by service',
facts: ['2 nights', '3 services'],
head: [
nights.map((n) => ({ id: n, label: n, span: 2, rows: 1, type: 'group' as const })),
nights.flatMap((n) => checks.map((c) => ({ id: `${n}|${c}`, label: c, span: 1, rows: 1, type: 'column' as const }))),
],
columns: nights.flatMap((n) => checks.map((c) => ({ id: `${n}|${c}`, groups: [n] }))),
rows: sort === 'failures' ? byFailures(rows) : rows,
value: { label: 'Failures' },
sorts: [
{ id: 'name', label: 'Name' },
{ id: 'failures', label: 'Failures' },
],
sort,
});
export function mount(host: HTMLElement): void {
const el = document.createElement('cae-mark-matrix');
el.matrix = matrix('name');
el.states = states;
el.strings = { rowsHeader: 'Service' };
el.addEventListener('cae-sort', (e) => (el.matrix = matrix(e.detail.id)));
el.addEventListener('cae-select', (e) => (el.selected = e.detail.id));
host.replaceChildren(el);
}

A MarkMatrixInput holds the header rows (outermost first), the columns, the rows and the line above the table. A column names the groups it is under: a border marks where a group starts. A mark is drawn in its state’s look (states, as in the layered graph).

src/matrix/types.ts
export interface MatrixRowInput {
/** Its id: what the matrix highlights (the series of the selected or hovered mark). */
id: string;
label: string;
tip?: string;
/** Its label in the plain, muted face (a row that stands for no item of its own). */
muted?: boolean;
/** The value column's cell, and whether it stands out. */
value?: string | number;
strong?: boolean;
/** One per column: its mark, or null. */
cells: readonly (MatrixMark | null)[];
}
export interface MarkMatrixInput {
/** The line above the table, and its tooltip. */
caption: string;
captionTip?: string;
/** The table's accessible name. */
label: string;
/** Facts beside the caption. */
facts?: readonly (string | Fact)[];
/** The header rows, outermost first. */
head: readonly (readonly MatrixHeadCell[])[];
columns: readonly MatrixColumnInput[];
/** Already in the order chosen (`sort`): the host sorts them. */
rows: readonly MatrixRowInput[];
/** A column of one value per row, after the rows' labels, and its header. */
value?: { label: string; tip?: string };
/** The orders the reader may choose, and the one chosen. */
sorts?: readonly { id: string; label: string }[];
sort?: string;
}

The marks are one tab stop. The arrow keys, Home, End, Page Up and Page Down move between them (Ctrl with Home and End: the first and the last row). A click or the focus sends cae-select; Enter or a double click sends cae-activate, or cae-drill for a mark with drill. The sort control sends cae-sort: the host sorts the rows and sets sort. Above windowAt rows (150), only the rows in view are drawn.

The words are strings (Strings): the sort control’s label and the header of the rows’ labels.