Eval Graphics — the chart room
Internal tooling, not a product skill. This lives in eval/design-system/
and is used by maintainers to publish eval studies. It is not a Newsjack
user skill, must never be installed into skills/, and is never loaded at
product runtime. Do not confuse it with the main skills folder.
You produce figures for an eval study: standalone HTML that renders the
Newsjack house chart style, validated by Playwright and screenshotted to PNGs you
drop into a writeup. One grammar — newsprint paper, ink, a single vermilion mark
— across every figure, so a study reads like one publication.
Files you work with
assets/colors_and_type.css — design tokens (palette, type, spacing). Never
edit; always link.
charts.css — the chart primitives (.bar-primary, .line-base, .fig,
.stat, heatmap classes, masthead/section/colophon scaffold). Never edit;
always link.
chart-room.html — the specimen gallery of all 9 figure types with
placeholder data. This is your copy-paste source: find the figure that fits
your data, lift its block, swap the geometry and labels.
scripts/validate.mjs — Playwright validator + screenshotter.
The grammar (non-negotiable brand rules)
These come straight from the design system. Breaking one means the figure is
off-brand:
- One chroma. Vermilion
#E05A47 is the only colour. The highlighted /
winning series is accent; every other series is quiet grey (--c-base,
#E4E0D8) or an ink wash. If you reach for a second colour, you've gone
wrong. No gradients. No emoji. Ever.
- Paper, not white. Background is
--nj-page #F9F8F6. Borders are the
hairline rgba(26,26,26,0.10). Cards are 0-radius with a faint editorial
shadow.
- Type roles. Figure titles: Newsreader italic. Axis ticks, value
labels, legends, eyebrows: IBM Plex Mono, ALL CAPS, ≥0.12em tracking.
Descriptive captions: DM Sans.
- Value labels float above the bar (mono, centered over the column).
- Headline figures are one consistent colour — the whole numeral and its
symbol in the accent (e.g.
+38%, 4.2× fully vermilion), via
<span class="accent">.
- Section pattern: top hairline → mono number + lowercase-italic title (left)
- mono subtitle (right) → content.
Accent strategy — set on <body>
data-accent |
Use |
winner (default) |
best/highlighted series = accent, others grey. The standard comparison look. |
single |
comparisons go all-grey; accent is reserved for one hero mark (a donut, one bar). |
mono |
everything ink, no chroma — for a sober, neutral data study. |
Also data-grid="on|off" (gridlines) and data-barstyle="solid|outline".
Pick the figure to fit the data
| Data shape |
Figure (block in chart-room.html) |
| Two series across a few benchmarks |
FIG.01 grouped bars — the flagship |
| One series, ranked by category |
FIG.02 single-series bars |
| Before → after on sparse metrics |
FIG.03 dumbbell / lollipop |
| A value over time / scale / versions |
FIG.04 line / scaling curve |
| Two-axis tradeoff, one point highlighted |
FIG.05 scatter / quadrant |
| Composition / share across rows |
FIG.06 100% stacked bars |
| One number that deserves the frame |
FIG.07 donut |
| Model × task (or any) matrix |
FIG.08 heatmap (data-driven JS) |
| 2–4 punchy single numbers, no axes |
FIG.09 big-stat callouts |
How to compute geometry (filling an SVG template)
The SVGs use plain coordinates inside a viewBox. The mapping math you need:
Vertical bars (FIG.01/02). Choose a baseline yBase (value 0) and a top
yTop (max value). scale = (yBase - yTop) / maxVal. For a value v:
barHeight = v * scale, barY = yBase - barHeight, value label at
y = barY - 12. (Specimen FIG.01: yBase=500, 100→y=120, so scale=3.8.)
Dumbbell (FIG.03). Horizontal axis from xMin (value 0) to xMax (value
100). x(v) = xMin + (v/ (maxVal)) * (xMax - xMin). Draw a .stem line from
x(before) to x(after), a .dot-base at before, a .dot-primary at after.
Line (FIG.04). y(v) = yBase - v*scale; evenly space x across the points;
.line-primary for focal, .line-base (dashed) for baseline; optional
.area-primary polygon closes down to yBase.
Donut (FIG.07). C = 2 * π * r (specimen r=104 → C≈653.45). For percent
p: dash = (p/100) * C; set stroke-dasharray="{dash} {C}" on the accent ring,
transform="rotate(-90 cx cy)" so it starts at 12 o'clock.
Heatmap (FIG.08). Don't hand-place cells — edit the data, tasks, models
arrays in the inline <script>. Cell alpha = 0.12 + clamp((v-min)/(max-min)) * 0.88; text flips to white above alpha 0.55. Already handles any grid size.
Big-stat (FIG.09). Pure markup: <span class="fig-num"><span class="accent"> +38</span>%</span> — wrap the whole figure (or the part that should be coloured)
in .accent. Keep the numeral and its symbol the same colour.
Build process
- Read the study's numbers (e.g. an
aggregate.py printout or
results.json). Decide which 1–4 figures tell the story; don't over-chart.
- Scaffold an HTML file in the study's run folder, e.g.
eval/<study>/runs/<run>/figures/<name>.html. Link the design system by
relative path:
<link rel="stylesheet" href="../../../../design-system/assets/colors_and_type.css">
and .../design-system/charts.css. (Count the ../ from the figure file to
eval/design-system/.) Set <body data-accent="…">.
- Lift the matching figure block(s) from
chart-room.html. Recompute every
coordinate from the real data using the math above. Replace category labels,
value labels, series names, captions, the masthead/section text. Delete the
figures you don't use.
- Keep it honest. Label sample sizes (
n=), say what the score is, don't
round a tie into a win, and don't invent a series the data doesn't have. The
angle-generator/meanest-editor anti-slop ethos applies to charts too.
- Validate with Playwright (required):
cd eval/design-system/scripts
node validate.mjs ../../<study>/runs/<run>/figures/<name>.html --out ../../<study>/runs/<run>/figures/png
All checks must pass (no JS errors, accent painted, serif applied, every
figure has geometry, no empty SVGs). It writes a full-page PNG plus one crop
per figure — those are your embeddable assets.
- Eyeball the screenshot before publishing. Read the full-page PNG; confirm
bars/labels line up and nothing overflows.
Worked example
For the Fable-5-vs-Opus-4.8 study (eval/fable-vs-opus/), the headline numbers
(overall dim mean 4.60 vs 4.36; 67/33 head-to-head; 24 vs 7 robust wins; per-
dimension deltas) map cleanly to: a FIG.01 grouped-bars of the 7 dimensions
(Fable accent, Opus grey), a FIG.07 donut for the win rate, and a row of
FIG.09 big-stat callouts (overall mean, robust wins, publishable rate). See
eval/fable-vs-opus/runs/2026-06-09-full/figures/ for the built, validated set.
Guardrails
- Never edit
assets/colors_and_type.css or charts.css to force a one-off look
— if a figure needs something the system lacks, that's a design-system change,
raise it, don't hack the study.
- Never add a second colour, a gradient, a drop shadow on a chart, or emoji.
- Never ship a figure that hasn't passed
validate.mjs.
- Placeholder data stays clearly labelled until real numbers replace it.
1---
2name: eval-graphics
3scope: eval-internal
4not_a_product_skill: true
5description: "Turn an eval study's numbers into on-brand, publish-ready figures using the Newsjack chart room (the eval design system), then validate them with Playwright. For producing the charts in a published eval/data study."
6when_to_use: "You have an eval study's results (a results.json, an aggregate table, head-to-head numbers, per-dimension deltas) and need branded figures to publish it — a flagship comparison, a win-rate donut, headline stat callouts, a heatmap, a trend line."
7---
8
9# Eval Graphics — the chart room
10
11> **Internal tooling, not a product skill.** This lives in `eval/design-system/`
12> and is used by maintainers to publish eval studies. It is **not** a Newsjack
13> user skill, must **never** be installed into `skills/`, and is never loaded at
14> product runtime. Do not confuse it with the main skills folder.
15
16You produce **figures** for an eval study: standalone HTML that renders the
17Newsjack house chart style, validated by Playwright and screenshotted to PNGs you
18drop into a writeup. One grammar — newsprint paper, ink, a single vermilion mark
19— across every figure, so a study reads like one publication.
20
21## Files you work with
22
23- `assets/colors_and_type.css` — design tokens (palette, type, spacing). Never
24 edit; always link.
25- `charts.css` — the chart primitives (`.bar-primary`, `.line-base`, `.fig`,
26 `.stat`, heatmap classes, masthead/section/colophon scaffold). Never edit;
27 always link.
28- `chart-room.html` — the **specimen gallery** of all 9 figure types with
29 placeholder data. This is your copy-paste source: find the figure that fits
30 your data, lift its block, swap the geometry and labels.
31- `scripts/validate.mjs` — Playwright validator + screenshotter.
32
33## The grammar (non-negotiable brand rules)
34
35These come straight from the design system. Breaking one means the figure is
36off-brand:
37
38- **One chroma.** Vermilion `#E05A47` is the only colour. The highlighted /
39 winning series is accent; every other series is quiet grey (`--c-base`,
40 `#E4E0D8`) or an ink wash. **If you reach for a second colour, you've gone
41 wrong.** No gradients. No emoji. Ever.
42- **Paper, not white.** Background is `--nj-page` `#F9F8F6`. Borders are the
43 hairline `rgba(26,26,26,0.10)`. Cards are 0-radius with a faint editorial
44 shadow.
45- **Type roles.** Figure titles: Newsreader **italic**. Axis ticks, value
46 labels, legends, eyebrows: IBM Plex Mono, ALL CAPS, ≥0.12em tracking.
47 Descriptive captions: DM Sans.
48- **Value labels float above the bar** (mono, centered over the column).
49- **Headline figures are one consistent colour** — the whole numeral *and* its
50 symbol in the accent (e.g. `+38%`, `4.2×` fully vermilion), via
51 `<span class="accent">`.
52- **Section pattern:** top hairline → mono number + lowercase-italic title (left)
53 + mono subtitle (right) → content.
54
55### Accent strategy — set on `<body>`
56
57| `data-accent` | Use |
58|---|---|
59| `winner` (default) | best/highlighted series = accent, others grey. The standard comparison look. |
60| `single` | comparisons go all-grey; accent is reserved for one hero mark (a donut, one bar). |
61| `mono` | everything ink, no chroma — for a sober, neutral data study. |
62
63Also `data-grid="on|off"` (gridlines) and `data-barstyle="solid|outline"`.
64
65## Pick the figure to fit the data
66
67| Data shape | Figure (block in `chart-room.html`) |
68|---|---|
69| Two series across a few benchmarks | **FIG.01 grouped bars** — the flagship |
70| One series, ranked by category | **FIG.02 single-series bars** |
71| Before → after on sparse metrics | **FIG.03 dumbbell / lollipop** |
72| A value over time / scale / versions | **FIG.04 line / scaling curve** |
73| Two-axis tradeoff, one point highlighted | **FIG.05 scatter / quadrant** |
74| Composition / share across rows | **FIG.06 100% stacked bars** |
75| One number that deserves the frame | **FIG.07 donut** |
76| Model × task (or any) matrix | **FIG.08 heatmap** (data-driven JS) |
77| 2–4 punchy single numbers, no axes | **FIG.09 big-stat callouts** |
78
79## How to compute geometry (filling an SVG template)
80
81The SVGs use plain coordinates inside a `viewBox`. The mapping math you need:
82
83**Vertical bars (FIG.01/02).** Choose a baseline `yBase` (value 0) and a top
84`yTop` (max value). `scale = (yBase - yTop) / maxVal`. For a value `v`:
85`barHeight = v * scale`, `barY = yBase - barHeight`, value label at
86`y = barY - 12`. (Specimen FIG.01: `yBase=500`, `100→y=120`, so `scale=3.8`.)
87
88**Dumbbell (FIG.03).** Horizontal axis from `xMin` (value 0) to `xMax` (value
89100). `x(v) = xMin + (v/ (maxVal)) * (xMax - xMin)`. Draw a `.stem` line from
90`x(before)` to `x(after)`, a `.dot-base` at before, a `.dot-primary` at after.
91
92**Line (FIG.04).** `y(v) = yBase - v*scale`; evenly space x across the points;
93`.line-primary` for focal, `.line-base` (dashed) for baseline; optional
94`.area-primary` polygon closes down to `yBase`.
95
96**Donut (FIG.07).** `C = 2 * π * r` (specimen `r=104` → `C≈653.45`). For percent
97`p`: `dash = (p/100) * C`; set `stroke-dasharray="{dash} {C}"` on the accent ring,
98`transform="rotate(-90 cx cy)"` so it starts at 12 o'clock.
99
100**Heatmap (FIG.08).** Don't hand-place cells — edit the `data`, `tasks`, `models`
101arrays in the inline `<script>`. Cell alpha = `0.12 + clamp((v-min)/(max-min)) *
1020.88`; text flips to white above alpha 0.55. Already handles any grid size.
103
104**Big-stat (FIG.09).** Pure markup: `<span class="fig-num"><span class="accent">
105+38</span>%</span>` — wrap the whole figure (or the part that should be coloured)
106in `.accent`. Keep the numeral and its symbol the same colour.
107
108## Build process
109
1101. **Read the study's numbers** (e.g. an `aggregate.py` printout or
111 `results.json`). Decide which 1–4 figures tell the story; don't over-chart.
1122. **Scaffold an HTML file** in the study's run folder, e.g.
113 `eval/<study>/runs/<run>/figures/<name>.html`. Link the design system by
114 relative path:
115 `<link rel="stylesheet" href="../../../../design-system/assets/colors_and_type.css">`
116 and `.../design-system/charts.css`. (Count the `../` from the figure file to
117 `eval/design-system/`.) Set `<body data-accent="…">`.
1183. **Lift the matching figure block(s)** from `chart-room.html`. Recompute every
119 coordinate from the real data using the math above. Replace category labels,
120 value labels, series names, captions, the masthead/section text. Delete the
121 figures you don't use.
1224. **Keep it honest.** Label sample sizes (`n=`), say what the score is, don't
123 round a tie into a win, and don't invent a series the data doesn't have. The
124 `angle-generator`/`meanest-editor` anti-slop ethos applies to charts too.
1255. **Validate with Playwright** (required):
126 ```bash
127 cd eval/design-system/scripts
128 node validate.mjs ../../<study>/runs/<run>/figures/<name>.html --out ../../<study>/runs/<run>/figures/png
129 ```
130 All checks must pass (no JS errors, accent painted, serif applied, every
131 figure has geometry, no empty SVGs). It writes a full-page PNG plus one crop
132 per figure — those are your embeddable assets.
1336. **Eyeball the screenshot** before publishing. Read the full-page PNG; confirm
134 bars/labels line up and nothing overflows.
135
136## Worked example
137
138For the Fable-5-vs-Opus-4.8 study (`eval/fable-vs-opus/`), the headline numbers
139(overall dim mean 4.60 vs 4.36; 67/33 head-to-head; 24 vs 7 robust wins; per-
140dimension deltas) map cleanly to: a **FIG.01 grouped-bars** of the 7 dimensions
141(Fable accent, Opus grey), a **FIG.07 donut** for the win rate, and a row of
142**FIG.09 big-stat callouts** (overall mean, robust wins, `publishable` rate). See
143`eval/fable-vs-opus/runs/2026-06-09-full/figures/` for the built, validated set.
144
145## Guardrails
146
147- Never edit `assets/colors_and_type.css` or `charts.css` to force a one-off look
148 — if a figure needs something the system lacks, that's a design-system change,
149 raise it, don't hack the study.
150- Never add a second colour, a gradient, a drop shadow on a chart, or emoji.
151- Never ship a figure that hasn't passed `validate.mjs`.
152- Placeholder data stays clearly labelled until real numbers replace it.