Skip to content

Latest commit

 

History

History
132 lines (101 loc) · 9.58 KB

File metadata and controls

132 lines (101 loc) · 9.58 KB
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
author homepage version
shuakami
0.1

paperchart

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.

The CLI

paperchart <type> -i data.json -o out.png [--width 1600] [--dpr 2]
paperchart <type> --defaults -o out.png      # render the built-in sample

Types: 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.

Authoring recipe

When the user asks for a chart:

  1. 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.
  2. 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.
  3. 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.
  4. Never embed chart titles inside the chart. Titles belong in the blog prose next to the image.

The input envelope

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.

Primitive schemas

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 one highlight: true row.
  • latencyRow[]. Each row: { group, caption, color, bars: [{ level, ms }] }.
  • bytesRow[]. Each row: { group, caption, accent, segments: [{ kb, fill, tag }], firstLoadKB, deferredKB }.
  • stacked-barRow[] 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 } }] }.
  • rankingRow[]. Each row: { label, caption?, value, accent? }. Sorted leaderboard, one accented row.
  • dumbbellRow[]. 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? }] }.
  • scatterPoint[] with optional regression line.
  • heatmap{ rowLabels, colLabels, values: number[][] }.
  • histogram{ bins: [{ x0, x1, count }] }.
  • cdf{ points: [{ value, cumulative }] }.
  • waterfall{ steps: [{ label, delta, subtotal? }] }.
  • timelineRow[] where each row is { label, caption?, spans: [{ start, end, label, accent? }], milestones?: [{ at, label }] }.
  • funnelStage[]. 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.
  • treemapItem[] 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-plotRow[]. 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-pathRow[]. Each row: { label, detail, startMs, endMs, kb, critical: boolean }.
  • recallQuery[]. Each query: { query, hits, sets: "equal" }.
  • pack-layoutSegment[]. 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".

The palette — five values only

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.

Typography

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.

Composition rules

  1. No chart titles. The blog prose explains what the chart shows.
  2. Inline labels over legends. Put axis names on the axis, group names next to groups, values next to bars.
  3. Single accent. Only one colour stands out.
  4. Generous whitespace. Give every row at least 200 px vertical room if it has a caption beneath the group name.
  5. Native-size PNG. Never ask the user to scale the chart down in the final layout — fonts get compressed horizontally and everything looks cheap.

Deeper customisation

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.

Repository

https://github.com/shuakami/paperchart