docs(adr-0023): charts and dashboards — d3 + observable plot #170

Merged
julien merged 1 commits from docs/adr-0023-charts-d3-observable-plot into main 2026-05-16 21:28:04 +02:00
Owner

Summary

Records the decision to use D3 + Observable Plot, wrapped in a new libs/shared/charts/, as the chart toolkit shared by portal-shell and portal-admin. ADR-only — implementation lands as the next chantier(s).

This is staged as a 3-PR chantier per the agreed plan:

PR Périmètre
PR 1 (this one) ADR-0023 — decision + a11y contract + bundle plan.
PR 2 libs/shared/charts/ foundations + 3 starter components (<lib-bar-chart>, <lib-donut-chart>, <lib-stacked-bar-chart>).
PR 3 Integration on the /audit page — daily-volume bar + outcome-breakdown donut + event-type-over-time stacked bar.

What lands

docs/decisions/0023-charts-d3-observable-plot.md

Full MADR 4.0.0 record. Highlights:

  • Choice: D3 + Observable Plot, both from Mike Bostock / Observable Inc., both MIT, both past 1.0. Plot covers ~80 % of standard charts in declarative one-liners; D3 stays the escape hatch for bespoke viz (heatmap, sankey, …) inside the same lib.
  • Why not D3 alone: ~250 LOC per chart × 4-5 types × a11y discipline = sustained code investment before the first dashboard ships.
  • Why not ECharts / Chart.js: 600 KB minified + canvas-rendered + an aria plugin afterthought (ECharts), or narrower vocabulary + brittle dark-mode (Chart.js). Both furthest from the Angular-Signals-zoneless idiom the rest of the workspace runs on.
  • A11y contract is baked into _internal/ (palette, tabular fallback, SVG <title> / <desc> builders) so every chart inherits WCAG 2.2 AA + AAA-targeted compliance from the lib, not from contributor discipline. Six commitments, each unit-tested per chart component.
  • Bundle plan: ~65 KB gzip added to a chart-bearing lazy chunk (d3 modules tree-shaken + Plot + thin wrapper) — well under ADR-0017's 100 KB cap.
  • Component contract: every <lib-*-chart> exposes the same Signal-based input shape ([data], [caption], [description], [ariaLabel], [colorScheme]) regardless of whether Plot or raw D3 powers the rendering.

docs/decisions/README.md

ADR-0023 added to the index table.

CLAUDE.md

  • "Architecture (recorded in ADRs)" gains a "Charts + dashboards" bullet describing the lib + a11y baseline + bundle posture.
  • "Repository status" bumps the ADR range to 0001 → 0023.
  • "Still on the roadmap" gains the charts implementation entry pointing at this ADR.

Notes for the reviewer

  • Why honour the user's D3 preference rather than recommend pure ECharts? D3 (and by extension Plot) is the closest match to the project's tech bar ("stable, recognized, battle-tested") for data-viz on the web; it's also the user's stated preference, and Plot's higher-level layer eliminates the "250 LOC per chart" cost that would otherwise push us toward an alternative. The ADR explicitly walks through ECharts + Chart.js as runners-up so future challengers see the trade-offs we chose against.
  • Why a single shared lib rather than per-app charts? Both SPAs (portal-shell + portal-admin) will host dashboards. The chart vocabulary, a11y contract, palette, and theme integration are identical between the two — duplicating into app-local code would invite drift. The lib stays at libs/shared/charts/ next to libs/shared/ui/.
  • Why the _internal/ folder for cross-cutting code? Single source of truth for the colour palette and the a11y plumbing. A lint rule (added in PR 2) will ban consumers from importing d3-scale-chromatic directly so the colour-blind-safe palette stays the only path.
  • Why no ADR amendment to ADR-0016 / ADR-0017? Both are binding constraints, not superseded. The new ADR operationalises both for the chart surface; cross-references in the "Related ADRs" section make that explicit.

Test plan

  • ADR validates as MADR 4.0.0 (frontmatter, section order, tag vocabulary).
  • No code touched — lint / test / build matrix unaffected.
  • docs/decisions/README.md index updated in the same change per the ADR conventions.
  • Review for trade-off accuracy: are the bundle estimates fair? Is the "Plot covers ~80 % of standard charts" framing defensible against the user's mental model of D3?
  • Implementation chantier (PR 2) lands directly behind this if accepted: pnpm add -w d3 @observablehq/plot @types/d3, libs/shared/charts/ scaffold via pnpm nx g @nx/angular:library --name=shared-charts --directory=libs/shared/charts --standalone=true --unitTestRunner=vitest-analog --tags="scope:shared,type:shared" --no-interactive, then the 3 starter components.

What's next

If accepted as-is, PR 2 (lib foundations + 3 starter components) follows. If a reviewer wants to push back on D3-vs-ECharts or on the a11y contract's strictness, this is the right PR to surface that — no implementation has started.

## Summary Records the decision to use **D3 + Observable Plot**, wrapped in a new `libs/shared/charts/`, as the chart toolkit shared by `portal-shell` and `portal-admin`. ADR-only — implementation lands as the next chantier(s). This is staged as a 3-PR chantier per the agreed plan: | PR | Périmètre | | --- | --- | | **PR 1 (this one)** | ADR-0023 — decision + a11y contract + bundle plan. | | PR 2 | `libs/shared/charts/` foundations + 3 starter components (`<lib-bar-chart>`, `<lib-donut-chart>`, `<lib-stacked-bar-chart>`). | | PR 3 | Integration on the `/audit` page — daily-volume bar + outcome-breakdown donut + event-type-over-time stacked bar. | ## What lands ### [`docs/decisions/0023-charts-d3-observable-plot.md`](docs/decisions/0023-charts-d3-observable-plot.md) Full MADR 4.0.0 record. Highlights: - **Choice**: D3 + Observable Plot, both from Mike Bostock / Observable Inc., both MIT, both past 1.0. Plot covers ~80 % of standard charts in declarative one-liners; D3 stays the escape hatch for bespoke viz (heatmap, sankey, …) inside the same lib. - **Why not D3 alone**: ~250 LOC per chart × 4-5 types × a11y discipline = sustained code investment before the first dashboard ships. - **Why not ECharts / Chart.js**: 600 KB minified + canvas-rendered + an `aria` plugin afterthought (ECharts), or narrower vocabulary + brittle dark-mode (Chart.js). Both furthest from the Angular-Signals-zoneless idiom the rest of the workspace runs on. - **A11y contract** is baked into `_internal/` (palette, tabular fallback, SVG `<title>` / `<desc>` builders) so every chart inherits WCAG 2.2 AA + AAA-targeted compliance from the lib, not from contributor discipline. Six commitments, each unit-tested per chart component. - **Bundle plan**: ~65 KB gzip added to a chart-bearing lazy chunk (d3 modules tree-shaken + Plot + thin wrapper) — well under [ADR-0017](docs/decisions/0017-performance-budgets-lighthouse-ci.md)'s 100 KB cap. - **Component contract**: every `<lib-*-chart>` exposes the same Signal-based input shape (`[data]`, `[caption]`, `[description]`, `[ariaLabel]`, `[colorScheme]`) regardless of whether Plot or raw D3 powers the rendering. ### [`docs/decisions/README.md`](docs/decisions/README.md) ADR-0023 added to the index table. ### [`CLAUDE.md`](CLAUDE.md) - "Architecture (recorded in ADRs)" gains a "Charts + dashboards" bullet describing the lib + a11y baseline + bundle posture. - "Repository status" bumps the ADR range to `0001 → 0023`. - "Still on the roadmap" gains the charts implementation entry pointing at this ADR. ## Notes for the reviewer - **Why honour the user's D3 preference rather than recommend pure ECharts?** D3 (and by extension Plot) is the closest match to the project's tech bar ("stable, recognized, battle-tested") for data-viz on the web; it's also the user's stated preference, and Plot's higher-level layer eliminates the "250 LOC per chart" cost that would otherwise push us toward an alternative. The ADR explicitly walks through ECharts + Chart.js as runners-up so future challengers see the trade-offs we chose against. - **Why a single shared lib rather than per-app charts?** Both SPAs (portal-shell + portal-admin) will host dashboards. The chart vocabulary, a11y contract, palette, and theme integration are identical between the two — duplicating into app-local code would invite drift. The lib stays at `libs/shared/charts/` next to `libs/shared/ui/`. - **Why the `_internal/` folder for cross-cutting code?** Single source of truth for the colour palette and the a11y plumbing. A lint rule (added in PR 2) will ban consumers from importing `d3-scale-chromatic` directly so the colour-blind-safe palette stays the only path. - **Why no ADR amendment to ADR-0016 / ADR-0017?** Both are binding constraints, not superseded. The new ADR operationalises both for the chart surface; cross-references in the "Related ADRs" section make that explicit. ## Test plan - [x] ADR validates as MADR 4.0.0 (frontmatter, section order, tag vocabulary). - [x] No code touched — lint / test / build matrix unaffected. - [x] `docs/decisions/README.md` index updated in the same change per the [ADR conventions](docs/decisions/README.md#conventions). - [ ] Review for trade-off accuracy: are the bundle estimates fair? Is the "Plot covers ~80 % of standard charts" framing defensible against the user's mental model of D3? - [ ] Implementation chantier (PR 2) lands directly behind this if accepted: `pnpm add -w d3 @observablehq/plot @types/d3`, `libs/shared/charts/` scaffold via `pnpm nx g @nx/angular:library --name=shared-charts --directory=libs/shared/charts --standalone=true --unitTestRunner=vitest-analog --tags="scope:shared,type:shared" --no-interactive`, then the 3 starter components. ## What's next If accepted as-is, PR 2 (lib foundations + 3 starter components) follows. If a reviewer wants to push back on D3-vs-ECharts or on the a11y contract's strictness, this is the right PR to surface that — no implementation has started.
julien added 1 commit 2026-05-16 21:27:47 +02:00
docs(adr-0023): charts and dashboards — d3 + observable plot
CI / scan (pull_request) Successful in 3m30s
CI / commits (pull_request) Successful in 3m48s
CI / check (pull_request) Successful in 4m1s
CI / a11y (pull_request) Successful in 3m7s
Docs site / build (pull_request) Successful in 3m24s
CI / perf (pull_request) Successful in 8m13s
aa87db4362
Records the decision to use D3 + Observable Plot, wrapped in a new
`libs/shared/charts/` lib, as the chart toolkit shared by
`portal-shell` and `portal-admin`. Implementation lands as a
separate chantier (`pnpm add` + `libs/shared/charts/` foundations +
3 starter components + audit-page integration).

The user's original ask cited D3 directly. The ADR honours that
preference but pairs D3 with Observable Plot — the same team's
higher-level layer (Mike Bostock / Observable Inc.). Plot turns
"standard charts" (bar / donut / line / stacked-bar / scatter) into
~50 LOC per type instead of the ~250 LOC each that raw D3 would
take. The shared lib's component contract stays uniform whether the
underlying technique is Plot or raw D3 — `<lib-bar-chart>`,
`<lib-heatmap>`, all expose the same `[data] / [caption] /
[description] / [ariaLabel] / [colorScheme]` signal-based shape.

Accessibility is enforced at the lib level (not bolted on per
chart): SVG `<title>` + `<desc>`, `<details>`-based tabular
fallback, colour-blind-safe palettes (Viridis / Cividis for
sequential, ColorBrewer Set2 for categorical), AA-contrast text,
`prefers-reduced-motion` gating. Six commitments, each unit-tested
so a sloppy contributor can't drop one silently.

Bundle impact (~65 KB gzip for the v1 vocabulary loaded on a
chart-bearing lazy chunk) stays well under ADR-0017's 100 KB lazy-
chunk cap via per-`d3-*` module imports.

Considered + rejected:
* D3 alone — 250 LOC per chart × 4-5 charts × consistent a11y
  posture across them all = sustained code investment before the
  first dashboard ships.
* Apache ECharts — 600 KB minified + canvas rendering with an
  `aria` plugin afterthought + JSON-config idiom is the furthest
  from the Angular-Signals direction the workspace runs on.
* Chart.js — narrower vocabulary than Plot, canvas-rendered with
  weaker out-of-the-box a11y, dark-mode integration brittle.

CLAUDE.md and `docs/decisions/README.md` updated in the same
change. ADR-0017 (perf budgets) and ADR-0016 (a11y baseline) are
referenced as the binding constraints; this ADR doesn't supersede
either, it operationalises both for the chart surface.
julien merged commit 7ee7b2dadf into main 2026-05-16 21:28:04 +02:00
julien deleted branch docs/adr-0023-charts-d3-observable-plot 2026-05-16 21:28:06 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: julien/apf_portal#170