Foundations
The rule sets every language, chart, and page builds on. Numbered, permanent, cited where perception research backs a law.
FN-01 . Token contract
The rule that makes the whole catalogue themeable and deterministic:
Chart and component specs reference color ROLES, never hex values. Design languages (DL files) bind roles to hex. Rebranding = swapping the DL token file. No spec changes, no re-authoring.
Required roles (every DL must bind all of these)
| Role | Meaning |
|---|---|
background | page/canvas fill |
ink | headings, key numbers, strongest text |
body | supporting text, labels, sources |
primary | the one accent; highlights, key series |
muted | context series, de-emphasized marks |
gridline | chart gridlines |
hairline | rules, baselines, separators |
positive | good deltas (always paired with a word, never alone) |
negative | bad deltas (same) |
Optional roles a DL may add: panel, benchmark, warning, primaryDark, and others. Specs that want an optional role declare a fallback chain, for example "context": ["benchmark", "muted"]: first role present in the palette wins. The renderer resolves the chain, so a spec never breaks on a DL that lacks an optional role.
Rules
- Hex values live in
tokens/DL-xx.jsononly. A hex value inside a chart spec or component spec fails validation on sight. - All hex is
#RRGGBBuppercase. Enforced byschema/dl.schema.jsonandtools/validate.py. inkandbodymust reach 4.5:1 contrast onbackground(WCAG AA). Enforced bytools/validate.py.- Fonts follow the same contract: specs reference
displayorbody, the DL binds each to a family stack whose last entries are installed-everywhere fallbacks.
FN-02 . Canvas and spacing grid
Canvases
| Target | Size | Use |
|---|---|---|
| Chart entry golden render | 960 x 540 px | every CH-xx golden |
| Full slide (16:9) | 1280 x 720 px | recipes (DL/CH/CP combinations) |
All positions and sizes in specs are exact pixels on these canvases. Adjectives ("comfortable margin", "medium bar") are banned in specs; a value the renderer cannot consume is not a spec.
The 8px grid
Every structural distance is a multiple of 8: margins, offsets, bar heights, gaps, plot bounds.
| Distance | Value |
|---|---|
| Canvas edge to content | 32 px |
| Title block to plot area | >= 40 px |
| Bar height / bar gap (CH-01) | 32 / 16 px |
| Label offset from mark | 8 or 16 px |
Text sizes are exempt from the 8-multiple (they follow FN-03) but their anchor positions are not.
Fixed furniture on every chart canvas
- Title: x=32, baseline y=56 (y=88 when the DL chrome adds an eyebrow).
- Source line: x=32, baseline y=516, always present, always bottom-left.
- Kicker + eyebrow: only when the DL's
chromeblock asks for them; kicker 24x4 px at (32, 28), eyebrow 11 px caps at (32, 52).
FN-03 . Type scale
Sizes are fixed per text role on the 960x540 chart canvas. A DL changes the family and weight, never the size or position (those belong to the chart spec).
| Text role | Size | Font slot | Notes |
|---|---|---|---|
| Title | 24 px | display | full sentence, states the finding with a number |
| Eyebrow | 11 px | body, weight 600 | CAPS, letter-spacing 2, only if DL chrome |
| Category / series label | 13 px | body | weight 600 only on the highlighted item |
| Value label | 13 px | body | weight 600 only on the highlighted item |
| Tick label | 11 px | body | |
| X-axis label | 11 px | body | |
| Annotation | 12 px | body, italic | |
| Source line | 11 px | body |
Rules
- Two font slots only:
display(titles) andbody(everything else). A third slot (monofor IDs and timestamps) is reserved for DLs that declare it; no spec may require it. - Every DL family stack ends in an installed-everywhere fallback (Segoe UI, Arial, Georgia, Consolas). Silent substitution with a different metric font is the failure mode this prevents.
- Nothing below 11 px on the 960x540 canvas. On the 1280x720 slide canvas the floor is 12 px.
- Emphasis is weight (400 to 600), never a size bump, never underline.
FN-04 . Contrast and color use
Hard limits (validated, not advisory)
inkandbodyreach 4.5:1 onbackground(WCAG AA body text).tools/validate.pycomputes this from the DL token file and blocks on failure.- Meaning is never carried by color alone. Status, deltas, and exceptions always pair color with a word or a number.
- One saturated highlight per chart. The
primaryrole appears on the one mark the title talks about; everything else usesmutedorbenchmark.
Discipline
- Series count before color count: if a chart needs more than 4 colors, the chart is wrong (split into small multiples), not the palette.
positiveandnegativemark deltas and status, never decorate.- Charts must survive grayscale: the highlighted mark must still read as the highlight through weight, position, or label emphasis.
The banned list (applies to every DL, every chart)
- Purple/blue gradient backgrounds, "hero" gradient bands
- Rainbow categorical palettes (one hue per bar)
- Color-only status dots
- Anything below AA contrast, including gray-on-gray captions
- 3D depth, glassmorphism, heavy drop shadows
FN-05 . Numbers, units, and scales
Numbers
- Tabular (monospaced-figure) rendering wherever numbers stack in columns; right-aligned in tables.
- Units appear ONCE per chart, not per value: - bar charts: on the first (largest) value label ("34 items", then "28", "21", ...) - line charts: on the top y tick label ("80%", then "60", "40", ...)
- Missing value = "-" (hyphen nil marker), never zero, never a guess.
- Titles state the finding with a number. "Backlog overview" fails QA; "Electrical holds 34 of 109 open items" passes.
Scales (deterministic by construction)
Bars start at zero, always, no exceptions. Line charts start at zero by default; a non-zero baseline requires a written justification in the entry that uses it.
Tick generation is an algorithm, not taste, so two renders can never disagree:
raw = max_value / target_ticks (target_ticks = 5)
mag = 10 ^ floor(log10(raw))
step = first of {1, 2, 5, 10} * mag that is >= raw
top = ceil(max_value / step) * step
ticks = 0, step, 2*step, ... top
Direct-label bar charts skip the axis entirely and scale bars to the max value; the labels carry the numbers.
Float formatting in renders
All coordinates format as max-2-decimal, trailing zeros stripped, "-0" normalized to "0". This is what makes byte-identical re-renders possible across machines.
FN-06 . Perceptual foundations
Every OVP rule traces to a named finding in visual perception research, not to taste. This entry is the trace, one line per rule family. It is deliberately one page: OVP cites the science, it does not rewrite it.
| OVP rule | Perceptual basis |
|---|---|
One primary highlight per chart (FN-04) | Preattentive pop-out: one distinct hue is found in constant time; two or more force serial search (Treisman; Ware) |
| Bars and positions over pies and areas (CH family design) | Graphical perception hierarchy: position and length are decoded far more accurately than angle and area (Cleveland & McGill) |
Pie capped at 3 slices, warn status (CH-PTW-03/04) | Angle judgment error grows with slice count; under ~15% differences angles are unreadable (Cleveland & McGill) |
| Max 4 stacked segments, max 5 KPI cards | Working-memory span: ~4 chunks held reliably (Cowan; Miller) |
| Direct labels and line-end labels, never legends | Every legend lookup is a saccade round-trip that reloads working memory (Ware; Few) |
| Annotation anchored at the data (CH-TIM rules) | Attention follows pointers; unanchored commentary is ignored under load (Ware) |
| 8px grid, gaps group related elements (FN-02) | Gestalt proximity and common region: spacing IS structure |
| Status word + color, never color alone (FN-04) | ~8% of men have deficient color vision; hue is not a channel you can require (accessibility canon) |
| Zero baselines on bars and areas (FN-05) | Length encoding lies when truncated; the lie compounds with fill (Tufte's lie factor) |
| Muted context + one saturated series | Contrast drives salience; salience should follow the message, not the palette (Ware; Few) |
| Action titles stating the finding (CORE rule 1) | Assertion-evidence structure improves recall and decision agreement versus topic titles (presentation research canon) |
| Tabular figures, right-aligned numbers (FN-05) | Digit alignment enables column scanning as a single visual object |
Canon referenced: Bertin (Semiologie graphique), Cleveland & McGill (graphical perception experiments), Ware (Information Visualization), Munzner (Visualization Analysis and Design), Tufte (VDQI), Few (Show Me the Numbers), Treisman (preattentive processing), Cowan/Miller (working memory), Kahneman (attention and effort).
Rule for extending OVP: a new rule that cannot cite a row here, or add one, is a preference, and preferences do not enter the protocol.