Data Visualization
A chart is read by people and executed by you. This skill turns "make it look
good" into a procedure with checks, so the result is right by construction rather
than by taste.
The method here is design-system-agnostic. Nothing in the procedure, the form
heuristic, the six checks, or the mark specs is specific to one product. A design
system supplies a small set of parameters (its ramps, a categorical order, a
diverging pair, a status palette, a texture, its surfaces, its filter components);
the method consumes them unchanged. A validated default palette is the
reference instance, fully specified in references/palette.md. To target your
brand, read that file's structure and substitute its values - touch nothing else.
The single most important habit: the color part is computable, so compute it.
Never eyeball whether a palette is colorblind-safe - run scripts/validate_palette.js.
The procedure - do these in order
Color comes LAST. Most bad charts pick colors first.
- Pick the form. What is the data's job - magnitude, identity, polarity, a
single headline, change-over-time? The job picks the chart type, and sometimes
the answer is not a chart (a stat tile or hero number). ->
references/choosing-a-form.md
- Assign color by the job it does. Categorical (identity), sequential
(magnitude), diverging (polarity), or status (state) - each has one rule.
Assign categorical hues in fixed order, never cycled. ->
references/color-formula.md
- VALIDATE the palette - run the script, don't reason about Delta E.
node scripts/validate_palette.js "<hex,hex,...>" --mode light (relative to
this skill's base directory - or load it as <script type="module"> in the
chart's own page, where it reads
data-palette off <body> and logs a console.table report). It returns
pass/fail on the lightness band, chroma floor, adjacent-pair CVD separation,
the normal-vision floor, and contrast. Fix anything that FAILs before continuing. Re-run for
--mode dark with that mode's surface.
- Apply mark specs & spacers. Thin marks, 4px rounded data-ends anchored to
the baseline, 2px lines, >=8px markers, a 2px surface gap between fills (stacked
segments and adjacent bars alike) and a 2px surface ring on overlapping marks,
selective direct labels. ->
references/marks-and-anatomy.md
- Add the hover layer - by default. An HTML/SVG chart is interactive; ship
a crosshair+tooltip on line/area and a per-mark hover tooltip on bar/dot/cell.
The only form that skips it is a bare stat tile with no plot. Hit targets bigger
than the mark; filters in one row above the charts. ->
references/interaction.md
- Final accessibility pass. For >= 2 series a legend is always present and <= 4
are also direct-labeled (a single series needs no legend box - the title names
it), so identity is never color-alone; a table view exists; dark mode is selected - its own
steps from the same ramps, validated against the dark surface, not an automatic
flip; texture is available for the CVD/print/forced-colors case.
- Render it and look at it. The validator checks color, not layout - open or
screenshot the output and eyeball it for label collisions, geometry, and overflow
before calling it done.
Then check the result against references/anti-patterns.md - it is the catalog
of what goes wrong. If your chart matches an entry, it's wrong.
Non-negotiables (true in every design system)
- Assign categorical hues in fixed order, never cycled. A 9th series is never a
generated hue - it folds into "Other," small multiples, or composite encoding.
- One axis. Never a dual-axis chart (two y-scales). Two measures of different
scale -> two charts, small multiples, or indexed to a common base. (This is the
#1 chart mistake - see anti-patterns.)
- Color follows the entity, never its rank. A filter that changes the series
count must not repaint the survivors.
- Sequential = one hue, light->dark. Diverging = two hues + a neutral gray
midpoint. Never a rainbow; never a hue at the diverging midpoint.
- Run the validator before shipping any categorical palette. CVD Delta E >= 8 is the
target (OKLab ×100); 6-8 is a floor that is legal ONLY with secondary encoding. A
normal-vision floor below 15 is a hard FAIL - full-color readers can't tell the
pair apart; re-step it on the adjacent pairlist (secondary encoding does not excuse
this one); under
--pairs all cut series or facet instead - see check 4. A contrast WARN
obligates visible labels or a table view - it is not dismissable.
- Thin marks; a legend always present for >= 2 series (none for one), with
selective direct labels (never a number on every point); recessive grid/axes.
- Text wears text tokens, never the series color - values, labels, and legends
stay in primary/secondary/muted ink; a colored mark beside them carries identity.
- Status colors are reserved (good/warning/serious/critical) and never reused
for "series 4"; they ship with an icon + label, never color alone.
Plugging in a design system
The method is invariant; only these parameters change per system. The reference
instance - every value filled in - is references/palette.md.
| Parameter |
What the system provides |
| Ramps |
the hue scales (named steps) the palette draws from |
| Categorical theme |
the fixed hue order (a named theme); default + alternates |
| Sequential hue |
the default single hue for magnitude |
| Diverging pair |
two warm/cool poles + a neutral midpoint |
| Status palette |
good / warning / serious / critical - steps distinct from categorical |
| Texture fill |
one directional hand-drawn fill, used at 45° / 135° |
| Surfaces |
light & dark chart-surface colors (the validator needs these) |
| Filter controls |
date-range & dimension controls (behavioral spec in interaction.md) |
To onboard a new system: fill those rows, feed its ramps to the validator, and let
it snap each slot to the nearest passing step. Structure and rules stay as written.
Reference files
| File |
What it answers |
references/choosing-a-form.md |
Which chart type / is it even a chart? |
references/color-formula.md |
The four jobs, the six checks, snap-to-passing |
references/marks-and-anatomy.md |
Mark specs, spacers, labels, figures, hero number |
references/interaction.md |
Tooltips & hover, filters & time ranges |
references/components.md |
The pieces a chart is made of - build each in plain HTML |
references/anti-patterns.md |
What goes wrong - check every chart against this |
references/palette.md |
The reference palette instance - every parameter, filled in; swap for your brand's |
scripts/validate_palette.js |
Runnable six-checks validator (run it; don't eyeball) |
1---2name: data-visualization-23description: Produce a chart, graph, dashboard, or any data visualization that reads as one system - elegant, accessible, and consistent in light and dark - BRAND-NEUTRAL, shipping a placeholder palette to swap for your own. Read this BEFORE generating ANY chart (bar, line, area, heatmap, scatter, sparkline, donut), choosing chart colors, building a stat tile / meter / KPI row, or laying out a dashboard. Teaches a design-system-AGNOSTIC method: a form heuristic, a color formula with a runnable validator, mark specs, and interaction rules. The method is invariant; a design system plugs in its own ramps and surfaces. A validated default palette is documented in `references/palette.md` -- swap that file's values for your brand's. Triggers on: "chart", "graph", "plot", "data viz", "dashboard", "analytics", "visualize data", "categorical colors", "sequential / diverging palette", "stat tile", "sparkline", "heatmap", "legend", "axis", "tooltip", "chart colors", "color by series".4---56# Data Visualization78A chart is **read by people and executed by you**. This skill turns "make it look9good" into a procedure with checks, so the result is right by construction rather10than by taste.1112**The method here is design-system-agnostic.** Nothing in the procedure, the form13heuristic, the six checks, or the mark specs is specific to one product. A design14system supplies a small set of *parameters* (its ramps, a categorical order, a15diverging pair, a status palette, a texture, its surfaces, its filter components);16the method consumes them unchanged. A **validated default palette** is the17reference instance, fully specified in `references/palette.md`. To target your18brand, read that file's structure and substitute its values - touch nothing else.1920> The single most important habit: **the color part is computable, so compute it.**21> Never eyeball whether a palette is colorblind-safe - run `scripts/validate_palette.js`.2223## The procedure - do these in order2425Color comes LAST. Most bad charts pick colors first.26271. **Pick the form.** What is the data's job - magnitude, identity, polarity, a28 single headline, change-over-time? The job picks the chart type, and sometimes29 the answer is *not a chart* (a stat tile or hero number). -> `references/choosing-a-form.md`302. **Assign color by the job it does.** Categorical (identity), sequential31 (magnitude), diverging (polarity), or status (state) - each has one rule.32 Assign categorical hues in fixed order, never cycled. -> `references/color-formula.md`333. **VALIDATE the palette - run the script, don't reason about Delta E.**34 `node scripts/validate_palette.js "<hex,hex,...>" --mode light` (relative to35 this skill's base directory - or load it as `<script type="module">` in the36 chart's own page, where it reads37 `data-palette` off `<body>` and logs a `console.table` report). It returns38 pass/fail on the lightness band, chroma floor, adjacent-pair CVD separation,39 the normal-vision floor, and contrast. Fix anything that FAILs before continuing. Re-run for40 `--mode dark` with that mode's surface.414. **Apply mark specs & spacers.** Thin marks, 4px rounded data-ends anchored to42 the baseline, 2px lines, >=8px markers, a 2px surface gap between fills (stacked43 segments and adjacent bars alike) and a 2px surface ring on overlapping marks,44 selective direct labels. -> `references/marks-and-anatomy.md`455. **Add the hover layer - by default.** An HTML/SVG chart *is* interactive; ship46 a crosshair+tooltip on line/area and a per-mark hover tooltip on bar/dot/cell.47 The only form that skips it is a bare stat tile with no plot. Hit targets bigger48 than the mark; filters in one row above the charts. -> `references/interaction.md`496. **Final accessibility pass.** For >= 2 series a legend is always present and <= 450 are also direct-labeled (a single series needs no legend box - the title names51 it), so identity is never color-alone; a table view exists; dark mode is **selected** - its own52 steps from the same ramps, validated against the dark surface, not an automatic53 flip; texture is available for the CVD/print/forced-colors case.547. **Render it and look at it.** The validator checks color, not layout - open or55 screenshot the output and eyeball it for label collisions, geometry, and overflow56 before calling it done.5758Then check the result against **`references/anti-patterns.md`** - it is the catalog59of what goes wrong. If your chart matches an entry, it's wrong.6061## Non-negotiables (true in every design system)6263- **Assign categorical hues in fixed order, never cycled.** A 9th series is never a64 generated hue - it folds into "Other," small multiples, or composite encoding.65- **One axis.** Never a dual-axis chart (two y-scales). Two measures of different66 scale -> two charts, small multiples, or indexed to a common base. *(This is the67 #1 chart mistake - see anti-patterns.)*68- **Color follows the entity, never its rank.** A filter that changes the series69 count must not repaint the survivors.70- **Sequential = one hue, light->dark. Diverging = two hues + a neutral gray71 midpoint.** Never a rainbow; never a hue at the diverging midpoint.72- **Run the validator before shipping any categorical palette.** CVD Delta E >= 8 is the73 target (OKLab ×100); 6-8 is a floor that is legal ONLY with secondary encoding. A74 normal-vision floor below 15 is a hard FAIL - full-color readers can't tell the75 pair apart; re-step it on the adjacent pairlist (secondary encoding does not excuse76 this one); under `--pairs all` cut series or facet instead - see check 4. A contrast WARN77 obligates visible labels or a table view - it is not dismissable.78- **Thin marks; a legend always present for >= 2 series (none for one), with79 selective direct labels (never a number on every point); recessive grid/axes.**80- **Text wears text tokens, never the series color** - values, labels, and legends81 stay in primary/secondary/muted ink; a colored mark beside them carries identity.82- **Status colors are reserved** (good/warning/serious/critical) and never reused83 for "series 4"; they ship with an icon + label, never color alone.8485## Plugging in a design system8687The method is invariant; only these parameters change per system. The reference88instance - every value filled in - is `references/palette.md`.8990| Parameter | What the system provides |91|---|---|92| **Ramps** | the hue scales (named steps) the palette draws from |93| **Categorical theme** | the fixed hue order (a named theme); default + alternates |94| **Sequential hue** | the default single hue for magnitude |95| **Diverging pair** | two warm/cool poles + a neutral midpoint |96| **Status palette** | good / warning / serious / critical - steps distinct from categorical |97| **Texture fill** | one directional hand-drawn fill, used at 45° / 135° |98| **Surfaces** | light & dark chart-surface colors (the validator needs these) |99| **Filter controls** | date-range & dimension controls (behavioral spec in `interaction.md`) |100101To onboard a new system: fill those rows, feed its ramps to the validator, and let102it snap each slot to the nearest passing step. Structure and rules stay as written.103104## Reference files105106| File | What it answers |107|------|-----------------|108| `references/choosing-a-form.md` | Which chart type / is it even a chart? |109| `references/color-formula.md` | The four jobs, the six checks, snap-to-passing |110| `references/marks-and-anatomy.md` | Mark specs, spacers, labels, figures, hero number |111| `references/interaction.md` | Tooltips & hover, filters & time ranges |112| `references/components.md` | The pieces a chart is made of - build each in plain HTML |113| `references/anti-patterns.md` | **What goes wrong - check every chart against this** |114| `references/palette.md` | **The reference palette instance** - every parameter, filled in; swap for your brand's |115| `scripts/validate_palette.js` | Runnable six-checks validator (run it; don't eyeball) |