| name | figura |
| description | Create publication-quality figures, plots, and diagrams for academic papers, with a render-view-fix iteration loop for catching visual defects. Use this skill whenever the user is preparing visuals for a paper, thesis, preprint, technical report, or conference submission — including data plots (line, bar, scatter, heatmap, violin, etc. via matplotlib/seaborn), system or architecture diagrams, schematics, model illustrations, ablation comparisons, or any figure intended for inclusion in a written research deliverable. Trigger on phrases like "figure for my paper," "plot for the manuscript," "architecture diagram," "schematic," "make this look publication-ready," "submission-quality figure," "iterate on this figure," "fix this plot," or any time the user references LaTeX, NeurIPS, ICML, ICLR, IEEE, ACM, Nature, Science, or arXiv. Also use when the user wants to fix or upgrade a figure that "looks like default matplotlib," asks for a colorblind-safe / vector / print-ready version, or wants Claude to visually inspect a rendered figure and fix legibility / overlap / legend / clipping issues. Output files should be vector when possible (PDF/SVG) with PNG only as a high-DPI raster fallback (the PNG also serves as the print-size preview for visual iteration). |
Paper Figures
Make figures that look like a careful researcher made them, not the matplotlib defaults.
Quick Reference
| Task | Where to look |
|---|
| Data plot (line, bar, scatter, heatmap, violin, error bars, multi-panel) | references/plots.md |
| System / architecture diagram | references/diagrams.md |
| Schematic, model illustration | references/diagrams.md |
| TikZ / LaTeX diagram (flowchart, state machine, paper-native vector) | references/tikz.md |
| Iterating on a rendered figure (view → fix → repeat) | references/iteration.md |
| Pre-submission QA | references/checklist.md |
| Color choices | scripts/colors.py (palettes) + the "Color" section below |
Setup (every figure starts with this)
The skill ships three small Python modules in scripts/. Import them at the top of every figure script:
import sys
sys.path.insert(0, "<absolute-path-to-this-skill>/scripts")
import matplotlib
matplotlib.use("Agg")
import pubstyle, colors, export
pubstyle.apply()
colors.apply_cycle()
For scripts that live inside the skill repo (e.g. examples/), use a path relative to the file so the script is portable across machines:
from pathlib import Path
SKILL = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(SKILL / "scripts"))
The examples/ directory holds runnable end-to-end scripts (e.g. examples/torus.py for the 3D surface pattern). Use them as templates when building a new figure.
Build the figure normally with matplotlib/seaborn. At the end:
export.save(fig, "fig_results", formats=("pdf", "svg", "png"))
This writes fig_results.{pdf,svg,png} into ./figures/ (in the current working directory) with embedded fonts, tight bbox, and 300 DPI for the PNG. Override with export.save(..., outdir="some/path") — for the Anthropic analysis-tool sandbox, pass outdir="/mnt/user-data/outputs". The vector formats (PDF/SVG) are what goes into the paper; the PNG is for quick previewing and Slack/Docs.
If the user asked for a specific venue style (NeurIPS, IEEE, Nature), pass it: pubstyle.apply(venue="ieee"). Default is generic.
For figure size, prefer the helpers — they match standard column widths in inches:
fig, ax = plt.subplots(figsize=pubstyle.figsize("single"))
fig, ax = plt.subplots(figsize=pubstyle.figsize("double"))
What "Publication-Quality" Means Here
The bar isn't "looks fancy." The bar is a careful reader at print size will not be confused or annoyed. That cashes out as:
- Designed at print size. A single-column figure is ~3.3 inches wide on the printed page. If the figure is built at 12 inches wide and shrunk down later, the 8pt fonts become 2.2pt and nobody can read the axis labels. Always start from a realistic figure size and judge legibility there.
- Vector format (PDF or SVG) for anything with text, lines, or shapes. PNG only for genuinely raster content — photos, microscopy, dense heatmaps where vector files would balloon to tens of MB.
- Embedded fonts in PDFs (
pdf.fonttype = 42, handled by pubstyle.apply()). Without this, journals reject the PDF and arXiv silently substitutes fonts.
- Colorblind-safe palette. ~8% of men and ~0.5% of women have red-green colorblindness — that's a non-trivial fraction of any paper's audience. Use
colors.categorical() (Okabe–Ito) for series, colors.SEQUENTIAL for ordered data, colors.DIVERGING for signed data.
- Math typeset properly with mathtext (
$x^2$) — readable as text in the SVG, embedded vector in the PDF. Not a screenshot.
Aesthetic Direction
Academic figures have their own distinct failure modes — the default matplotlib look is the giveaway that the author didn't think about it. Aim for the visual register of a careful Nature, NeurIPS, or IEEE figure: clean, minimal, information-dense, no chartjunk.
What that looks like in practice (most of these are handled by pubstyle.apply(), but understand the why):
- Top and right spines off for line/scatter/bar plots. Eye stays on the data, not the frame.
- Subtle gridlines (
alpha=0.3, behind the data) only when they help reading values. Default off.
- Tick labels short. "1k, 10k, 100k" beats "1000, 10000, 100000". Use
ax.ticklabel_format or formatters.
- Legend inside the plot when space allows, with no frame. Outside-right is fine but eats space — only do it when overlap is unavoidable.
- One color per series, not gradients on bars. Categorical = distinct hues. Sequential = single-hue gradient.
- Lines 1.0–1.5pt, markers ~4pt at print size. Visible without dominating.
- Sans-serif fonts at 8–10pt for labels, 7–8pt for tick labels. Helvetica/Arial preferred (Nature requires it; others accept it).
Color
Three categories, three answers:
- Categorical (different conditions, methods, classes):
colors.categorical() — returns Okabe–Ito hex codes. Or just call colors.apply_cycle() once and use plot() normally.
- Sequential (ordered data, e.g. depth, density, time):
cmap="viridis" or "cividis" (cividis is the most colorblind-friendly). Available constants in colors.SEQUENTIAL.
- Diverging (signed data with meaningful zero, e.g. residuals, correlations, log-fold-change):
cmap="RdBu_r" or "coolwarm". Available in colors.DIVERGING.
Never use jet, rainbow, hsv, or nipy_spectral. They're perceptually nonlinear, mis-rank values in grayscale, and look unprofessional in a 2025 paper. (Listed in colors.AVOID for reference.)
If the paper might be printed in grayscale, encode each series in two channels: color and line style, or color and marker shape. Color alone is fragile.
Multi-figure papers: lock the palette once
When the same configs appear across multiple figures, define the config→color map once at the start of the project and pass it explicitly to every plotting call. Reader scans across figures looking for the same hue; drift breaks the mental thread.
- Same Python config = same color across all figures. If
svd_train is vermillion in Figure 2, it stays vermillion in Figure 5.
- Linestyle distinguishes within a panel when configs share a hue family (two warm hues, two blues). Three channels of distinction (color + linestyle + lw) survive both grayscale print and tritanopia.
- 'Ours = blue' convention beats strict per-config color when each figure has a different "ours" config. Reader instantly identifies the headline; different Python codepaths headlining different figures can both be
#0072B2 so long as they don't co-appear in a panel.
- Avoid
#F0E442 yellow for primary line series — washes out on white at print size. Reserve for filled regions.
For a multi-figure dispatch (parallel subagents producing one figure each), inject the palette table as a literal string in every subagent prompt — don't ask each to derive it from spec. Subagents drift on style minutiae if given freedom.
Anti-Patterns (the "default matplotlib" tells)
These scream "no thought given":
jet / rainbow colormaps. Already covered. The single most common giveaway.
- 3D bar charts, 3D pies, exploded pies. No paper has ever been improved by these.
- Default DejaVu Sans at 10pt with all four spines on. Literally the matplotlib default.
pubstyle.apply() fixes this.
- Saving as JPEG. Compression artifacts on every line and letter. PNG for raster, PDF/SVG for vector. (
export.save() warns on JPEG.)
- Drop shadows, gradient fills, beveled bars, 3D effects. Tufte-defined chartjunk. Information density goes down, ink-to-data ratio goes up.
- Rasterized text in a vector file. Defeats the entire point.
pubstyle.apply() sets pdf.fonttype = 42 to keep text as text.
mathtext.fontset = "cm". Computer Modern math historically emits Type 3 glyphs even with pdf.fonttype=42, slipping past the embedding gate. pubstyle.apply() defaults to "stixsans" (Type 42-safe and matches sans body text). Don't override to "cm" — verify with pdffonts fig.pdf | grep -i type3 if in doubt.
tight_layout() with inset axes / colorbars. tight_layout clips inset frames and miscalculates space around colorbars. Use plt.subplots(..., constrained_layout=True) instead; it co-operates with bbox_inches='tight' on save.
- Red
× markers for off-panel / divergent data. Reads as "error / excluded data point" in scientific figures, not "value lives above the panel." Use a broken-axis indicator (// marks on the y-axis) plus an upward arrow + text annotation.
- Tiny fonts only readable at design size. Designing at 12 inches and shipping at 3.3 inches makes 8pt → 2.2pt. Design at print size from the start.
- Legend covering data. Move it, shrink it, or pull it out into its own panel.
- Color-only encoding for grayscale-printable content. Add a second channel (line style, marker shape, hatching).
plt.tight_layout() without bbox_inches='tight' on save. Things get clipped at the edges. export.save() handles bbox.
- Identical-looking lines when you have 2 conditions ("blue solid" vs "orange solid"). Make them maximally distinct: different line styles too.
- 3D surface shaded by a parametric coordinate.
LightSource.shade(V, cmap) on a torus or similar lights from V's gradient — produces flat angular bands that look 2D. Shade by depth (Z) instead, so the lit region matches what a viewer expects from a 3D object. See references/plots.md § 3D surface.
Diagrams (Architecture / Schematic / Model Illustration)
Four reasonable paths, depending on the diagram's character. Read references/diagrams.md for code patterns and tradeoffs.
- matplotlib custom drawing — best when the diagram should match the visual style of your data figures (same fonts, same palette, same export pipeline). Layout is manual, but you get total control and one consistent look.
- TikZ / LaTeX — best when the paper is already LaTeX and the diagram is a flowchart, state machine, or boxes-and-arrows: typography matches the paper body exactly, and the source is plain
.tex. Build with scripts/tikz_build.sh (compile + 300 DPI PNG preview for the iteration loop). Template at examples/diagram_flow.tex. Full reference: references/tikz.md.
- graphviz (via the
graphviz Python package) — best when there are many nodes and auto-layout matters more than exact positioning. Less control over aesthetics but very fast for complex graphs.
- Hand-authored SVG — best for one-off polished illustrations where layout is artistic and a layout engine would fight you. Slowest to build, most control.
For ML papers specifically, model architecture diagrams (transformer blocks, U-Nets, etc.) are usually matplotlib + custom drawing or hand-SVG. Graphviz is better for dataflow / pipeline / system diagrams with many components.
QA Workflow (Render → View → Fix → Repeat)
Every figure goes through this loop before declaring done. Don't skip — first renders almost always have at least one user-visible defect (small fonts, legend overlap, tick collision) that's invisible while writing the code.
- Render with
export.save(). PDF/SVG/PNG come out together.
- View the PNG with the
view tool. The PNG is rendered at 300 DPI from a figure whose dimensions are the actual print size, so what you see on screen is what a reader sees on paper.
- Inspect for defects using the inspection prompt and defect catalog in
references/iteration.md. Be specific about which element is wrong and why.
- Apply fixes from the catalog (most common issues — small fonts, legend covering data, ticks colliding, panels too close — have standard fixes that drop straight in).
- Re-render and re-view.
- Stop after at most 2 fix cycles, or sooner if only sub-pixel / cosmetic issues remain.
The stopping rule matters. Figure work has a long tail of nitpicks that don't matter to readers — the bar is "a careful reader is not confused or annoyed", not "every pixel is perfect."
For full inspection prompt, defect catalog, and worked examples → references/iteration.md.
For the final pre-submission audit (font embedding, format, etc., separate from visual defects) → references/checklist.md.
When to Reach for a Reference File
- Iterating on a rendered figure (the Render → View → Fix loop) →
references/iteration.md
- Building a specific plot type →
references/plots.md (ready-to-adapt patterns)
- Building a system / architecture diagram or schematic →
references/diagrams.md
- Final pre-submission check →
references/checklist.md
Each reference file's code snippets already use pubstyle, colors, and export so they drop in directly.
Slash Commands: which one to reach for
The plugin ships six user-facing commands. Pick by which class of work dominates:
| Situation | Command |
|---|
| Rendered figure looks like default matplotlib (DejaVu Sans, four spines, tab10) | /figura:beautify |
| Rendered figure has overlap/collision defects (legend covers data, ticks collide) | /figura:fix-overlap |
| Rendered figure has mixed defects across categories | /figura:iterate |
| Need a defect report without modifying anything | /figura:analyze-image |
| jet/rainbow palette in use, or color-only encoding | /figura:beautify |
| Dynamic-range squeeze (outlier dominates axis, comparison band compressed) | /figura:iterate |
| Need to switch a script to a specific venue's font/spacing (NeurIPS, ICML, IEEE, CVPR, …) | /figura:paper-style |
Need PNGs of every PDF in a paper's figures/ directory (for Slack, README, slides) | /figura:export-png-bundle |
/figura:analyze-image and /figura:export-png-bundle are read-only / non-destructive. /figura:beautify, /figura:fix-overlap, /figura:iterate, /figura:paper-style modify the source script. After analyze-image returns its defect table, route to the modifying command whose category dominates.