Skip to content

Add categorical chart palette tokens - #366

Open
zoharma wants to merge 9 commits into
mainfrom
zma/chart-palette
Open

zoharma wants to merge 9 commits into
mainfrom
zma/chart-palette

Conversation

@zoharma

@zoharma zoharma commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a categorical chart palette to DiamondDS: 12 series colours per mode (8 core, 4 additional), each paired with a marker shape so series can be told apart without colour.

  • --ds-chart-N tokens, with -channel variants and fill opacities. Loaded with the DiamondDS theme.
  • Covers lines, graphs, surfaces, selection and labels: series colours for lines and markers, area and band fills for graphs, region fills for surfaces such as images, rest/hover/selected states for selection, and label tints with checked text contrast.
  • Generator and validator in tools/chart-palette/. palette.mjs is the source of truth. The CSS, JSON export and validation report are generated from it.
  • New Chart Colours page in Storybook (Foundation).

Light

  • Core (1–8): #7c5ee0, #ed604f, #3d3027, #9283b0, #464892, #316f5e, #b67886, #a25500
  • Additional (9–12): #318ff8, #786941, #589704, #9c5894

Dark

  • Core (1–8): #9a7efb, #dc5453, #fff2ee, #7f6d99, #a7b9fe, #5ab09a, #9a5060, #ffb045
  • Additional (9–12): #076cd0, #b8a461, #bded9a, #beabba
Slot Name Light Dark Marker
1 violet #7c5ee0 #9a7efb plus
2 vermilion #ed604f #dc5453 square
3 sepia (neutral) #3d3027 #fff2ee diamond
4 lavender #9283b0 #7f6d99 triangle
5 indigo #464892 #a7b9fe circle
6 green #316f5e #5ab09a triangle down
7 rose #b67886 #9a5060 triangle ring
8 amber #a25500 #ffb045 star
9 blue #318ff8 #076cd0 cross
10 khaki #786941 #b8a461 diamond ring
11 fern #589704 #bded9a square ring
12 plum #9c5894 #beabba ring
image

What is checked

node tools/chart-palette/build.mjs --check fails if the generated files are stale. The validator gates, in both modes:

  • Contrast ≥ 3:1 on the chart surface and page background, and ≥ 2.25:1 on raised and dialog surfaces.
  • Data fills keep ≥ 3:1 once composited.
  • --ds-on-surface label text keeps ≥ 4.5:1 on series-coloured tints.
  • ΔE2000 ≥ 15 between any two slots, normal vision.
  • ΔE2000 ≥ 13 between core slots under protan, deutan and tritan, and ≥ 7 between any two slots.
  • One neutral slot, every other slot chromatic, hue within 20° across modes.
  • A unique marker shape per slot.

Commits

Split by function, so each can be reviewed or reverted on its own:

  1. Palette tokens, generator and validator
  2. JSON export and validation report
  3. Preview page (generated locally, not committed)
  4. Load the tokens with the theme, plus changelog
  5. Storybook docs page

Each of the first three passes build.mjs --check on its own.

Research

Sources

  • Okabe-Ito (Wong, 2011): 8 colours chosen for colour-blind readers. Used as the reference for CVD separation.
  • Paul Tol's qualitative schemes (bright, vibrant, muted): colour-blind-safe sets with a neutral grey. Used as the reference for a neutral slot and for muted tones.
  • IBM Carbon data-visualisation palettes: 14 colours, with separate light and dark sets. The closest in approach to ours.
  • GOV.UK data visualisation colours: 6 colours. Used as the reference for a contrast-first palette.
  • Methods: WCAG 2.x contrast, CIEDE2000 (ΔE), Machado et al. 2009 colour-vision-deficiency simulation (protan, deutan, tritan, full severity), OKLCH for hue and chroma, APCA as a secondary text check.

Evaluation

Every palette was measured with the same tool. "CVD" is the smallest ΔE between any two colours under protan, deutan or tritan. Contrast is the lowest of any colour on the DiamondDS chart surface and page background in that mode. Single-set palettes are tested on both the light and dark surfaces. Carbon and DiamondDS use their own light and dark sets.

Palette Colours Min ΔE (normal) Min ΔE (CVD) Min contrast, light surfaces Min contrast, dark surfaces
Okabe-Ito 8 21.7 10.9 1.23 1.11
Tol bright 7 20.5 8.6 1.70 2.91
Tol vibrant 7 18.3 8.6 1.78 3.42
Tol muted 9 15.0 11.5 1.50 1.46
GOV.UK 6 17.6 4.9 2.81 1.63
Carbon light, all 14 14 12.2 3.0 3.09 n/a
Carbon light, first 8 8 12.8 7.4 3.09 n/a
Carbon dark, all 14 14 14.5 4.9 n/a 3.52
Carbon dark, first 8 8 14.5 8.5 n/a 3.54
DiamondDS light, core 8 8 15.7 13.3 3.05 n/a
DiamondDS light, all 12 12 15.5 7.5 3.03 n/a
DiamondDS dark, core 8 8 16.4 13.5 n/a 3.11
DiamondDS dark, all 12 12 16.4 7.7 n/a 3.11

What this shows:

  • CVD separation: the core 8 beat every published set, at 13.3 and 13.5 against 3.0 to 11.5 for the others. Carbon's first 8 reach 7.4 (light) and 8.5 (dark).
  • Contrast: Carbon is the only published palette that reaches 3:1 on the chart surfaces in both of its modes. The single-set palettes are below 3:1 on at least one surface. GOV.UK just misses on light (2.81).
  • Carbon trade-off: at 14 colours its normal-vision ΔE (12.2 light, 14.5 dark) and CVD minimum (3.0 light, 4.9 dark) are below our gates. Light and dark are tuned separately, but CVD separation isn't gated, and there is no second channel.
  • Cost: twelve colours at this contrast and separation leave less room. Normal-vision ΔE is lower than Okabe-Ito, Tol bright, Tol vibrant and GOV.UK, and the all-12 CVD minimum is 7.5. The marker shape carries the weak pairs, which are listed in validation.md.

Known limits

Recorded in validation.md, not gated:

  • Blue is in the additional four. Putting it in the core misses the core CVD gate by about 0.03 ΔE, because violet and blue collapse under protan and deutan.
  • Light and dark identity: indigo, amber and plum are not nearest to their own counterpart in the other mode.
  • Some slots sit close to status or brand colours. Closeness matters little for charts.

Test plan

  • node tools/chart-palette/build.mjs --check exits 0
  • node tools/chart-palette/build.mjs, then open tools/chart-palette/render-check.html (gitignored, written locally) and check the palette preview in light and dark
  • pnpm vitest run tools/chart-palette
  • pnpm lint
  • Storybook: Foundation > Chart Colours renders in light and dark

- Add 12 series colours per mode as --ds-chart-* tokens, with channel variants and fill opacities (area, band, data, region and label states)
- Add tools/chart-palette, which generates the tokens from palette.mjs and gates the build on contrast, colour-vision separation, hue and marker checks
- Add a test that fails on stale output or a failed check
- Export the palette as chart-palette.json: colours, channels, simulated colour-vision values, thresholds, opacities, marker sizes and slots near status colours
- Generate validation.md with contrast, colour-vision, greyscale and status-colour figures
- Include both in the build and stale-output check
@zoharma
zoharma requested a review from a team October 30, 2026 12:35
- Generate render-check.html: charts, bars, scatter, regions on an image and labels in both modes
- Add switches for mode, series order, colour-vision simulation and series count
- Add seeded sample data that the preview and the docs page share
- Write the page locally on build and ignore it, so it is not committed or checked
- Import DiamondDSChartTokens.css from DiamondDSTheme so consumers get the --ds-chart-* tokens with the theme
- Add the chart palette to the changelog
- Add Foundations/Chart Colours with the swatches, example line and scatter charts, colour-vision simulation, usage rules, region and label states, and the token table
- Read the colours, thresholds, sample data and marker shapes from tools/chart-palette so the page follows the generator
- Add the page styles to storybook.css and list the page last under Foundations
@zoharma

zoharma commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor Author
Screen.Recording.2026-10-30.at.13.34.24.mp4
Screen.Recording.2026-10-30.at.13.48.10.mp4

- Change light slots 1, 4 and 5 so each pair is at least ΔE 21 apart (was 15.7)
- Keep indigo lighter than sepia (L* 7 apart) so the two don't merge in greyscale
- Regenerate tokens, JSON and validation report
- Add a colour box above each slot on the Chart Colours page, in light and dark
- Label each swatch with its hex value for that mode
@zoharma

zoharma commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Updated:

Light

  • Core (1–8): #8133d6, #ed604f, #610c12, #8a8ab9, #1e3f80, #316f5e, #b67886, #a25500
  • Additional (9–12): #318ff8, #786941, #589704, #9c5894

Dark

  • Core (1–8): #9a7efb, #dc5453, #ffebeb, #7f6d99, #a7b9fe, #5ab09a, #9a5060, #ffb045
  • Additional (9–12): #076cd0, #b8a461, #bded9a, #beabba
Slot Name Light Dark Marker
1 violet #8133d6 #9a7efb plus
2 vermilion #ed604f #dc5453 square
3 wine #610c12 #ffebeb diamond
4 lavender #8a8ab9 #7f6d99 triangle
5 indigo #1e3f80 #a7b9fe circle
6 green #316f5e #5ab09a triangle down
7 rose #b67886 #9a5060 triangle ring
8 amber #a25500 #ffb045 star
9 blue #318ff8 #076cd0 cross
10 khaki #786941 #b8a461 diamond ring
11 fern #589704 #bded9a square ring
12 plum #9c5894 #beabba ring
image image

- Add vision, show (lines, markers or both), series count, line width, cycle size, per-cycle line style and gap controls to render-check.html and the Chart Colours page
- Cycle the colours in sets of 8 or 12, with solid, dashed and dotted lines per cycle; gaps in a broken line take the colour half way to the surface
- Build the Chart Colours controls with MUI and drop its scatter example
- Style render-check.html with the DiamondDS tokens
@zoharma

zoharma commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author
  • Add vision, show (lines, markers or both), series count, line width, cycle size, per-cycle line style and gap controls to render-check.html and the Chart Colours page
  • Cycle the colours in sets of 8 or 12, with solid, dashed and dotted lines per cycle; gaps in a broken line take the colour half way to the surface
image

- Slot 3 is now wine (#610c12 light, #ffebeb dark), still the darkest colour in light and the lightest in dark
- Drop the neutral-slot gate; every slot now needs chroma of at least 0.02 in both modes, and hue identity applies to all 12
- Regenerate tokens, JSON and validation report

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant