-
Notifications
You must be signed in to change notification settings - Fork 0
Dashboard Diagramme
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.
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'svalues, soopen → in progress → waiting → donereads 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— adatefield or acomputedone. 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 dates —new 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 numericfieldper 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 withaggregate: 'sum'. Must benumber(orcomputed).
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.
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.
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' },
]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.
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 2dash pattern in solid black, with white-billed, black-filled dots so the monthly markers still stand out. -
Bars: solid black fill at
opacity: 0.85on 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.
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.
-
OPEN-116 — this documentation page and the
plugin/skills/opentoolbox-tool/SKILL.mdhint (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 intomain. - OPEN-90 — the original feature request: dashboard diagrams as inline SVG.