From figura
Creates publication-quality plots (line, bar, scatter, heatmap via matplotlib/seaborn), diagrams, and figures for academic papers with render-view-fix iteration.
npx claudepluginhub chrischoy/figura --plugin figuraThis skill uses the workspace's default tool permissions.
Make figures that look like a careful researcher made them, not the matplotlib defaults.
Monitors deployed URLs for regressions after deploys, merges, or upgrades by checking HTTP status, console errors, network failures, performance (LCP/CLS/INP), content, and API health.
Share bugs, ideas, or general feedback.
Make figures that look like a careful researcher made them, not the matplotlib defaults.
| 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 |
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") # headless backend; skip if you want a GUI
import pubstyle, colors, export
pubstyle.apply() # vector-safe matplotlib defaults
colors.apply_cycle() # colorblind-safe categorical 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 # adjust depth as needed
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")) # 3.3 × 2.2 in, single-column
fig, ax = plt.subplots(figsize=pubstyle.figsize("double")) # 6.8 × 2.6 in, double-column
# Also: "single_tall", "double_tall", "square"
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:
pdf.fonttype = 42, handled by pubstyle.apply()). Without this, journals reject the PDF and arXiv silently substitutes fonts.colors.categorical() (Okabe–Ito) for series, colors.SEQUENTIAL for ordered data, colors.DIVERGING for signed data.$x^2$) — readable as text in the SVG, embedded vector in the PDF. Not a screenshot.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):
alpha=0.3, behind the data) only when they help reading values. Default off.ax.ticklabel_format or formatters.Three categories, three answers:
colors.categorical() — returns Okabe–Ito hex codes. Or just call colors.apply_cycle() once and use plot() normally.cmap="viridis" or "cividis" (cividis is the most colorblind-friendly). Available constants in colors.SEQUENTIAL.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.
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.
svd_train is vermillion in Figure 2, it stays vermillion in Figure 5.#0072B2 so long as they don't co-appear in a panel.#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.
These scream "no thought given":
jet / rainbow colormaps. Already covered. The single most common giveaway.pubstyle.apply() fixes this.export.save() warns on JPEG.)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.× 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.plt.tight_layout() without bbox_inches='tight' on save. Things get clipped at the edges. export.save() handles bbox.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.Four reasonable paths, depending on the diagram's character. Read references/diagrams.md for code patterns and tradeoffs.
.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 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.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.
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.
export.save(). PDF/SVG/PNG come out together.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.references/iteration.md. Be specific about which element is wrong and why.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.
references/iteration.mdreferences/plots.md (ready-to-adapt patterns)references/diagrams.mdreferences/checklist.mdEach reference file's code snippets already use pubstyle, colors, and export so they drop in directly.
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.