Skip to content

Dashboard Diagramme

multica-agent edited this page Aug 28, 2026 · 1 revision

Dashboard Diagrams

The dashboard can carry inline-SVG charts alongside the older stat, bar and donut tiles. The old tile shapes still work — a domain that already declared { type: 'bar', groupBy: 'area' } keeps it. New diagrams use the unified type: 'chart' shape, which is the only one that supports a line time series. A schema can mix both; they render in the same grid.

The whole thing is pure inline SVG, no charting library, no external request, no <script>. A chart renders the same way with the network cable pulled — the same posture as the rest of the file.

Declaring a chart

A DASHBOARD export gains an optional charts array. Each entry is one tile:

export const DASHBOARD = {
  /* tiles: [...] — unchanged, kept for backward compatibility */
  charts: [
    { type: 'chart', kind: 'bar',   groupBy: 'area',     measure: 'effort' },
    { type: 'chart', kind: 'donut', groupBy: 'status' },
    { type: 'chart', kind: 'line',  dateField: 'due', aggregate: 'count' },
    { type: 'chart', kind: 'line',  dateField: 'due', aggregate: 'sum',  field: 'effort' },
  ],
}

Three kinds:

kind Shows Needs
bar one bar per value of groupBy groupBy, optional measure
donut same data as a ring with a legend groupBy, optional measure
line monthly time series over dateField dateField (date / computed), aggregate: 'count' or 'sum' with field

Options shared with the older tiles:

  • groupBy — an enum (or reference) field. The bar/donut keep the order declared in the schema's values, so open → in progress → waiting → done reads as a progression rather than alphabetically.
  • measure'count' or a field key whose values are summed. A calculated field works here, so a tile can total something like a risk score without that score ever being stored.
  • label, caption — free text; without a label the chart names itself from the schema.
  • entity — only relevant with multiple entities; defaults to the entity currently being viewed.
  • filter(record) — narrows the record set before measuring. Same semantics as the stat tiles.

For line:

  • dateField — a date field or a computed one. The renderer reads the date as a local calendar day from the field's ISO string, the same way the dashboard's due-date widget compares datesnew Date('2026-08-20') lands on the previous day west of Greenwich and would put the wrong item in the wrong bucket without ever throwing an error, so the chart code does not use it.
  • aggregate'count' counts records per month, 'sum' sums a numeric field per month. Both legs are taken from a closed catalog: there is no expression string, nothing that gets evaluated. The file is passed around by hand, and a declaration that could carry code would make every recipient an executor of it — same posture as the metric tiles.
  • field — only with aggregate: 'sum'. Must be number (or computed).

Months without data are skipped, not faked. The line jumps over them rather than drawing a value that isn't there — a missing month should look like a missing month, not like a record that the chart invented to keep the line continuous.

How the renderer is built

The render layer in src/dashboard.jsx draws the SVG. The math — aggregation, axis scaling, path construction, validation — lives next door as pure functions in src/lib/charts.js:

Function What it does
prepareBarRows(entity, records, groupBy, measure) Groups records by groupBy and measures each group. Returns { rows, max }. Order: the schema-declared enum values first, then any values seen in the data but missing from the schema — the legend stays stable when a new category appears.
prepareDonutRows(entity, records, groupBy, measure) Same as prepareBarRows, plus total so the donut can show it in the centre.
prepareLinePoints(entity, records, dateField, aggregate, field) Aggregates by month (YYYY-MM). Returns { points, months, max }.
niceScale(maxRaw) Picks a clean upper bound and three ticks (0, middle, max) on the 1/2/5/10 progression so the largest bar or line peak reaches the top tick instead of clinging to the edge.
linePath(points, x, y) SVG path d-string through the given points. Skips null/undefined/NaN so the line does not draw across a missing month.
validateChart(decl, entity) Checks the declaration against the schema. Returns a list of issues (unknown kind, missing groupBy, sum without a numeric field, dateField that isn't a date …).

The split is deliberate. The dashboard components consume the pure functions; they do not rebuild the math themselves. That keeps sanitizeSvg and the action-validation contract simple — SVG is prepared here, drawn there, and the renderer itself does not insert anything scriptable. The tests in test/charts.mjs exercise the math without a browser.

Invalid declarations are named, not hidden

validateChart returns issues for the same reason validateMetrics does — a declaration that quietly switches itself off gets noticed only when somebody misses the number; naming the rejection turns that from a silent data loss into a visible typo.

A broken chart entry (unknown groupBy, sum without a numeric field, dateField that isn't a date, …) shows up as its own rejection tile between the valid ones, with the reason in the interface language. The valid charts keep working. The fixture test/fixtures/charts.domain.js ships an intentionally broken declaration to exercise this path:

charts: [
  // …
  { type: 'chart', kind: 'line', dateField: 'ghost', label: 'Broken chart' },
]

Colours

Category colours are derived from the tool's own accent colour (Settings → Colors), as a run of shades. Two consequences worth knowing:

  • A rebranded tool recolours its dashboard by itself. There is no second palette to maintain, and no risk of the charts clashing with the rest of the interface.
  • The shade direction flips in dark mode. On a light background the run goes light → dark; on a dark one, dark → light. Without that flip, one end of the range disappears into the background — which is exactly what the first version did, and what the test suite now asserts against.

Semantic colours (the red used for overdue, the green for done) are deliberately not used for categories: a neutral category tinted red reads as a warning it isn't.

Printing

Charts switch to black-and-white stroke patterns when the file is printed, so the printout stays readable on a printer without a colour profile:

  • Lines: a 4 2 dash pattern in solid black, with white-billed, black-filled dots so the monthly markers still stand out.
  • Bars: solid black fill at opacity: 0.85 on a light grey track, with a darker axis.
  • Grid: solid grey instead of the usual soft line.

Same posture as the rest of the print stylesheet — the file is passed around by hand, and a printout that loses all information the moment it leaves a colour printer would not be a printout worth keeping.

Schema reference

For the full schema description — every option, every rule that breaks a single-file build, the place DASHBOARD.charts lives in the broader SCHEMA shape — see Building Your Own Tool. The relevant section is "Inline-SVG charts in the dashboard".

For the older stat tiles, the due-date widget and the metric catalog — the dashboard features the charts are most often combined with — see Dashboards and Printing.

Source

  • OPEN-116 — this documentation page and the plugin/skills/opentoolbox-tool/SKILL.md hint (the Doku-Pfleger share of the work).
  • OPEN-103 — the framework change: src/lib/charts.js, the dashboard renderer, the fixture and the tests. Merged as PR #81 into main.
  • OPEN-90 — the original feature request: dashboard diagrams as inline SVG.

Clone this wiki locally