Turns tabular data into an 85 mm wide, single-panel line or bar figure: a PDF at a fixed physical size, a PNG preview, a snapshot of the input, the resolved style profile and a check report. Then, where the toolchain allows, sets that figure into a real conference body page so its final printed size can be judged rather than assumed.
The style numbers are provisional. They are the project’s initial choices, not a journal or conference requirement. Never present them as a venue standard.
Handled: line charts (one shared numeric x, one or more named y series) and vertical bar charts (one numeric value per named category, negatives allowed).
Not handled — say so plainly rather than improvising: scatter, histogram, heatmap, 3D, box/violin, stacked or grouped bars, twin axes, error bars, log axes, multi-panel and two-column layouts. A bar chart here compares named categories; it does not bin raw samples, so a request for a distribution is a histogram request and is out of scope.
Put the user’s data into one JSON file. Read references/input-schema.md for the
exact fields, and read it before writing the first spec of a session — the
validator rejects unknown fields, so guessing costs a round trip.
Restructuring data (a CSV column into x, a column per series) is fine.
Reordering, smoothing, resampling, rounding or dropping points is not: the figure
must show what the user gave you. If the data genuinely cannot be plotted — gaps,
NaN, mismatched lengths — say so and ask, rather than patching it silently.
Keep the spec somewhere durable (next to the output, or in the user’s project), not in a temporary directory — it is how the figure gets regenerated later.
python skills/scientific-figures/scripts/render.py \
--spec <your-spec>.json \
--output-dir <where-the-figure-should-go>
Worked examples of the input live in ../../tests/data/, and what a render
produces is committed under ../../tests/output/.
Add --profile <path> to use an edited style profile. Paths are relative to
wherever you run the command; the script itself works from any directory.
Exit codes: 0 the figure rendered (possibly with warnings), 1 a check failed,
2 the spec or profile was rejected and nothing was written.
If it exits 2, the message names the field and the reason. Fix the spec — do not
work around the validator by changing the data.
report.json and the terminal summary carry one entry per check, each pass,
warn, fail or not_checked. Read the failures and warnings; do not skim to the
overall status.
Common ones and what they mean:
| Check | If it complains |
|---|---|
glyph_coverage |
The font lacks characters in the labels (e.g. CJK in DejaVu Sans). Set fonts.family/fonts.fallbacks in a profile copy to a font that covers them. Do not ship a figure with box glyphs. |
font_resolution |
The requested font is not installed and matplotlib substituted one. Install it or change the profile. |
text_within_canvas |
Labels are being clipped. Shorten the label, rotate bar categories via bar.category_label_rotation_deg, or widen canvas.width_mm. |
tick_label_overlap |
Tick labels collide. Lower axes.max_xticks, rotate, shorten, or widen. |
panel_area |
Decorations are crowding out the plot. Usually too many series or overlong labels. |
line_data_integrity, bar_category_order |
A real bug — the drawing does not match the input. Stop and investigate; do not ship it. |
visual_review always comes back not_checked, and nothing fills it in
automatically. Open figure.png and judge what no script can:
If the environment cannot display images, say explicitly that the visual review did not happen. Never report it as passed.
There is a browser tool that shows a single-panel figure at its printed size on a
calibrated page of any of the three venues, with sliders for the type sizes,
weights, line widths, tick counts and bar settings. It runs this repository’s own
figure_core.py in the browser through Pyodide, so the preview is not an
imitation of the figure — it is the figure.
python scripts/build_and_verify_web.py --only fetch-runtime --only fonts --only assets
python web/serve.py # then open http://127.0.0.1:8765/index.html
Point the user at it when they want to decide how the figure should look rather
than have you decide. Its “Copy for an agent” button produces a complete hand-off:
the resolved profile, the spec, the physical size, the versions and the exact
render.py command.
When a user hands you that text, load it and use it. Do not “improve” the values they chose. Render with the profile as given, and report the profile, the size, the fonts and the environment you actually used. If something in it cannot work, say which field and why — do not quietly substitute your own number.
Three things about that preview are worth being straight about, because a user may assume otherwise:
Step 5 below is still the check for a real page. The browser tool exists so that day-to-day previewing does not need LaTeX; it does not replace the compile.
A figure is read at whatever width the template gives it, not at 85 mm on white.
Half of ICLR’s text block is 69.85 mm; one ICML column is 82.55 mm. An 85 mm
figure dropped into the ICLR slot shrinks by 0.82, and its 9 pt labels print at
7.4 pt. report.json cannot see any of that.
python tests/render_conference_context.py --venue iclr2026 --case line-multi
That compiles the venue’s own style around a page of real prose, places the
figure, and writes page.pdf, page.png and context-report.json under
tests/output/conference-context/<venue>/<case>/. Read
references/article-context-evaluation.md before running it or reporting what it
found.
When to do this:
--all.The default path re-renders the figure at the template’s width, so it lands at
scale 1 and the type sizes are the ones you set. Placing an already-rendered
figure (--figure-dir) reports the real reduction instead.
Then look at page.png with the prose beside it: are the tick labels
readable next to 10 pt body text, is the legend clear of the data at this width,
are the series still separable? A high-DPI preview stretched to your screen is
not the printed size — the physical numbers are in the report, and page.pdf at
100 % is the real thing.
Needs LaTeX and poppler; render.py does not.
python tests/render_conference_context.py --check-dependencies says whether
they are installed. If they are missing, or the fixtures are not there, say the
context evaluation did not run. The figure itself is still deliverable — but do
not report the article context as verified when nothing compiled.
The context output goes in its own directory and never touches the five files
from step 2; report.json keeps the standalone check results.
Fix what the report and your own look at the PNG turned up, then re-render. At most two correction rounds, then report what is still wrong. Never edit the user’s data to make a check pass, and never loop.
Style fixes go in a copy of the profile passed with --profile, or — if the user
wants the change to be the new default — in
references/profiles/single-column.json itself, with their agreement. Never
hard-code a size or colour in a template.
Changing the figure changes the page, so re-run step 5 as well as step 2.
Report where the files are, which checks warned, whether you looked at the PNG, and
the reproduce command from report.json.
If step 5 ran, say which venue’s page you produced, the width it was placed at, the scale factor, and that the style values are provisional project choices — not that venue’s requirement. If step 5 did not run, say so and why.
The output directory gets exactly five files. Anything else already there is left alone; nothing is deleted.
| File | What it is |
|---|---|
figure.pdf |
The deliverable, at the profile’s physical size (85 mm wide by default) |
figure.png |
Raster preview at output.png_dpi (300) |
spec.json |
The validated input, exactly as drawn |
profile.resolved.json |
The style profile that produced this figure |
report.json |
Check results, environment (matplotlib version, resolved fonts), reproduce command |
spec.json + profile.resolved.json reproduce the figure; report.json carries
the command that does it.
All style lives in references/profiles/single-column.json — sizes, fonts,
colours, tick counts, bar width, output settings. Templates read it and hold no
defaults of their own.
Most type settings have an optional per-axis override — fonts.size_x_label_pt,
fonts.size_ytick_pt, fonts.weight_x_label and so on — which falls back to the
shared field (size_axis_label_pt, size_tick_pt, weight) when absent. A
profile written before those existed renders exactly what it always did. Ask
figure_core.font_size(profile, role) / font_weight(profile, role) rather than
reading a field directly; those are what the renderer uses.
Point sizes are physical: a 9 pt label is 9 pt on the page, and png_dpi only
changes preview resolution. Read references/design-rules.md before changing a
value, arguing for one, or explaining the visual choices — it covers the palette,
the marker/dash strategy, the layout engine constraint, and exactly what the checks
can and cannot prove.
Python with matplotlib and numpy. No LaTeX installation is needed to draw
a figure — maths labels use matplotlib mathtext ("$\\alpha_i$"). See
references/input-schema.md for the maths and non-ASCII text rules.
The step 5 article-context check is the one exception: it needs latexmk,
pdflatex, bibtex and pdftoppm (sudo apt install
texlive-latex-recommended texlive-fonts-recommended texlive-latex-extra latexmk
poppler-utils). Without them the figure still renders; the context check reports
that it did not run.
scripts/render.py — CLI: validate, draw, export, checkscripts/inspect_figure.py — the checks; also runnable standalone on an existing
output directory (--output-dir) for file-level re-checksscripts/evaluate_context.py — places a figure in a conference template, measures
the result, keeps the body page. Takes its fixtures by path, so a copied skill
still worksscripts/figure_core.py — spec/profile validation, style resolution, exportassets/templates/line.py, assets/templates/bar.py — drawing onlyreferences/input-schema.md — the input contractreferences/design-rules.md — the design decisions and the limits of the checksreferences/article-context-evaluation.md — how the in-page check works, what it
measures, and what it deliberately refuses to judgereferences/profiles/single-column.json — every style valueOutside the skill, web/ holds the browser style lab described in step 4b — its
paper pages are generated from compiled LaTeX by
scripts/calibrate_conference_pages.py and the evidence that they match is under
tests/web/calibration/. tests/data/ holds prefab specs and tests/output/ holds their
rendered results — a proof sheet of the current profile, regenerated by
python tests/render_gallery.py. Look there when you need a worked example of the
input format or want to see what the preset values actually produce.
tests/output/conference-context/ holds the same figures set into real ICLR,
NeurIPS and ICML pages, regenerated by
python tests/render_conference_context.py --all. That is where you see what the
preset values look like at the size a reader gets them. The venue templates,
the body-text excerpt and the wrappers live in tests/fixtures/ and
tests/conference_context/.