Data Visualization
Seven categorical slots, CVD-verified in CI — brand blue is slot 1 and status colors are reserved
Data visualization in this system: what earns a chart, the anatomy every chart shares, and the machine-verified palette. One rule above all — the data is the only thing allowed to be loud.
Form first — is it even a chart?
Chart it when
Not a chart
Anatomy — every chart is these parts
Frame with title + mono subtitle · legend (always, for two or more series; never for one) · recessive hairline gridlines · mono axis labels · thin marks in slot colors · tooltip on hover/focus. Hover a column below — the whole column is the hit target, not just the bar.
<figure class="ns-chart">
<div class="ns-chart__head">
<span class="ns-chart__title">Course completions</span>
<span class="ns-chart__sub">weekly · last 6 weeks</span>
</div>
<ul class="ns-chart__legend">
<li class="ns-chart__key" data-c="1"><span class="ns-chart__swatch"></span>Admin track</li>
<li class="ns-chart__key" data-c="2"><span class="ns-chart__swatch"></span>Developer track</li>
</ul>
<div class="ns-chart__frame">
<div class="ns-chart__y"><span>0</span><span>25</span><span>50</span><span>75</span><span>100</span></div>
<div class="ns-chart__plot">
<div class="ns-chart__col ns-chart__col--group" tabindex="0">
<span class="ns-chart__tip" style="--v:38%">W1 · admin 38 · dev 24</span>
<div class="ns-chart__bar" data-c="1" style="--v:38%"></div>
<div class="ns-chart__bar" data-c="2" style="--v:24%"></div>
</div>
<div class="ns-chart__col ns-chart__col--group" tabindex="0">
<span class="ns-chart__tip" style="--v:52%">W2 · admin 52 · dev 30</span>
<div class="ns-chart__bar" data-c="1" style="--v:52%"></div>
<div class="ns-chart__bar" data-c="2" style="--v:30%"></div>
</div>
<div class="ns-chart__col ns-chart__col--group" tabindex="0">
<span class="ns-chart__tip" style="--v:46%">W3 · admin 46 · dev 41</span>
<div class="ns-chart__bar" data-c="1" style="--v:46%"></div>
<div class="ns-chart__bar" data-c="2" style="--v:41%"></div>
</div>
<div class="ns-chart__col ns-chart__col--group" tabindex="0">
<span class="ns-chart__tip" style="--v:64%">W4 · admin 64 · dev 38</span>
<div class="ns-chart__bar" data-c="1" style="--v:64%"></div>
<div class="ns-chart__bar" data-c="2" style="--v:38%"></div>
</div>
<div class="ns-chart__col ns-chart__col--group" tabindex="0">
<span class="ns-chart__tip" style="--v:58%">W5 · admin 58 · dev 52</span>
<div class="ns-chart__bar" data-c="1" style="--v:58%"></div>
<div class="ns-chart__bar" data-c="2" style="--v:52%"></div>
</div>
<div class="ns-chart__col ns-chart__col--group" tabindex="0">
<span class="ns-chart__tip" style="--v:82%">W6 · admin 82 · dev 61</span>
<div class="ns-chart__bar" data-c="1" style="--v:82%"></div>
<div class="ns-chart__bar" data-c="2" style="--v:61%"></div>
</div>
</div>
<div class="ns-chart__x"><span>W1</span><span>W2</span><span>W3</span><span>W4</span><span>W5</span><span>W6</span></div>
</div>
</figure>Color has four jobs — and only four
Categorical answers “which series is this?” — seven fixed slots, assigned 1→7 in order, never cycled or reordered; slot 1 is always brand blue. Filtering a series out never repaints the survivors: color follows the entity.
--chart-cat-1
#0176d3 flips
--chart-cat-2
#d1490d flips
--chart-cat-3
#2e844a flips
--chart-cat-4
#9c3ad9 flips
--chart-cat-5
#0f9ba8 flips
--chart-cat-6
#a8890a flips
--chart-cat-7
#d6247a flips
Sequential answers “how much?” — one hue, light→dark, four honest steps.
--chart-seq-1
#5aa9f7 flips
--chart-seq-2
#0176d3 flips
--chart-seq-3
#0b5cab flips
--chart-seq-4
#03386b flips
Diverging answers “which side of the baseline?” — cool arm above, warm arm below, a true neutral at the midpoint (never a hue).
--chart-div-cool-3
#03386b flips
--chart-div-cool-2
#0176d3 flips
--chart-div-cool-1
#5aa9f7 flips
--chart-div-mid
#c9c7c5 flips
--chart-div-warm-1
#e3a06c flips
--chart-div-warm-2
#d1490d flips
--chart-div-warm-3
#7a2a06 flips
Status is reserved. Success/warning/error never paint a series, and a series color never means “bad”. Conditional formatting happens on TEXT with status ink — a delta beside the value — never by recoloring the mark:
The six rules, verified in CI
npm run check:palette proves all of itSeven categorical slots, CVD-verified in CI — brand blue is slot 1 and status colors are reserved