name: Data Visualization
description: >
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".
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.
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 Δ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 readsdata-paletteoff<body>and logs aconsole.tablereport). It returns pass/fail on the lightness band, chroma floor, adjacent-pair CVD separation, and contrast. Fix anything that FAILs before continuing. Re-run for--mode darkwith 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.
- 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 ≥ 12 is the target; 8–12 is a floor that is legal ONLY with secondary encoding. 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.
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.
| 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) |