feat(shared-charts): foundations + bar / donut / stacked-bar components
CI / commits (pull_request) Successful in 3m31s
CI / scan (pull_request) Successful in 4m28s
CI / check (pull_request) Failing after 8m9s
CI / a11y (pull_request) Successful in 3m56s
Docs site / build (pull_request) Successful in 4m59s
CI / perf (pull_request) Successful in 10m8s
CI / commits (pull_request) Successful in 3m31s
CI / scan (pull_request) Successful in 4m28s
CI / check (pull_request) Failing after 8m9s
CI / a11y (pull_request) Successful in 3m56s
Docs site / build (pull_request) Successful in 4m59s
CI / perf (pull_request) Successful in 10m8s
Implementation of ADR-0023 (PR 1). The next chantier wires these
components into the audit-log page; this PR delivers the lib + the
three starter chart types + the a11y contract.
Deps:
* d3 + @types/d3 (top-level)
* d3-shape + @types/d3-shape (donut's pie/arc generators)
* d3-scale-chromatic + @types/d3-scale-chromatic (palettes)
* @observablehq/plot (bar + stacked-bar)
Lib `libs/shared/charts/`:
* `_internal/palette.ts` — single source of palettes. Sequential
(Viridis / Cividis for dark mode) + categorical (ColorBrewer
Set2). Colour-blind-safe per the ColorBrewer review.
* `_internal/a11y.ts` — chartId generator, SVG <title>/<desc>
injector, `findChartSvg` for Plot's legend-wrapped output,
prefers-reduced-motion guard, theme resolver.
* `_internal/chart-envelope.scss` — shared figure/caption/
fallback rules consumed via `@use` by each chart's .scss.
Extracted at the third consumer per CLAUDE.md's "three similar
things" rule.
* `_internal/chart-types.ts` — common `ChartBaseInputs<T>`
interface every component extends.
Three Plot-or-D3-backed components:
* `<lib-bar-chart>` — Plot.barY, categorical-by-default palette.
* `<lib-donut-chart>` — raw d3-shape (pie + arc); Plot has no
donut mark so we reach for D3 directly. Each slice carries its
own <title> child for SVG-tooltip accessibility.
* `<lib-stacked-bar-chart>` — Plot.barY with `fill` set to the
series key (auto-stacked), legend on.
Each component honours ADR-0023's six commitments:
1. <figure role="img" aria-labelledby aria-describedby>
2. SVG <title> + <desc> as first two children (injected post-
render because Plot doesn't emit them)
3. <details> tabular fallback rendering every data point
4. Palette from the canonical list (no ad-hoc colours)
5. AA-contrast text on axis ticks (theme-aware grayscale via
the .chart-canvas svg text rule)
6. prefers-reduced-motion → data-no-transitions marker on the
SVG, CSS strips animations.
A custom ESLint rule in `libs/shared/charts/eslint.config.mjs`
bans direct imports of `d3-scale-chromatic` outside
`_internal/palette.ts`, so a future contributor can't bypass the
colour-blind-safe contract.
Tests: 13 specs across the three components. Each component covers
the figure ARIA contract, the SVG title/desc injection, the
tabular fallback, and data-reactivity. Donut spec additionally
asserts per-slice <title> labels for screen readers.
Verification:
* `pnpm nx lint test build --projects=shared-charts,portal-shell,portal-admin`
— 12/12 tasks green.
* `pnpm nx build portal-shell --configuration=production` —
i18n-strict prod build clean (the lib ships no i18n marks
since axis labels are caller-supplied).
This commit is contained in:
Generated
+48
@@ -60,6 +60,9 @@ importers:
|
||||
'@nestjs/swagger':
|
||||
specifier: ^11.4.3
|
||||
version: 11.4.3(@nestjs/common@11.1.21(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)
|
||||
'@observablehq/plot':
|
||||
specifier: ^0.6.17
|
||||
version: 0.6.17
|
||||
'@opentelemetry/api':
|
||||
specifier: ^1.9.1
|
||||
version: 1.9.1
|
||||
@@ -132,6 +135,15 @@ importers:
|
||||
cookie-parser:
|
||||
specifier: ^1.4.7
|
||||
version: 1.4.7
|
||||
d3:
|
||||
specifier: ^7.9.0
|
||||
version: 7.9.0
|
||||
d3-scale-chromatic:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
d3-shape:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
express-rate-limit:
|
||||
specifier: ^8.5.1
|
||||
version: 8.5.2(express@5.2.1)
|
||||
@@ -280,6 +292,15 @@ importers:
|
||||
'@types/cookie-parser':
|
||||
specifier: ^1.4.10
|
||||
version: 1.4.10(@types/express@5.0.6)
|
||||
'@types/d3':
|
||||
specifier: ^7.4.3
|
||||
version: 7.4.3
|
||||
'@types/d3-scale-chromatic':
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
'@types/d3-shape':
|
||||
specifier: ^3.1.8
|
||||
version: 3.1.8
|
||||
'@types/express':
|
||||
specifier: ^5.0.6
|
||||
version: 5.0.6
|
||||
@@ -3199,6 +3220,10 @@ packages:
|
||||
'@nx/workspace@22.7.1':
|
||||
resolution: {integrity: sha512-wnBMgeogdGaRdxDDzZspSt1U87PMeYJhz1ygr42L9Lot9E5nCf17E85iyHl3YxcMSNslHxnHyTkq7MyQ8hHjdQ==}
|
||||
|
||||
'@observablehq/plot@0.6.17':
|
||||
resolution: {integrity: sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@opentelemetry/api-logs@0.217.0':
|
||||
resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -5798,6 +5823,9 @@ packages:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
binary-search-bounds@2.0.5:
|
||||
resolution: {integrity: sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==}
|
||||
|
||||
birpc@2.9.0:
|
||||
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||
|
||||
@@ -7641,6 +7669,9 @@ packages:
|
||||
resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
interval-tree-1d@1.0.4:
|
||||
resolution: {integrity: sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==}
|
||||
|
||||
intl-messageformat@10.7.18:
|
||||
resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
|
||||
|
||||
@@ -7811,6 +7842,9 @@ packages:
|
||||
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
isoformat@0.2.1:
|
||||
resolution: {integrity: sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==}
|
||||
|
||||
isomorphic-fetch@3.0.0:
|
||||
resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==}
|
||||
|
||||
@@ -15036,6 +15070,12 @@ snapshots:
|
||||
- '@swc/core'
|
||||
- debug
|
||||
|
||||
'@observablehq/plot@0.6.17':
|
||||
dependencies:
|
||||
d3: 7.9.0
|
||||
interval-tree-1d: 1.0.4
|
||||
isoformat: 0.2.1
|
||||
|
||||
'@opentelemetry/api-logs@0.217.0':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.1
|
||||
@@ -17682,6 +17722,8 @@ snapshots:
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
binary-search-bounds@2.0.5: {}
|
||||
|
||||
birpc@2.9.0: {}
|
||||
|
||||
bl@4.1.0:
|
||||
@@ -19772,6 +19814,10 @@ snapshots:
|
||||
|
||||
interpret@3.1.1: {}
|
||||
|
||||
interval-tree-1d@1.0.4:
|
||||
dependencies:
|
||||
binary-search-bounds: 2.0.5
|
||||
|
||||
intl-messageformat@10.7.18:
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.3.6
|
||||
@@ -19897,6 +19943,8 @@ snapshots:
|
||||
|
||||
isobject@3.0.1: {}
|
||||
|
||||
isoformat@0.2.1: {}
|
||||
|
||||
isomorphic-fetch@3.0.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
|
||||
Reference in New Issue
Block a user