orx-figures
GitHub用于生成交互式HTML图表或静态出版级矢量图(PDF/SVG)的Skill。支持实验指标可视化,要求数据真实、自包含且符合排版规范,适用于聊天交互与论文发表场景。
Trigger Scenarios
Install
npx skills add alphaXiv/OpenResearch --skill orx-figures -g -y
SKILL.md
Frontmatter
{
"name": "orx-figures",
"description": "Create or update interactive HTML charts for chat and static publication figures. Invoke before writing plotting code or when an existing figure needs correction; use `orx-results` for experiment interpretation and `orx-logs` for retrieving measurements."
}
Prefer interactive HTML for suitable experiment metrics in chat, and static vector
figures for papers. Skip a chart when the data or available templates do not fit,
or when a text summary communicates the result better. Both should use real evidence, clear units, and honest uncertainty.
Show newly created image figures inline in the chat handoff, including SVGs:
. Keep their clickable file links as well.
Interactive charts in chat
When a visualization helps explain experiment metrics in chat, use the bundled offline HTML chart. Start with one useful view. Add a dropdown only when the same data benefits from multiple distinct views; never add views just to fill the selector. The template handles chart styling, controls, and interactions. Use real run data, preserve raw values, and state the seed count. Use static publication figures when the user requests a paper or export.
Vendor both files beside the rendering script:
orx skill figures/assets/orx-chart.html > artifacts/orx-chart.html
orx skill figures/assets/orx_chart.py > artifacts/orx_chart.py
from orx_chart import render_chart
render_chart("artifacts/loss.html", title="Learning-rate comparison",
subtitle="20 steps · single seed",
metrics=[dict(key="loss", label="Validation loss", unit="nats/token")],
series=[dict(name="LR 0.001", points=baseline_rows),
dict(name="LR 0.002", points=comparison_rows)])
Each point is a dict with numeric step and metric values. A missing or null
metric is skipped. Pass at least one finite point per metric; use matching steps
for run comparisons. Import from the directory where you vendored the helper.
Keep HTML self-contained: no CDN scripts, external fonts, or network dependency.
Embed the result inline using image syntax (HTML opens as a sandboxed figure):

Link CSV/JSON evidence alongside the figure for access to the underlying data. Reuse a run's ready-made chart rather than regenerating it. The paper-specific rules below apply to static publication output.
Non-negotiables
- Build at the final printed size. A figure drawn 8 inches wide and shrunk
to a 3.25-inch column has 3pt tick labels. Pick the width from the document
(
COLUMN,TEXT,WIDEin the style module), then include it withwidth=\linewidth, which scales by 1.0 once the size is right (see Where the figure goes). Never build big and rescale to fit. AWIDEfigure only gets a 6.75-inch\linewidthinside afigure*— in a plainfigurein a two-column paper it silently scales to about a half. - Vector out. Write PDF for
\includegraphicsand SVG alongside it for preview. A rasterized plot — axes, lines, text — is a defect: it blurs under the zoom every reviewer uses. PNG is correct only for genuinely raster content: a photograph, a sample image grid, an attention map at pixel resolution. - Every number comes from a run. Read metrics from the file located by
orx logs(see theorx-logsmodule). Never plot a remembered, rounded, or plausible number, and never leave synthetic demo data in a script that ships. - The caption is the title. No
ax.set_titleon a paper figure — a title duplicates the caption and steals vertical space. Panel letters (a, b) are how you name parts of a multi-panel figure. - Show the uncertainty, or say there is none. One seed is an anecdote. Plot the interval across seeds and state the seed count in the caption; if you only have one run, say "single seed" rather than implying more.
- Label the axes with units. "Loss" is a label; "step" without knowing whether it counts optimizer steps or tokens is not.
- Colorblind-safe, greyscale-safe. Use the module's palette. Never
jet,rainbow, orhsv— they invent structure that is not in the data. - Sans-serif text, and the same face in every figure.
use_style()defaults to a Helvetica-metric sans, which is what Nature and Science require in figures and what survives the 5-8pt sizes tick labels print at. Figure text is its own register — it does not have to match the body font, but it must match across your figures, diagrams included. Passuse_style(family="serif")only for a figure carrying heavy math that should read as part of a serif-set page.
Set up once per project
Vendor the style module next to the figure scripts so a figure stays reproducible after this session ends:
mkdir -p figs && orx skill figures/assets/orx_figstyle.py > figs/orx_figstyle.py
Vendor it next to whichever destination the script lives in — a report's script under the artifacts directory needs its own copy there, or the import fails.
(That works from any directory. Check the file is non-empty before importing it — the redirect creates the target before the lookup runs.)
Follow the session playbook's Python policy. The Python plotting templates
use matplotlib and numpy. For independent plots using uv:
uv run --no-project --with matplotlib --with numpy python figs/loss_curve.py
For saved scripts with inline dependency metadata, use
uv run --no-project figs/loss_curve.py so uv reads that metadata.
Plots importing project code must run in the project environment, not the
isolated environment above.
Where the figure goes, and how to cite it
Two destinations, and the citation tag is different for each. Getting the pair wrong is the most common way a finished figure reaches the user as a broken link.
| The figure is for | Write it to | Cite it as |
|---|---|---|
A paper (.tex) |
figs/ beside the .tex in the working tree — \includegraphics resolves paths relative to the source, and the artifacts directory is not beside it |
<file path="figs/loss_curve.pdf" /> — repository-relative, no artifacts/ prefix |
| A report, a summary, an answer in chat | Load orx-reports and use the relevant topic or deliverable folder under the artifacts directory, e.g. transformer-sweep/figures/ |
<file path="artifacts/transformer-sweep/figures/loss_curve.pdf" /> |
The tag must match the destination. A figure written to the worktree but
cited with an artifacts/ prefix is looked for in the artifacts directory and
in the worktree at artifacts/... — it is in neither, and the user gets "File
not found" when they click it.
Those two are the only destinations. Never write a figure you intend to
cite into /tmp, a scratch directory, or anywhere outside the worktree and the
artifacts directory: a chat file link resolves against exactly those two roots
and nothing else. Pulling logs or intermediate data into /tmp is fine — the
figure, and the script that made it, are not intermediate.
Keep the generating script beside its output, in whichever of the two
destinations you chose (figs/loss_curve.py → figs/loss_curve.pdf,
figs/loss_curve.svg). A figure whose script is gone
cannot be corrected when a reviewer asks for one more seed.
Build the figure at the column width, and still write width=\linewidth:
\begin{figure}[t] % figure* for a WIDE figure in a two-column paper
\centering
\includegraphics[width=\linewidth]{figs/loss_curve.pdf}
\caption{Validation loss over training, mean of 5 seeds with 95\% intervals.}
\label{fig:loss}
\end{figure}
The two together are the point. Sized correctly, width=\linewidth scales by
1.0 and changes nothing — but it still fits if the venue's column turns out
narrower than you assumed. What it cannot do is rescue a figure built at the
wrong size: a 15-inch canvas dropped into a 5.5-inch column is scaled to 0.35,
and 11pt tick labels land at 4pt. That is the most common reason a real paper's
figures are unreadable, and width=\linewidth is what hides it until print.
Write the caption with the figure
Every reference below tells you to "state it in the caption". This is what that means, and the caption is written with the figure, not after the draft.
Captions in current papers run a median of about 28 words, and half are three sentences or more. The dominant shape is a short bold phrase naming the claim, then the detail a reader needs to trust it:
\caption{\textbf{LPO reaches the reward plateau in a third of the compute.}
Held-out reward against training compute for LPO and two baselines; mean of 5
seeds, bands are 95\% intervals. The dotted line is the pretrained model.}
- Lead with the finding, not the setup. "Training curves for LPO and baselines" names the axes, which the axes already do. The caption's first clause should be the thing you want remembered.
- The figure must stand alone. A reader who skips your Section 4 should still get the claim right. That is the whole job.
- Put the method facts here, because nowhere else in the figure has room: seed count, what the band or bar means, any smoothing, any normalization, which points were fitted and which excluded, and whether a frontier line is measured or a guide.
- Say what is not shown when it matters — a single seed, a truncated axis, a run cut short.
- Do not restate the axis labels in prose, and do not repeat the caption as an axes title. The title is the caption's job.
A one-panel figure earns roughly 25-40 words; a multi-panel figure earns more, and each panel needs its own clause.
Multi-panel figures
Roughly a quarter of figures in current ML papers are multi-panel — (a)/(b)
subpanels, or a Left:/Right: pair. Panels are a composition choice that cuts
across every figure type below, and they go wrong in consistent ways.
- Label every panel
(a),(b), … at the top-left (panel_labels()). Prose refers to "Fig. 2b"; without letters the only way to name a panel is by position, which breaks the moment one moves. - Share the axis when panels share a quantity (
sharey=True), and let the shared axis carry one label on the panel that shows the ticks. Two panels of the same metric on silently different ranges is the multi-panel version of a truncated bar axis — the reader compares heights that are not comparable. - One legend for the figure, not one per panel. Put it under the panels or outside the last one.
- Panels read in argument order: left to right, top to bottom. Panel (a) should be the one the text discusses first.
- If the panels do not support one claim, they are separate figures. A 2×3 grid of unrelated plots is a contact sheet, and its caption will read as a list rather than a sentence.
Build them with figure_grid(nrows, ncols, width=TEXT, sharey=True), which
sizes the whole grid to the final printed width.
Read exactly one reference
Pick by the question the figure answers, not by the shape you have in mind.
| The figure answers | Read |
|---|---|
| How does a metric move over training, and is the gap bigger than seed noise? | references/curves.md |
| How does performance change with scale, and what does the trend predict? | references/scaling.md |
| Which method wins across benchmarks, or which ablated component mattered? | references/comparison.md |
| What is traded off against what — reward vs KL, quality vs cost or latency? | references/pareto.md |
| What does this 2D grid, confusion matrix, or sweep look like? | references/matrix.md |
| What is the method, architecture, or pipeline? | references/diagram.md |
Every reference's template writes to figs/, which is the paper destination.
For a report or a chat answer, adapt the template's script and output paths to
the folder chosen per orx-reports.
Do not read references for figures you are not making. If an installed
reference cannot be read, orx skill figures/<name> prints it.
Anything genuinely outside those six — a photograph, a qualitative sample grid, a map — still obeys the non-negotiables above and the style module.
Before you hand it over
- Read the audit line
save()prints. It checks the printed width, the Type 42 font embedding, stray axes titles, missing axis labels, text below the 5pt floor, text that overlaps other text, and text running off the canvas.cleanis the bar; anything else is a defect to fix, not a warning to note and move past. - One audit finding is not yours to fix: if it reports no publication font on the machine, say so when you hand the figure over rather than installing fonts, which is a change to the environment you should ask about first.
- A label placed at a reference line or a data point is the usual source of an
overlap — it lands on a tick label at some data ranges and not others. Move
it, or give it
backgroundcolor="white"so it masks what it covers. - Open the PDF and read it at printed size. If a tick label is unreadable on screen at 100%, it is unreadable on paper.
- Every axis labelled, with units. No stray
ax.set_title. - The claim in the caption is visible in the figure without the prose.
- Uncertainty shown, with n stated.
- No unexplained series, no legend entry for something that was cut.
- The script reruns from scratch and reproduces the same file.
Interactive graph templates
One graph is visible at a time. The shared dropdown chooses the view; do not build composite dashboards or append tables to this component.
Use orx_chart.render_chart with title, subtitle, metrics, and series.
Each view has a unique key, label, type, unit, optional xLabel and
yLabel, and optionally its own series. A series has name, points,
optional color and dash. Points use numeric step for x and the view's
key for y. Use real measurements and state seed count in the subtitle.
| ORX family | Interactive template and data |
|---|---|
| Learning curves | line; measured lower/upper bounds add a confidence band. area fills to zero for signed differences. |
| Scaling laws | scaling; positive x/y coordinates, logarithmic axes by default. Supply separately computed fitted samples as series.fit; fitDash distinguishes extrapolation. State equation, fit range and uncertainty in the subtitle. The renderer does not estimate a law. |
| Comparisons / ablations | bar groups methods at shared numeric category positions with point label for ticks, zero baseline. dot shows horizontal deltas with optional measured lower/upper whiskers and category label. |
| Pareto trade-offs | pareto; numeric x/y points, defaults minimize x/maximize y; set minimizeX/maximizeY for other directions. All measured points remain visible; only non-dominated points are connected per series. Name directions and state the frontier is a guide. |
| Matrices | heatmap with one series per row; columns supplies column labels. Sequential viridis colors by default; colorScale="diverging" centers signed values on zero. Missing cells remain gray. confusion uses square cells; supply normalized values, colorMin=0, colorMax=1, and state normalization/support. |
| Method diagrams | diagram uses nodes with id, label, integer column/row, optional accent/description; edges have from, to, optional label/dashed. Clicking a node highlights adjacent connections; Escape restores the diagram. |
scatter is also available for measured relationships. Curves, scatter,
scaling and Pareto plots may set xScale or yScale to log. Bars and areas
require a linear y-axis because their baseline is zero. Numeric curves, scatter and Pareto plots use
mouse drag selection zoom and show Reset only after zoom. Matrix cells,
diagram nodes and dot intervals expose exact data on hover/focus. Download
exports the current graph as SVG. Do not invent uncertainty or scaling data
just to demonstrate a template; a single-seed demo can show single-seed curves,
comparisons, measured trade-offs, a matrix, and its actual method diagram.
Version History
-
6285fee
Current 2026-10-18 16:16
增强在线演示中的图表渲染与图片预览功能;修复图表范围及内联预览尺寸问题;优化轴标签精度、缩放选择保留及置信带显示;拆分实验证据相关技能以改进代码审查反馈。
- 24e404e 2026-09-21 23:53


