Data visualization
On this page
When to use a chart
A chart earns its space when:
- The shape of the data matters more than the exact values. Trends, distributions, correlations.
- Comparison across 3+ values or 2+ series is the point. Two values is a delta, not a comparison.
- Time trend is load-bearing - “is this going up or down” is the operator question.
A chart is the wrong answer when:
- The operator needs one exact number. Use a large-type readout: H2 numeral + label + unit.
- The operator needs to compare two values. Use a delta readout (
+12%or-3in semantic color). - The operator needs to see the structure of a list. Use a table.
- The data has fewer than 3 points. A chart of 2 bars is a waste.
The default is not “chart.” The default is the simplest artifact that answers the question. A chart is a commitment.
Permitted chart types
| Type | Use | Primitive |
|---|---|---|
| Line | Time series, continuous values | wa-chart |
| Area | Time series with magnitude emphasis, stacked categories | wa-chart |
| Bar (horizontal, vertical) | Categorical comparison, counts | wa-chart |
| Radar | Multi-dimensional comparison (4–8 axes), doctrinal fit profiles | wa-radar-chart |
| Scatter | Two-variable correlation, outlier detection | wa-chart |
| Heatmap | Dense 2D data - sector risk, time-of-day engagement rates | Custom composition |
| Sparkline | Inline trend, paired with a readout - no axes, no labels | Custom composition |
Not permitted
- Pie charts - low information density, bad for comparison. Use a horizontal bar or stacked area.
- Donut charts - same problem as pie, with worse center waste.
- 3D charts of any kind - distortion from perspective, zero analytical advantage, unmistakably consumer-dashboard aesthetic.
- Treemaps - unless the hierarchy genuinely needs rendering. Rare in tactical contexts.
- Gauge / speedometer charts - imply a “redline” without being useful at reading it. Use a progress bar or a horizontal threshold line.
Anatomy
Every chart carries up to six elements, each with a defined type treatment:
- Title - Inter 600 14px,
fog-100, left-aligned above the plot. One line. - Subtitle / unit - Inter 400 12px,
fog-200. Time range, unit of measure, data source. Optional but encouraged. - Y axis - label rotated 90°, JetBrains Mono 10px,
steel-300. Tick marks only at major gridlines. Unit goes in the subtitle, not on every tick. - X axis - JetBrains Mono 10px,
steel-300. Time labels formatHH:MMZ(24h Zulu for timelines spanning <24h) orYYYY-MM-DDfor longer spans. - Gridlines - horizontal only,
steel-500at 30% opacity, 0.5px. No vertical gridlines. Gridlines whisper - they are orientation, not decoration. - Data series - see color rules below.
Color rules
- Single series:
electric-500. - Categorical series with semantic meaning: use the semantic palette directly. Friendly track count →
electric-500; hostile →hostile; neutral →neutral; caution state →caution. The chart’s legend doubles as a classification key - the operator already knows what red means in a tactical context. - Multi-series without semantic meaning: assign in order
electric-500,electric-300,fog-200,steel-400. Beyond four, add distinguishing shape - dashed line, dotted line, different marker - rather than inventing more colors. Four distinct colors is the operator-reliable ceiling. - Emphasis line (threshold, benchmark, target):
cautionamber, 1px dashed, labeled in caution at the right edge of the line with Inter 500 UC tracked 10px. - Selected point or highlighted range:
electric-500fill with--fdt-glow-sm. The selection is the one thing that lights up; everything else dims to 60% opacity.
Color is reinforcement, not semantics. Every chart passes color-blind review. Series are distinguished by shape, position, or pattern first; color reinforces the reading. A chart that relies on red-vs-green as its only signal fails.
Typography in charts
| Element | Font | Size | Color |
|---|---|---|---|
| Title | Inter 600 | 14px | fog-100 |
| Subtitle | Inter 400 | 12px | fog-200 |
| Axis tick labels | JetBrains Mono 400 | 10px | steel-300 |
| Axis titles | Inter 500 UC tracked 0.08em | 10px | steel-300 |
| Legend labels | Inter 500 | 11px | fog-200 |
| Data value callouts | JetBrains Mono 500 | 11px | fog-100 |
| Annotations | Inter 400 italic | 11px | steel-300 |
Numeric values are always JetBrains Mono with tabular figures - the axis needs the 9 and the 4 to land on the same baseline regardless of which digit sits above them.
Animation
- Entrance animation on first render: permitted. 250ms ease-out, draw from origin outward. No stagger across series, no bounce, no spring.
- Update animation on data change: permitted. 150ms linear interpolation between old and new values. No morph dramatics.
- Hover tooltips: instant, no delay. The operator is asking a question; don’t make them wait.
- Never animate decoratively. No “reveal when scrolled into view,” no counter-ups on numeric readouts, no background particles, no gradient flows. A chart is not a loading screen.
Under prefers-reduced-motion: reduce, entrance animations are disabled; charts render in their final state immediately.
Empty and error states
Charts follow the same state conventions as System states:
- No data - centered
steel-300inboxicon,NO DATAuppercase label in Barlow Condensed, one-line description, primary action if applicable (adjust filters, change scope). The plot area renders with gridlines faded to 15% opacity so the empty frame is still recognizable as a chart, not broken UI. - Data error / fetch failure -
octagon-exclamationinhostile-400, error code in JetBrains Mono, title + description, retry action. Plot area empty. - Loading - see Loading & progress states. The plot renders a shimmer skeleton matching the chart’s grid.
Sparklines
Sparklines accompany readouts - they live inline, not in isolation. A sparkline is the shape of the number next to it.
- Height: 24px, matching single-line readout height
- Width: 60–120px depending on container
- No axes, no gridlines, no labels - if any of those are needed, it’s a chart, not a sparkline
- Color:
electric-500by default;hostile-400if the most recent value crosses a downward threshold;neutralif crossing an upward target - Paired readout provides the exact number; the sparkline provides the shape. They’re one composed element, not two adjacent ones.
Example composition:
ENGAGEMENT RATE 84% ↗ [sparkline] past 24h
The uppercase label is Barlow Condensed 700 10px tracked. The number is Inter 700 32px. The arrow is the delta indicator. The sparkline is 80px wide. The suffix is Inter 400 11px steel-300.
Dashboards
Dashboards compose multiple charts + readouts on one canvas. Rules:
- Reading order top-left to bottom-right, aligned to a 12-column grid.
- Readouts above charts - the summary number precedes its trend visualization. The operator reads the answer, then the shape that supports it.
- Maximum 8 charts per dashboard view. Beyond that, operators stop reading and start skimming. Split into tabs or drill-downs, not more rows.
- Classification strip at top and bottom of the dashboard surface, matching product-level rules.
- Single
LAST UPDATED HH:MMZtimestamp in the top-right of the dashboard frame - not per chart. One clock for the whole surface. - Refresh rhythm indicated below the timestamp if data auto-refreshes:
UPDATES · 30Sin Barlow Condensed uppercase tracked.
Tactical vs analytical register
FDT renders two classes of chart with different visual weight:
- Tactical charts - in-product, live data, mission-critical. Dense, quiet, minimal padding, JetBrains Mono labels dominant,
electric-500primary, minimal annotation. The operator glances. Example: a GHOST GRID resource-burn readout. - Analytical charts - briefings, reports, decks, program reviews. More padding, slightly larger type, annotations welcome, semantic color usage permitted on multi-series, gridlines slightly more present. The reader dwells. Example: quarterly engagement outcomes in a program review slide.
Both render the same underlying data. The difference is the pace at which the reader consumes the surface. Tactical = glance; analytical = dwell. Don’t mix - a tactical dashboard with analytical chart annotations is overdecorated and slow; an analytical briefing with tactical charts feels sparse and under-informative.