v1.16.0 — a non-colour channel for categorical axes - #24
Merged
Conversation
v1.15.0 added three categorical colours and documented a hard ceiling: a fourth drops the worst pair to ΔE ≈ 3.0 under simulated red-green colour blindness. This is the channel that scales past it. The number that settles the fourth-colour question is already inside the tokens. Desaturated, cat-indigo and cat-magenta measure L* 40.7 and 40.6 — the same grey. No colour scheme survives a black-and-white print, so the ceiling was never going to move by adding hues. KindMark splits one distinction across three channels of decreasing coarseness: silhouette (4 values, pre-attentive, geometry), then glyph (separates kinds inside a silhouette), then label (always rendered, cannot be suppressed). Colour is one hue per silhouette and fully redundant — delete it and nothing is lost. That is the test: it must work in greyscale. Three categories plus "no category" is four, and four is exactly what the shape channel holds. The two ceilings coincide rather than fighting. MEASURED, on real Chromium rasters in greyscale (DSSIM = (1-SSIM)x100), with the floor of 30 calibrated on controls rather than asserted — circle vs octagon 13.4, square vs squircle 25.4, eye vs eye-slash 26.3, identical glyph 0: - Silhouettes: worst pair 59.0 at 32px. Rejected octagon (13.4), squircle (25.4), pentagon (53.2 and falling), and triangle — separable at 59.0, but its inscribed square is 30% of the box so it cannot hold a glyph. - Size: 55.2 / 59.0 / 68.4 / 55.5 / 37.8 at 40/32/24/16/12px; gone at 8px. - Glyph, worst within-family pair: 55.2 (lg) / 49.6 (md) / 34.8 (sm). - Minimum stroke weight binds on CONTRAST, not separation: a 0.5px cat-indigo outline antialiases to 2.17:1 on --color-bg, under WCAG 1.4.11's 3:1, and only reaches its nominal 5.87:1 at >=1.25px. Ships at 1.5px silhouette / 1.4px glyph, constant in device px. - No severity token is reachable: nearest distance to severity is 31.2 normal / 16.1 at worst under protan-deutan simulation. HONEST DEGRADATION, reported rather than buried: across all 105 pairs of 15 complete marks in greyscale the median is 82.6 and the WORST is 6.7 — two kinds in the same family differing only by glyph. That is what a family is, and it is why the label is not optional. A prerequisite found on the way: the consuming app's 15 kinds ran on 11 glyphs, four pairs sharing one — DSSIM 0, i.e. identical. Three new icons exist so no two kinds share a glyph. WHERE IT STOPS: four families. No fifth silhouette exists; the rejection table above is the whole search. Inside a family the glyph is roomy (a greedy max-min over the 36-glyph registry holds every pair above 70.0 out to 17 glyphs), but the real limit is what has to be LEARNED — four silhouettes, taught by KindLegend. That legend is four rows for fifteen kinds and stays four for fifty. A legend that grew with the axis would be the admission that the encoding had failed. Gates: typecheck, lint (0 errors; two v-html warnings matching Icon.vue's existing pattern), build incl. verify:externals and verify:dev-warnings, build-storybook, test:ci 237 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v1.15.0 added three categorical colours and documented a hard ceiling: a fourth drops the worst pair to ΔE ≈ 3.0 under simulated red-green colour blindness. This is the channel that scales past it.
The number that settles the fourth-colour question
It was already inside the tokens. Desaturated,
cat-indigoandcat-magentameasure L* 40.7 and 40.6 — the same grey.No colour scheme survives a black-and-white print. The ceiling was never going to move by adding hues, so adding a fourth would have bought a little separation for sighted users on a screen and nothing at all for anyone else.
The design
KindMarksplits one distinction across three channels of decreasing coarseness:Colour is one hue per silhouette and fully redundant. Delete it and nothing is lost. That is the test this design had to pass: it must work in greyscale.
A pleasing coincidence rather than a compromise: three categories plus "no category" is four, and four is exactly what the shape channel holds. The two ceilings coincide instead of fighting.
Measured, not asserted
DSSIM = (1−SSIM)×100 on real Chromium rasters, in greyscale. The floor of ≥30 is calibrated on controls rather than picked: circle↔octagon 13.4, square↔squircle 25.4,
eye↔eye-slash26.3, identical glyph 0.Silhouettes — worst pair 59.0 at 32px/1.5px.
Size — silhouette holds 55.2 / 59.0 / 68.4 / 55.5 / 37.8 at 40/32/24/16/12px. Gone at 8px (25.2).
Glyph, worst within-family pair — 55.2 (
lg, 15px) / 49.6 (md, 12px) / 34.8 (sm, 9px).Minimum stroke weight binds on contrast, not separation. On
--color-bga 0.5pxcat-indigooutline antialiases to 2.17:1 — under WCAG 1.4.11's 3:1 — and only reaches its nominal 5.87:1 at ≥1.25px. Ships at 1.5px silhouette / 1.4px glyph, held constant in device pixels.Colour — all four clear AA-as-text on white,
--color-bg,surface-2and their own soft (lowest 5.62:1). Distance to severity, normal / worst protan-deutan: indigo↔success 31.2/27.3, purple↔success 27.6/19.3, magenta↔success 34.1/17.9, accent↔danger 28.4/16.1. No severity token is reachable.Honest degradation
Across all 105 pairs of the 15 complete marks in greyscale the median is 82.6 — and the worst is 6.7: two kinds in the same family, differing only by glyph.
That is reported rather than buried, because it is what a family is, and it is precisely why the label is not optional. A design that claimed 15 mutually distinguishable marks would be lying.
A prerequisite found on the way
The consuming app's 15 kinds ran on 11 glyphs — four pairs each sharing one (
code,document,cloud,linkall doing double duty). DSSIM 0, i.e. identical. Three new icons (database,chat,chip) exist so that no two kinds share a glyph.Where it stops scaling
Four families. No fifth silhouette exists — the rejection table above is the whole search.
Inside a family the glyph is roomy: a greedy max-min over the 36-glyph registry holds every pair above 70.0 out to 17 glyphs. But the real limit is not geometric, it is what has to be learned — four silhouettes, taught by
KindLegend. That legend is four rows for fifteen kinds and stays four rows for fifty. A legend that grew with the axis would be the admission that the encoding had failed.Adoption
Deliberately not adopted in the consuming app in this PR — the release isn't out, and a dependency bump would break work in flight there. The app-side change is documented in place and is mechanical once this is tagged: swap
KIND_STYLES+ICONSfor one per-kind{ shape, icon, category }, render<KindMark>in place of the<Icon>+ type-label pair, and add<KindLegend>above the canvas.The existing ceiling comment there is rewritten rather than deleted, because the ceiling moved rather than vanished — it now says the ceiling is four families, not four colours, and explicitly forbids half-adopting. Recolouring to the categorical tier without the silhouettes would remove the severity collision, still fail the greyscale test, and look finished.
Gates
typecheck✓ ·lint✓ (0 errors; twov-htmlwarnings matchingIcon.vue's existing pattern) ·build✓ incl.verify:externalsandverify:dev-warnings·build-storybook✓ ·test:ci✓ 237 passed🤖 Generated with Claude Code