| name | paperchart | ||||||
|---|---|---|---|---|---|---|---|
| description | Generate clean, calm chart PNGs in the spirit of Anthropic and OpenAI blog posts. Trigger when the user asks to produce a chart, model comparison table, diagram or infographic for a technical blog post, release notes, a research write-up or a slide deck and wants a single restrained palette, minimal chrome, heavy inline labelling, and PNG output rendered from React at native resolution. The primary surface is a shell CLI named `paperchart` that takes a JSON file and writes a PNG. Thirty-two primitives covering charts (line, area, scatter, heatmap, calendar heatmap, histogram, cdf, box-plot, ranking, dumbbell, slope, waterfall, small-multiples, timeline, funnel, sankey, treemap, radar, table, stacked-bar, grouped-bar, critical-path, pack-layout, latency, bytes, recall) and Mermaid-style structural diagrams (flowchart, architecture, sequence, state-diagram, er-diagram, delivery). Six themes, layout + style overrides per chart. | ||||||
| license | MIT | ||||||
| metadata |
|
A six-primitive chart toolkit for rendering quiet, print-weight charts to PNG. The surface is a shell CLI: feed it a JSON file, get a 2×-DPR figure.
Trigger on any of: "make a chart for this blog post", "render a before / after benchmark as an image", "I need a figure for my paper / release notes / announcement", "design a minimal chart in the style of Anthropic or OpenAI research posts", "draw a binary layout / critical path / latency comparison / recall parity chart".
Do not use this skill when the user asks for an interactive dashboard, a real-time graph, or a chart with brand colours other than the paperchart palette. This skill is opinionated on purpose.
paperchart <type> -i data.json -o out.png [--width 1600] [--dpr 2]
paperchart <type> --defaults -o out.png # render the built-in sampleTypes: table, latency, bytes, stacked-bar, grouped-bar, funnel, ranking, dumbbell, slope, line, area, small-multiples, timeline, scatter, heatmap, calendar-heatmap, histogram, box-plot, cdf, radar, treemap, sankey, waterfall, critical-path, recall, pack-layout, delivery, flowchart, architecture, sequence, state-diagram, er-diagram.
Themes: paper (warm off-white, rust), ink (near-white, black), slate (cool grey, deep blue), forest (off-white, forest green), mono (pure monochrome), dusk (dark charcoal, warm orange). Pass --theme <name> on the CLI or put "theme": "<name>" in the JSON envelope.
Invoke it with npx github:shuakami/paperchart <type> -i data.json -o out.png from any directory. On first run npm clones the repo, runs prepare to build the Vite bundle, and playwright-chromium's postinstall downloads headless Chromium once (~200 MB). Subsequent runs are near-instant.
If the skill has already been installed via npx skills add shuakami/paperchart, the full repo is available under .claude/skills/paperchart/ (or .agents/skills/paperchart/ on non-Claude agents). You can also just cd there and node bin/paperchart.mjs <type> -i data.json -o out.png.
When the user asks for a chart:
- Pick the primitive whose shape fits the data. One primitive per figure. If none fit, decline and explain why — do not invent a new chart kind inline.
- Write the JSON file with the shape documented below for that primitive. Keep captions short — 6 to 12 words. Use the accent colour on exactly one row / segment / panel. Leave the rest neutral.
- Run the CLI. Show the user the output path. If they want to iterate, edit the JSON and re-run — the file is the single source of truth.
- Never embed chart titles inside the chart. Titles belong in the blog prose next to the image.
Every chart accepts the same shape:
{
"theme": "ink",
"layout": { "width": 1800, "fontScale": 1.05, "xAxisCaption": "..." },
"style": { "accent": "#1f6feb" },
"data": <chart-specific payload>
}theme, layout, and style are optional. If you pass just data (or wrap your data as a bare array), the chart uses good defaults.
Short form. Full schemas are in the project README and in the per-type skills/<type>/SKILL.md files.
table—{ columns: [{ key, label, align?, unit?, group? }], rows: [{ label, caption?, highlight?, values: { [key]: number | string } }] }. Model / config / product comparison. Exactly onehighlight: truerow.latency—Row[]. Each row:{ group, caption, color, bars: [{ level, ms }] }.bytes—Row[]. Each row:{ group, caption, accent, segments: [{ kb, fill, tag }], firstLoadKB, deferredKB }.stacked-bar—Row[]where each row is{ label, caption?, segments: [{ key, value, color? }] }. Composition per row.grouped-bar—{ series: [{ key, label, color? }], groups: [{ label, caption?, values: { [key]: number } }] }.ranking—Row[]. Each row:{ label, caption?, value, accent? }. Sorted leaderboard, one accented row.dumbbell—Row[]. Each row:{ label, before, after }.slope—{ startLabel, endLabel, unit?, series: [{ label, start, end, accent? }] }.line—{ xLabels, series: [{ label, values, accent? }] }.area—{ xLabels, series: [{ label, values }] }(stacked).small-multiples—{ xLabels?, unit?, panels: [{ label, caption?, values, accent? }] }.scatter—Point[]with optional regression line.heatmap—{ rowLabels, colLabels, values: number[][] }.histogram—{ bins: [{ x0, x1, count }] }.cdf—{ points: [{ value, cumulative }] }.waterfall—{ steps: [{ label, delta, subtotal? }] }.timeline—Row[]where each row is{ label, caption?, spans: [{ start, end, label, accent? }], milestones?: [{ at, label }] }.funnel—Stage[]. Each stage:{ label, caption?, count, accent? }. Drop-off auto-computed between stages.sankey—{ sources: Node[], targets: Node[], flows: [{ from, to, value }] }. Two-column flow diagram.treemap—Item[]where each item is{ label, value, caption?, accent?, children?: [{ label, value }] }. Two-level squarified.radar—{ axes: [{ key, label, caption?, max? }], series: [{ label, caption?, accent?, values: { [axisKey]: number } }] }.box-plot—Row[]. Each row:{ label, caption?, min, q1, median, q3, max, outliers?, accent? }.calendar-heatmap—{ start?: "YYYY-MM-DD", days: [{ date, value }] }or just[{ date, value }].critical-path—Row[]. Each row:{ label, detail, startMs, endMs, kb, critical: boolean }.recall—Query[]. Each query:{ query, hits, sets: "equal" }.pack-layout—Segment[]. Each segment:{ label, detail, bytes, accent: boolean }.delivery—{ header, subheader, panels: PanelSpec[] }with up to 3 panels.flowchart—{ direction?: "TD" | "LR", nodes: [{ id, label, caption?, shape?: "rect" | "round" | "stadium" | "diamond" | "cylinder", accent? }], edges: [{ from, to, label?, dashed?, accent? }] }. Layered DAG, back-edges detected and drawn as loop-backs.architecture—{ groups: [{ id, label, caption?, services: [{ id, label, caption?, accent? }] }], connections: [{ from, to, label?, dashed?, accent?, bidirectional? }] }. Groups stack left-to-right; services stack top-to-bottom inside each group.sequence—{ actors: [{ id, label, caption? }], steps: [{ kind: "msg", from, to, label?, reply?, async?, accent? } | { kind: "note", over: id | [id, id], text }] }. Time flows downward.state-diagram—{ direction?: "TD" | "LR", states: [{ id, label, caption?, start?, end?, accent? }], transitions: [{ from, to, label?, dashed?, accent? }] }. Start / end pseudostates render as • / ⦾.er-diagram—{ entities: [{ id, label, caption?, accent?, fields: [{ name, type?, pk?, fk?, note? }] }], relationships: [{ from, to, fromCard, toCard, label?, accent? }] }where cardinality codes are"1","0..1","M","1..M","0..M".
| role | hex |
|---|---|
| page / card background | #F6F1EA |
| accent (one element per chart) | #C75F3C |
| secondary neutral | #D6B99B |
| ink | #2B2A27 |
| rule / hairline | #E6DCCE |
No gradients, no shadows, no rounded corners, no emojis.
Inter, weights 400 / 500 / 600, sizes 13 to 26 px. Tabular numerals for any numeric value. Group title: 22 px / 600. Caption: 14 px / 400 at 62 % opacity. Axis caption: 15 px / 400 at 62 % opacity. Inline value: 16–17 px / 500.
- No chart titles. The blog prose explains what the chart shows.
- Inline labels over legends. Put axis names on the axis, group names next to groups, values next to bars.
- Single accent. Only one colour stands out.
- Generous whitespace. Give every row at least 200 px vertical room if it has a caption beneath the group name.
- Native-size PNG. Never ask the user to scale the chart down in the final layout — fonts get compressed horizontally and everything looks cheap.
If the user needs something the CLI does not cover (different sizes, extra annotations, a different accent placement), point them at the repository. Every chart lives in its own file under src/charts/ with the default data array at the top; edit, save, run npm run snap.
https://github.com/shuakami/paperchart