agentleFS
lanceguilancegui/causal-powers/skills/figure-craft/SKILL.md

figure-craft

Skill2 starsChanged 4 months agoMITView on GitHub

Create or revise figures for papers, decks, and presentations in R, Python, or Julia after the underlying data is validated. Covers chart choice, event-study markers, Paul-Tol colors, black-and-white legibility, concise labels, and visual checks. Match project export dimensions and final display size before choosing font proportions; use 16.5pt on an 8.33×5 in canvas as a fallback. Keep analysis metadata in manuscript notes. Use for requests to plot, chart, visualize, or fix figure formatting.

The file

Show its rules and text

We split this skill into the rules it gives an agent

14 rules. About 0 are common, 13 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈13

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Create the selected figure directory if needed.

    line 102
  2. Distinctive:

    After every save, open the file at its intended manuscript or slide size, compare it with neighboring figures, and verify:

    line 108
  3. Distinctive:

    The legend fits: If legend entries are long or numerous, they collide or wrap awkwardly — switch to two rows or move to the bottom (below).

    line 112
  4. Distinctive:

    When x-axis tick labels collide, in order of preference:

    line 119
  5. Distinctive:

    Drop it entirely: if the title already names the grouping and there's only one series — a legend with one entry is wasted ink.

    line 129
  6. Distinctive:

    Do not default to packing several graphs into one canvas.

    line 154
  7. Distinctive:

    Use facets or stacked panels only when the user explicitly needs a tightly linked comparison and each panel remains independently readable.

    line 166
  8. Distinctive:

    Dodged points: when multiple groups share an x-value, so points and bars don't stack on each other.

    line 178
  9. Distinctive:

    ECDF: when the reader needs to read percentiles — less intuitive but more precise for "what share is below X."

    line 205
  10. Distinctive:

    A figure that collapses in black and white: If two series are indistinguishable in grayscale, color was the only channel — add shape, …

    line 252
  11. Distinctive:

    Fonts too small for presentation: If the audience is more than arm's length from the screen, default text is invisible.

    line 262
  12. Distinctive:

    A legend with one entry: If there's one series, the title already names it.

    line 263
  13. Distinctive:

    Ensure black-and-white legibility: Every group distinction needs a redundant non-color channel — shape for points, linetype for lines, …

    line 291
  14. Distinctive:

    Match the project’s export settings: Use its figure directory, canvas, units, and explicit DPI.

    line 293

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Figure Craft

Overview

The discipline skills keep you from being wrong; this skill keeps the figure readable at a glance from the back of the room. A figure is a communication artifact, not a debug plot — it will be projected on a screen, embedded in a paper, or dropped into a slide deck, and the audience will give it three seconds. It may also be printed in black and white. Every choice below serves those constraints.

Core principle: the minimum ink that conveys the idea, at a size the back row can read, with nothing clipped or overlapping — legible in color and in grayscale. Restraint, not decoration.

This guide applies to EVERY figure, in every language. The house style is defined by its principles (clean theme, colorblind-safe palette, large fonts, no y-axis label, concise labels, right geom, B&W-safe, visual self-check); the implementation differs by language. The R implementation — theme_hc() + scale_color_ptol() from ggthemes — is the reference because it's the most direct, but the same principles map to Python and Julia. Don't skip the style because you're not in R; translate it.

This skill fires after the data is right — after question-framing (what each mark encodes, the unit, the joins), after descriptive-evidence or causal-identification (the number is real), and after result-verification (it reproduces). It governs only the rendering — the bridge from a verified result to a presentation-ready exhibit. If the data hasn't been validated yet, route there first (result-verification itself runs only if the user asks for it); a beautiful figure of a wrong number is still a wrong number.

Where this sits

This is the last step of the analysis chain, not the first. The figure renders a result that has already been earned:

  • question-framing → what each mark encodes, the unit, the joins — frame before you plot.
  • descriptive-evidence or causal-identification → the number is real and the composition / identification is checked.
  • result-verification → the number reproduces from a clean seed.
  • figure-craft → render the verified result as a presentation-ready exhibit.
  • project-organization → save the figure to results/figures/ with a conventional name (fig_<what_it_shows>.pdf); the figure is a deliverable, not scratch.

analysis-craft runs alongside — keep the plotting code minimal and legible, the same as any analysis code. analysis-checkpoints applies if a figure choice would change what the reader takes away (e.g., switching from levels to changes mid-stream) — that's a framing change, not a style tweak.

The house style — non-negotiable defaults

These are the style guide's fixed choices. Apply them unless the user explicitly overrides; if you deviate, say why. The table below gives the principle, the R implementation (the reference), and the Python/Julia equivalents.

Theme and color

PrincipleR (reference)PythonJulia
Clean, high-contrast, minimal-chrome base themetheme_hc() from ggthemesseaborn.set_style("white") + despine; or a custom matplotlib style with minimal gridlines, white background, dark axis linesMakie.jl with Theme(backgroundcolor = :white) or Plots.jl gr() with minimal frame
Colorblind-safe qualitative palette (Paul-Tol)scale_color_ptol() / scale_fill_ptol() from ggthemesManually set the Paul-Tol hex codes (see below) in plt.rcParams["axes.prop_cycle"] or per-artist color=Pass the Paul-Tol hex array to palette/color arguments
Black axis line grounding the plotaxis.line = element_line(color = "black")ax.spines[...].set_color("black") + ax.tick_params(colors="black")Makie/Plots axis line color
No legend titlelegend.title = element_blank()ax.legend(title=None)legendtitle = nothing

The Paul-Tol qualitative palette (use these hex codes when the library doesn't provide them natively):

#332288  #117733  #44AA99  #88CCEE  #DDCC77  #CC6677  #AA4499  #882255

(8 colors — if you have more categories than this, the palette is being stretched; reconsider the encoding.)

Black-and-white legibility — color is never the only channel

A figure that reads in color but falls apart in grayscale is not done. Papers get printed in B&W; slides get photocopied; reviewers read on e-ink. Every distinction the color encodes must have a redundant non-color channel — shape, linetype, size, or label:

ChannelRPythonJulia
Point shape by group (for scatter / coefficient plots)aes(shape = group) + scale_shape_manual()plt.scatter(..., marker=...) per groupmarkershape per series
Linetype by group (for lines)aes(linetype = group) + scale_linetype_manual()plt.plot(..., linestyle=...) per grouplinestyle per series
Fill pattern for bars (hatching)ggpattern::geom_col_pattern() or manual fill densityhatch parameter in plt.bar()Makie Mesh patterns or manual
Direct labels instead of a legendgeom_text / ggrepel::geom_text_repelax.annotate(...) per grouptext! / Makie.text

The test: before declaring a figure done, render it in grayscale (or mentally squint at the color version) and ask: "can I still tell the groups apart?" If two series collapse to the same gray, add shape or linetype — color alone was carrying a distinction it can't carry alone. With the Paul-Tol palette, the colors are chosen for colorblind-safety, but even they are not guaranteed to survive a B&W conversion without redundant encoding.

Fonts — sized for the back row

Choose font size together with the export canvas and final display size. Before plotting, inspect a comparable figure’s producer (ggsave() or equivalent): width, height, units, DPI, and manuscript insertion width. Match that project convention first. The 16.5pt default below pairs with an approximately 8.33 × 5 in canvas; it can look disproportionately large on a 5 × 3 in canvas. When correcting oversized text, match the existing canvas before changing fonts; do not automatically enlarge the font along with the canvas. The title carries the y-axis meaning and uses the same size as the axis text.

ElementSize (pt)RPythonJulia
Axis title (x)16.5axis.title = element_text(size = 16.5)ax.set_xlabel(..., fontsize=16.5)xlabel(..., fontsize=16.5)
Axis tick labels16.5axis.text = element_text(size = 16.5)ax.tick_params(labelsize=16.5)tick label font size
Legend text16.5legend.text = element_text(size = 16.5)legend.get_texts() → set_fontsize(16.5)legend font size
Plot title / subtitle16.5plot.title = element_text(size = 16.5)ax.set_title(..., fontsize=16.5)title font size
Y-axis title—axis.title.y = element_blank() — never setdo not call set_ylabelno ylabel

The y-axis label rule

No y-axis label. The y-axis meaning goes in the title/subtitle, not beside the axis. This frees vertical space, avoids rotated text, and lets the title carry the full description in a readable horizontal orientation.

The title IS the y-axis label — and for a paper figure that is all the title ever is: the headline belongs in the manuscript's \caption, never on the figure. Only a deck/presentation figure (no caption to carry it) may add a headline, as title with the y-axis meaning moving to the subtitle (R: labs(title = "<headline>", subtitle = "<y-axis>"); Python: fig.suptitle + ax.set_title; Julia: title! + subtitle!).

Axis labels — concise to the point of abbreviation

X and Y labels must be the shortest string that is still unambiguous — incomplete sentences are fine, abbreviations are encouraged when standard. "Year", not "Calendar Year of Observation." "Coef.", not "Estimated Coefficient." If the y-axis meaning is already in the title/subtitle, the x-axis label is often a single word. Every character you save is space the audience doesn't have to parse.

Output — size and file location

Existing project dimensions take precedence. Match comparable manuscript figures rather than imposing a 5 × 3 in canvas. If no convention exists, start with 2500 × 1500 px at 300 dpi, equivalent to 8.33 × 5 in, for the 16.5pt font defaults. An explicit user size overrides this fallback.

In ggsave(), physical inches = pixels / DPI when units = "px"; save figures as PDF only (no PNG copy) and still pass DPI explicitly. With dimensions in inches, increasing DPI changes raster resolution, not the relative size of text. At a fixed manuscript insertion width, enlarging the export canvas while keeping the font fixed makes the displayed text smaller: 16.5pt on an 8.33 in canvas becomes about 9.9pt when inserted at 5 in. Check that final size for readability; a presentation may need different sizing from a paper.

Save to the project's existing figure directory per project-organization conventions; use results/figures/ for canonical deliverables and results/diagnostics/ for scratch plots when no convention exists. Name by what the figure shows, traceable to its producer: fig_eventstudy_mortality.pdf.

LanguageSave call
Rggsave("results/figures/fig_<name>.pdf", p, width = 2500, height = 1500, units = "px", dpi = 300)
Pythonfig.savefig("results/figures/fig_<name>.pdf", dpi=300) with fig = plt.figure(figsize=(25/3, 5)); arrange margins within the canvas
Julia (Makie)Match the project’s Figure(size=...) and export scaling; inspect the saved physical PDF dimensions
Julia (Plots.jl)Match the project’s plot(..., size=...), DPI, and backend; inspect the saved physical PDF dimensions

Create the selected figure directory if needed. Never save a deliverable figure to the project root or sandbox/ — it's a deliverable, not scratch.

The visual self-check — MANDATORY

You must render the figure to an image file and visually examine it before declaring it done. "The code ran" is not "the figure is readable." This is the figure equivalent of result-verification — you are checking the output, not the code. This applies in every language.

After every save, open the file at its intended manuscript or slide size, compare it with neighboring figures, and verify:

  1. No text is clipped or extends outside the plot panel. Check all four edges — axis labels, the title/subtitle, the legend, tick labels. Large fonts make this the #1 failure: a label that fit at default size overflows at 16.5pt. In R, watch plot.margin; in Python, plt.tight_layout() / bbox_inches="tight"; in Julia, check fig size vs content.
  2. No text overlaps. The most common overlap is on a busy x-axis — many tick labels colliding. Fix it (below). Also check legend entries colliding with the plot, and the subtitle crashing into the axis.
  3. The legend fits. If legend entries are long or numerous, they collide or wrap awkwardly — switch to two rows or move to the bottom (below).
  4. Point markers and error bars are visible. At presentation scale, large markers and thick error bars are the floor; if the figure is wide, increase both.
  5. The color palette is doing its job — and the figure survives a grayscale render. Categories should be distinguishable in color and in B&W (see Black-and-white legibility above). If you have > 8 categories, the palette is being stretched — reconsider the encoding.
  6. Annotations (if any) are placed cleanly — not overlapping data markers, not clipped at the edge, and legible at the font size the rest of the figure uses.

Fixing a busy x-axis

When x-axis tick labels collide, in order of preference:

  • Reduce breaks. Show every other year, or every 5th unit. The audience doesn't need every tick; they need enough to locate themselves.
  • Rotate labels as a last resort — but rotated labels are harder to read; prefer fewer breaks.
  • If the axis is categorical with long level names: abbreviate the levels in the data before plotting.

Fixing legend overflow

  • Two rows — the default single-row legend overflows on wide category names.
  • Bottom placement — frees horizontal space and lets the legend wrap naturally.
  • Drop it entirely if the title already names the grouping and there's only one series — a legend with one entry is wasted ink. Or use direct labels on the series themselves (B&W-safe into the bargain).

Annotations — at most 1–2, and only to enhance understanding

Inside-canvas annotations are allowed but tightly constrained: at most 1–2, and only when they help the reader understand something the axes and title alone don't convey. The purpose of an annotation is solely to enhance understanding, not to decorate or to carry metadata.

An annotation earns its place when removing it would make the figure harder to interpret:

  • Absolute values on top of percentage bars. If the y-axis shows percentages but the audience needs the absolute counts to judge whether a small percentage is a large number of people, label each bar with the count. The percentage is in the axis; the absolute is the annotation. This is information the axes can't carry simultaneously.
  • "Pretreatment treatment group" on a DiD plot. A label showing the pretreatment level of the treatment group gives the reader the relative scale — is the effect large or small compared to the baseline? The coefficient plot alone shows the change; the annotation anchors the level. Place it as a text label near the relevant series.

What is NOT an annotation — these belong in LaTeX figure notes (\caption or a note), never on the canvas:

  • Sample size (N = 1,204), data source (Source: ACS 2019), sample restrictions, p-value stars/conventions
  • The regression equation, control variable lists, specification notes
  • Anything that is metadata about the analysis rather than part of the visual message

"In LaTeX notes" means in the manuscript, never rendered into the figure file: no labs(caption =) / ggplot captions, no matplotlib figtext under the axes, no note text below the plot. The saved figure contains zero note text — write the note as manuscript prose only if the user asks for it.

The rule: at most 1–2 annotations. Each must pass the test: "does removing this make the figure harder to understand?" If yes, it stays; if it's merely informative, it goes in the notes.

Chart-type decisions

Panel and specification layout — compare like with like

Do not default to packing several graphs into one canvas. Keep a single output figure focused on one outcome and one unit of measurement.

  • Same outcome, alternative specifications: show the estimates together so the comparison is direct. For DiD or event-study estimates at the same x-value, dodge the points and confidence intervals by specification (with distinct shape/linetype as well as color); do not layer them on top of one another.
  • Different outcomes: make separate output figures, even when they use the same estimator or share a unit. A second outcome is a separate visual claim, not another specification of the first outcome.

Use facets or stacked panels only when the user explicitly needs a tightly linked comparison and each panel remains independently readable. They do not override the one-outcome default.

DiD / event-study coefficient plots — dots and error bars only

The canonical exhibit for causal-identification results: estimated coefficients with confidence intervals over time or event-time.

Rules:

  • Dots + error bars, not lines. A connected line implies continuity between estimates; event-study coefficients are discrete period-by-period estimates. Dots convey "point estimate with uncertainty"; a line conveys "a continuous trajectory." Don't blur them. This applies in every language — plt.errorbar with fmt="o" and no line in Python, scatter! + errorbars! in Julia.
  • The dashed vertical line follows the agreed timing convention. For an existing figure series, match its reference-year or treatment-boundary placement, or the user’s explicit request; do not silently substitute a half-year boundary. For a new event-time plot, the boundary between period -1 and 0 is conventionally -0.5. Describe what the marker denotes in the manuscript notes; moving it does not change the model’s omitted year or treatment timing.
  • A horizontal line at zero — anchors the reader to the null.
  • Dodged points when multiple groups share an x-value, so points and bars don't stack on each other.
  • Alternative specifications of the same outcome belong in this same plot, dodged by specification; different outcomes belong in separate figure files.
  • B&W-safe shapes — if two groups share an x-value, give them different point shapes (aes(shape = group)), not just different colors.
  • Consider a "pretreatment treatment group" annotation to show the relative scale of the effect (see Annotations above).

Trend lines — when to connect, when to stack, when to bar

Think about what the chart is saying before you pick the geom. The choice between a line, a stacked bar, or dots is not aesthetic — it carries meaning:

The messageThe right geomWhy
A single series tracks a level over timelineContinuity is the point — each period flows into the next
Multiple series track levels over time, to compare their shapesline (dodged, one color/linetype per series)You want the reader to trace each series
A composition shifts over time (shares of a whole)stacked barThe stack shows both the total and the within-share change simultaneously
A composition at a few discrete points (not over time)dodged barStacking across non-adjacent categories misleads
Discrete estimates with uncertainty (coefficients, event-study)point + errorbarDots = "here's an estimate ± noise"; a line would falsely imply interpolation
A distributionhistogram / density / barSee Distributions below

The dot-vs-line distinction is the most important choice on this page. A connected line says "the value moved smoothly from A to B." Dots say "here are independent estimates." If the x-values are independent estimates (event-study coefficients, cross-sectional regressions by group), never connect them — the connection invents a trajectory the data doesn't claim. If the x-values are a time series of observed values (not estimates), a line is natural.

Stacked bars are for composition — when the story is "the total stayed flat but the mix shifted." If the story is "series A grew faster than series B," stacked bars hide the individual trends; use dodged lines or dodged bars.

Distributions

  • Histogram for the shape of one variable — choose bin width deliberately (too fine = noise, too coarse = hides structure); state it.
  • Density for comparing two or more distributions on the same axes — overlay with alpha so they don't occlude. Use different linetypes as well as colors for B&W.
  • ECDF when the reader needs to read percentiles — less intuitive but more precise for "what share is below X."
  • Log x-axis for heavy-tailed distributions (income, firm size, claim amounts) — otherwise the bottom 90% is crushed against zero and the "shape" is just "the top is big."

Scatter plots

  • Show the points — don't let a fitted line claim a relationship the scatter doesn't support. If N is large, use alpha (0.1–0.3) so density is visible.
  • A fitted line is a claim — a loess/lowess through noisy data can invent a trend the scatter doesn't show. Only add it if you'd defend the functional form.
  • Color by a grouping variable to reveal composition — a single-color scatter that looks like noise can resolve into clean clusters when you color by group. Use different marker shapes too (B&W-safe).

The complete template

A copy-pasteable skeleton encoding every default above. The R version is the reference; see the language tables above for Python/Julia equivalents.

library(ggplot2)
library(ggthemes)

ggplot(data, aes(x = ..., y = ..., color = ..., shape = ...)) +    # shape = B&W redundancy
  geom_hline(yintercept = 0, linetype = "solid", color = "grey50") +
  geom_vline(xintercept = -0.5, linetype = "dashed") +              # treatment threshold (event-time)
  geom_point(position = position_dodge(width = 0.3), size = 3) +
  geom_errorbar(aes(ymin = ..., ymax = ...),
                width = 0.2, position = position_dodge(width = 0.3)) +
  scale_x_continuous(breaks = ...) +                                # space out a busy axis
  theme_hc() +
  theme(legend.title = element_blank(),
        axis.line = element_line(color = "black"),
        axis.title = element_text(size = 16.5),
        axis.text = element_text(size = 16.5),
        legend.text = element_text(size = 16.5),
        plot.title = element_text(size = 16.5),
        axis.title.y = element_blank(),
        legend.position = "bottom") +
  scale_color_ptol() +
  scale_shape_manual(values = c(16, 17, 15, 18)) +                 # B&W-safe shapes
  xlab("...") +                                                     # concise — one word if possible
  ggtitle("...")                                                     # the y-axis meaning (subtitle role)

# Match the project’s existing save settings first; fallback shown here.
ggsave("results/figures/fig_<name>.pdf",
       width = 2500, height = 1500, units = "px", dpi = 300)
# Open at the intended insertion size and visually check.

Red flags — STOP

  • Shipping a figure you haven't visually examined. "The code ran clean" is not "the figure is readable." Render it, open it, check for clipping and overlap — every time, in every language.
  • A figure that collapses in black and white. If two series are indistinguishable in grayscale, color was the only channel — add shape, linetype, or direct labels.
  • A y-axis label on a presentation figure. It belongs in the title/subtitle; the y-axis title is never set.
  • Connecting independent estimates with a line. Event-study coefficients, cross-sectional regressions, group-wise estimates — dots + error bars, never a line. The line invents a trajectory.
  • Combining different outcomes in one canvas by default. Each outcome gets its own output figure; reserve a shared plot for competing specifications of the same outcome, with estimates visibly dodged.
  • Overlapping x-axis labels. A busy axis with every tick shown is unreadable. Reduce breaks or rotate — but prefer fewer breaks.
  • More than 2 inside-canvas annotations. Annotations enhance understanding — they are not decoration. If you're reaching for a third, one of them is metadata that belongs in the LaTeX notes.
  • Metadata (N, source, p-values, equations) inside the plot. They go in the LaTeX figure notes, not on the canvas.
  • Note text rendered below the plot (labs(caption =), fig.text) — the figure file carries no notes; notes are manuscript prose.
  • Fonts too small for presentation. If the audience is more than arm's length from the screen, default text is invisible. The floor is 16.5pt for axis/legend text.
  • A legend with one entry. If there's one series, the title already names it; drop the legend.
  • Saving a figure to the project root or a non-standard path. Deliverable figures go to results/figures/fig_<name>.pdf per project-organization.
  • Skipping the style because "I'm in Python, not R." The house style is principles, not an R package. Translate the theme, palette, and font sizes to your language.

Common rationalizations

ExcuseReality
"It's just a draft figure, I'll fix the labels later."The visual self-check catches the clipping now, when it's cheap. A draft with overlapping labels gets shown to a coauthor and the first thing they say is "fix the axis."
"A line looks cleaner than dots."A line claims continuity the estimates don't have. "Cleaner" here means "misleading." Dots + error bars is the honest encoding for discrete estimates.
"I'll put the sample size in the corner of the plot."That's metadata — it goes in the figure notes. Inside the plot it competes with the data and gets clipped at projection scale. But a load-bearing annotation (absolute counts on a percentage bar) stays.
"The default font size is fine."The default is ~11pt — for a paper page, not a screen. For a presentation, 16.5pt is the floor. If you can't read it from across the room, neither can the audience.
"I'll show every year on the x-axis so nothing is hidden."Colliding labels hide more than omitted ticks. Reduce breaks; the reader locates themselves from 3–5 visible labels, not 20 overlapping ones.
"The legend is a little long but it fits."At 16.5pt a long legend overflows or collides. Wrap to two rows or move to bottom before it breaks, not after.
"I connected the event-study dots to show the trend."There is no trend — those are independent estimates. The line fabricates interpolation between points you never estimated.
"I'm in Python, so theme_hc doesn't apply."The principle applies: clean white background, dark axis lines, minimal gridlines, Paul-Tol palette, 16.5pt fonts. Translate it, don't skip it.
"Nobody prints in black and white anymore."Reviewers do. E-ink readers do. And colorblind readers experience your color figure the way a B&W reader does. Add shape/linetype redundancy — it costs nothing and saves the figure in half its real-world renderings.
"I added five annotations to make the figure self-contained."At most 1–2 annotations, and only if removing them makes the figure harder to understand. The rest goes in LaTeX notes. A figure cluttered with labels is harder to read, not easier.

The Process

  1. Confirm the data is validated first. The figure is the last step, not the first. If the numbers haven't been verified, say so and offer result-verification — a polished figure of an unverified number is still an unverified number.
  2. Choose the figure boundary before the geom. One outcome per output figure. Compare alternative specifications of that outcome directly in the same plot (dodge coincident estimates); render different outcomes as separate figures.
  3. Pick the geom by the message, not by habit. Dots for discrete estimates (DiD, event study); lines for continuous observed series; stacked bars for composition; dodged bars for cross-sectional comparison. The dot-vs-line choice is a claim about what the data is — get it right.
  4. Apply the house style in your language. Clean theme + Paul-Tol palette + large fonts (16.5pt floor) + no y-axis label (meaning in the title/subtitle) + concise x-axis label + dashed treatment line for DiD + zero line + dodged points. Use the language tables above to translate the R reference to Python or Julia.
  5. Ensure black-and-white legibility. Every group distinction needs a redundant non-color channel — shape for points, linetype for lines, hatch for bars, or direct labels. Render in grayscale and verify you can still tell groups apart.
  6. Add at most 1–2 annotations that enhance understanding. Absolute values on percentage bars, a "pretreatment treatment group" label for relative scale. Each must pass: "does removing this make the figure harder to understand?"
  7. Match the project’s export settings. Use its figure directory, canvas, units, and explicit DPI; the fallback is 2500×1500 px at 300 dpi (8.33×5 in). Judge font proportions at the final insertion size.
  8. Render and visually examine. Open the saved file and run the visual self-check: no clipping, no overlap, legend fits, markers visible, palette distinguishable, B&W-safe, annotations placed cleanly. Fix and re-render until clean.
  9. Move all metadata to LaTeX figure notes — in the manuscript, not the image. N, source, sample restrictions, p-value conventions — they go in \caption or a note, never on the canvas and never rendered into the figure file (no labs(caption =), no figtext).
  10. Hand off to project-organization — confirm the figure is named and placed per convention; commit the checkpoint.

The bottom line

Presentation figure  →  clean theme + Paul-Tol palette + 16.5pt fonts + no y-label (title carries it) +
                         concise axis labels + right geom (dots for estimates, lines for series, stacks for composition) +
                         one outcome per output figure (dodge alternative specifications together) +
                         dashed treatment line for DiD + zero line + B&W-safe (shape/linetype redundancy) +
                         at most 1-2 enhancing annotations + metadata in LaTeX notes (in the paper, not the image) +
                         saved with project-matched dimensions + visually checked at final size (no clip, no overlap) — in ANY language
Otherwise             →  a plot that runs clean but is unreadable from the back row, or from a photocopier —
                         labels clipped, text overlapping, wrong geom inventing a trend, color-only distinctions
                         collapsing in grayscale, too many annotations crowding the data

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.