SWIVEL · 07 · DRAFT

Charts.

Chart components built on the Data Viz Palette. Anatomy, types, states, annotation patterns, small multiples, and do / don't rules — all in the slate / switch family so a dashboard reads as one fabric.

01

Anatomy

The canonical chart frame every type extends.

Every Swivel chart is built on the same frame: a value axis on the left, a category axis on the bottom, dashed gridlines on major y-ticks, a single-series title and time stamp in the header, and a dark tooltip on hover. Drop a chart in this frame and the rest of the design system aligns to it.

Fill rate · last 30 days Updated 14:02 · 30d range 100% 75% 50% 25% 0% FILL RATE May 1 May 8 May 15 May 22 May 30 TARGET 85% May 23, 2026 at 02:00pm Fill rate 83.2% +1.8% vs prior week 1 2 3 4 5 6 7
1
Title + timestamp
Avantt SemiBold 13/uppercase title. Antarctica 9px secondary line for last-updated / range. .demo-title · .demo-meta
2
Value axis
Left axis only. Rotated --fs-axis-title (13px UPPERCASE Antarctica) title at the midline. Tick labels right-aligned in --fs-axis-tick (12px Avantt SemiBold).
3
Gridlines
Dashed 2-3 on every major y-tick using --chart-grid. No vertical gridlines unless the x-axis is also quantitative.
4
Target / threshold
Solid-dash 4-3 line in --graphite-bg. Label sits flush to the right margin, never floating mid-plot.
5
Series
2px stroke, round joins. Color from the Data Viz Palette categorical tokens. One series gets --viz-slate by default.
6
Category axis
No axis line above the labels. Labels in --fs-axis-tick (12px Avantt SemiBold). Aim for 4-6 labels max; thin or rotate before truncating.
7
Tooltip
Surface plate, hairline stroke, anchored to the vertical guide. Title in UI/Default, series names + values in UI/Small. The active series promotes to Avantt SemiBold; everything else stays muted. See the spec card below.
TOOLTIP · MULTI-SERIES PATTERN

A single tooltip pattern works for every chart on this page. Surface plate, hairline stroke, UI/Default for the timestamp and series names, UI/Small for the right-aligned values. The hovered series promotes to Avantt SemiBold; the rest stay muted in --graphite-bg. Catch-all rows like "Other" sit at the bottom in a neutral graphite swatch.

Light · default surface
05/18/2026 at 6:00am
Tier by Label - O&O1,446
Tier_ Add/Remove - Backfill84
SOV_MultiPub_Script28
Tier_ Add/Remove - Primary39
Budget_Weighted FW MPP VAC35
CPM_Dynamic Floors_Script24
Tier_ Add/Remove - Sequential17
BlockList_MGNI not billable_Remove0
BlockList_MGNI not billable_Add0
Margin Manager V110
Other71
Dark · graphite canvas
05/18/2026 at 8:00am
Tier by Label - O&O1,783
Tier_ Add/Remove - Backfill147
SOV_MultiPub_Script52
Tier_ Add/Remove - Primary42
Budget_Weighted FW MPP VAC23
CPM_Dynamic Floors_Script18
Tier_ Add/Remove - Sequential18
BlockList_MGNI not billable_Remove0
BlockList_MGNI not billable_Add0
Margin Manager V15
Other75

Tooltip rules

  • Fill / stroke — always --surface-base with a 1px --surface-stroke hairline. Never invert the plate per theme — the tokens already adapt.
  • Title — UI/Default (Antarctica 14px, regular). Full date + time, no abbreviations. Lives at the top, left-aligned, no underline or divider.
  • Series rows — name in UI/Default muted (--graphite-bg), value in UI/Small (Avantt SemiBold 12px) right-aligned with tabular numerals. Swatch is a 16×3 rounded line, not a square — matches the line/area-fill the tooltip describes.
  • Active row — the series the cursor is on promotes to Avantt SemiBold + --graphite-90. Only one row at a time. Don't reorder rows on hover; visual stability beats hover novelty.
  • "Other" row — always last, swatch in --graphite-20, never in a series color.
AXIS LABELS · NUMBER FORMATTING

Tickmark labels are read in passing — they need to fit in a narrow gutter without truncating, and the unit must be unambiguous at a glance. If the y-axis is a dollar value, every tick carries the currency symbol, even when the axis title already says "revenue." Abbreviate magnitudes so labels never wrap: thousands as K, millions as MM, billions as B. Apply the same rule to tooltip values, KPI cards, and bar end-labels so the formatting is consistent everywhere a number appears.

--fs-axis-tick
12px · Avantt SemiBold · UI/Small alias
$12K
Tick numbers · category labels · in-chart value labels
--fs-axis-title
13px · Antarctica Medium · UI/Caps alias · uppercase
REVENUE
Rotated value-axis title · category-axis title (rare)
Thousands · $16K range 0 – $20K
REVENUE $20K $15K $10K $5K $0 Jan Feb Mar Apr May Jun
Millions · $8MM range 0 – $10MM
BOOKINGS $10MM $7.5MM $5MM $2.5MM $0 Q1 '24 Q2 '24 Q3 '24 Q4 '24 Q1 '25 Q2 '25
close Don't write raw values on the tick
16000 12000 8000 0
No currency symbol leaves the reader guessing — are these dollars, impressions, milliseconds? Long unabbreviated numbers eat the gutter, push the plot inward, and force the eye to count zeros.
check Do prefix the currency and abbreviate
$16K $12K $8K $0
Same data, same gutter, half the ink. $16,000 → $16K; 8,000,000 → $8MM. The currency symbol is on every tick, not just the topmost — readers scan ticks in any order.

Axis label rules

  • Typography is tokenized — tick + category labels use --fs-axis-tick (12px Avantt SemiBold), rotated value-axis titles use --fs-axis-title (13px Antarctica Medium, UPPERCASE, 4% tracking). Don't hand-set sizes in chart code; the aliases keep every chart at the same density and theme-swap cleanly.
  • Currency on every tick — if the metric is money, every label carries $ (or € / £ / etc.). Including the symbol on the axis title alone isn't enough; readers don't scan top-down.
  • Abbreviate by magnitude — 1,000 → 1K, 1,000,000 → 1MM, 1,000,000,000 → 1B. Use MM (not M) for millions in finance contexts to avoid confusion with thousands in roman-numeral notation; switch to M only if the surrounding product uses it consistently.
  • Pick one suffix per axis — don't mix $500, $1.5K, $2K on the same scale. Snap the whole axis to the largest tick's unit so labels line up in length.
  • Precision follows the range — 0–10MM gets one decimal ($2.5MM); 0–100MM rounds to whole units ($25MM). Never write more decimals than the data resolves.
  • Non-currency metrics still abbreviate — 1.2K impressions, 8MM bids. Just drop the $. Percentages keep % on every tick by the same rule.
  • Negative values — currency symbol goes inside the sign: −$2.4K, never $−2.4K. Divergent bars and waterfall charts both rely on this.
  • Tooltips show full precision — the axis abbreviates for scanning; the tooltip is where exact figures live ($16,482.30). Same goes for bar end-labels when the chart is wide enough.
02

Line

Continuous metric over time. The default chart for ratios and rates.

Use line for continuous metrics over time — fill rate, CPM, bid density, ROI. One series uses --viz-slate. Add up to five comparators using the categorical palette in order. Above six, switch to small multiples (§11) instead of stacking lines.

Single series --viz-slate · 2px
Multi-series · 3 lines slate · switch · deep
CTV Video Web Display

Rules

  • Order series by importance, not alphabetically. Primary metric → --viz-slate, comparator → --viz-switch, then deep, steel, mist, graphite.
  • Connect through missing data with a dashed segment. Don't drop to zero on null.
  • No markers on lines < 200px wide — they crowd the path. Use markers on small multiples and sparklines instead.
03

Area

Magnitude over time. Use when total matters more than rate-of-change.

Single-series area is a line with a 10-15% fill underneath, anchored to the zero baseline. Stacked area for part-of-whole over time. Keep the palette to the slate/switch family — never mix in semantic colors (those mean "good/bad," not "category").

Single area --viz-slate fill @ 12%
Stacked area · 5 series categorical 1…5
CTV Video Web Audio Display Native
04

Bar

Categorical comparison. Sortable, rankable, and almost always the right answer.

Bars beat pies, donuts, and most "creative" charts at the comparison job. Four variants cover 90% of cases: vertical for time, horizontal for ranked categories, grouped for split comparisons, stacked for part-of-whole. Always sort by value unless time order is the point.

Vertical · over time --viz-slate
Horizontal · ranked --viz-slate · sorted desc
Grouped · split comparison slate · switch
This week Prior week
Stacked · part-of-whole categorical 1…4
CTV Video Web Audio Display
Divergent · plus/minus from baseline --div-1…9 · centered on 0
05

KPI card

The most-used pattern on the Command surface.

A KPI card pairs a single value with a directional delta. The number is the answer; the delta is the trend. No axes, no chart — if you need history, promote to a real chart. Stick to --viz-slate and let the delta color carry good/bad.

Fill rate
83.2% ↑ +1.8%
eCPM
$14.62 ↑ +$0.42
Bid density
4.81 → flat
Pacing variance
−2.4% ↓ −1.1%

KPI card rules

  • One number, one label, one delta. If you need a second dimension, the answer is a chart, not a bigger card.
  • Don't bury the delta. Arrow + value next to the metric. Color from --success / --error / --slate-100.
  • The label says what window the delta covers ("vs. last 7d"), not the card chrome.
06

Donut

One number with one breakdown. Sparingly.

Use donuts only when there's a single dominant fact you want to anchor in the center (a percent, a count) and at most three or four slices supporting it. Past four slices, switch to a horizontal bar — humans can rank lengths but not arc lengths. Never use a pie chart with no center; the donut's center is what justifies the format.

CTV 64% Video 24% Audio 12%
82% of monthly pacing target
When to swap
  • Five or more slices → horizontal bar
  • Slice values within 5% of each other → bar
  • No anchor number for the center → bar
  • You need trend, not snapshot → line
07

Heatmap

Two categorical dimensions, one magnitude. Uses sequential ramps.

A grid of cells colored by a sequential ramp from the palette. Heatmaps reward dense data — give them enough rows and columns to show pattern, not so many cells become noise. Pair with a one-line color legend below.

Fill rate · day × hour --seq-slate-1…7
0% 100%
08

Scatter

Two continuous dimensions. Optional third encoded as size.

Use scatter when the question is "are these correlated" or "where are the outliers." Plot points at 4-5px radius, 60% opacity so density reads as darker clusters. For a categorical third dimension, color by the categorical palette in order. For a continuous third, encode as area (not radius — area is what eyes compare).

CPM × fill rate --viz-slate · 60% alpha
CPM × fill rate × format categorical · size = impressions
CTV Video Web Audio
09

Bullet

Actual vs. target with a qualitative range. Replaces gauges.

A bullet chart fits a gauge's job in a tenth of the space. A solid bar shows actual, a vertical tick shows target, and a banded background shows poor/ok/good ranges. Use any time someone asks for a gauge or speedometer.

Pacing · 4 campaigns vs. target slate · band = seq-slate-1/2/3
10

States

Loading, empty, no-data, partial, and error.

Every chart has five non-happy states. Treat them as first-class components, not afterthoughts. A skeleton tells the user data is on the way; an empty state tells them the query is valid but turned up nothing; a no-data slice tells them part of the range is unknown; an error state tells them the fetch failed and offers a retry.

Loading · skeleton animated · 1.6s linear
Replace the chart with grayscale shapes of roughly the same dimensions. Animate a 1.6s linear shimmer left → right. Never use a spinner inside a chart frame — the frame's geometry is the skeleton.
Empty · no matches query returned 0 rows
filter_alt_off
No campaigns match these filters.
Try removing the format filter, or expanding the date range past 7 days.
A valid query that returned nothing. Tell the user why and what to change. Never just say "no data" — that conflates empty with broken.
Partial · gap in series missing window rendered dashed
When part of the range is missing, draw the known segments solid, the gap dashed, and tag the affected window in an annotation. Never silently drop to zero.
Error · fetch failed --error · retry available
error
Couldn't load this chart.
The upstream connector returned a 502. We'll keep trying.
Distinct from empty. Explains the failure in plain language, names the source, and offers a retry. Keep the chart frame visible so the layout doesn't collapse.
11

Annotations

Target lines, event markers, range bands, and callouts.

Annotations are how a chart says something. They earn their place — every annotation should answer a question the chart's bare shape doesn't. Four canonical kinds: target line, event marker, range band, point callout.

Target line--graphite-bg · dash 4 3
Event marker--slate-100 · vertical guide
Range band--seq-slate-1 · 60% alpha
Point callout--switch-contrast · 4px dot
12

Small multiples

When you'd otherwise stack six lines on one chart.

A grid of tiny charts on a shared scale. Reach for these the moment a multi-series line gets crowded — they always win the comparison job past three series. Use the same y-scale across every tile so heights compare directly, and never use more than one series color per tile.

13

Do / Don't

The five biggest ways charts go off-brand.

Most chart problems come from the same handful of mistakes. Each pair below shows the wrong choice and the right one.

close Don't truncate the y-axis on bar charts.
Truncating to "show the change" inflates a 2% difference into a wall. Bars must start at zero. Use a line chart with a zoomed y-axis if the absolute change is small.
check Do start bar charts at zero.
The y-axis runs from 0 to the natural max. The difference reads accurately, and the annotation calls out the magnitude in words.
close Don't sort bars alphabetically.
Alpha order forces the eye to scan everything. The ranking is the insight; alphabet is incidental.
check Do sort bars by value.
Descending by default. Time series is the exception — sort by time. Geography is the other exception — sort by region or alpha there.
close Don't use a rainbow palette.
Hue jumps treat categories as ordered when they aren't, and red / green look like good / bad. Stay inside the slate / switch family.
check Do use the Swivel categorical palette.
Primary in --viz-slate, comparator in --viz-switch, then deep → steel → mist → graphite. Past six series, switch to small multiples.
close Don't put 7 slices in a pie.
Eyes can't rank 7 arc lengths. Crowding the legend with a percent next to each slice doesn't help — you've turned the chart into a table with extra steps.
check Do use a horizontal bar.
Same data, ranked, with the values right where the bar ends. Five seconds to read instead of fifteen.
close Don't add 3D, drop shadows, or gradients.
Depth distorts magnitude. Drop shadows on bars push the eye to the wrong edge. Gradients suggest a continuous scale where the data is categorical.
check Do keep marks flat and crisp.
Solid fills, sharp edges, no gradient. The brand reads as a precise instrument — every chart should too.
USAGE · CHEAT SHEET
  • Line — continuous metric over time. Default for fill rate, CPM, ROI.
  • Area — magnitude over time. Total matters more than rate-of-change.
  • Bar (vertical) — value over time when the gap between points matters.
  • Bar (horizontal) — ranked categories. Default for "top N."
  • Stacked bar / area — part-of-whole when the total is also interesting.
  • Divergent bar — plus/minus vs. a baseline. Use diverging palette.
  • Sparkline — context for a single metric. Inline only — don't combine with axes.
  • Donut — one anchor number with ≤4 supporting slices.
  • Heatmap — two categorical dimensions, one magnitude. Sequential palette.
  • Scatter — correlation, outliers. Two continuous dimensions.
  • Bullet — actual vs. target with qualitative bands. Use instead of a gauge.
  • Small multiples — past three series, always.