| name | nature-style |
| description | Nature-journal house style for publication-ready scientific figures, optimised for Python/matplotlib plotting. Bundles palette.py (a drop-in matplotlib style module with the Nature-style house palette, Wong colourblind-safe set, point-scale type, safe stroke weights, and helpers) plus the full design system as CSS tokens and figure components for HTML/SVG. Use this skill whenever creating or restyling any chart, plot, diagram, schematic, or multi-panel figure for a scientific paper or analysis — matplotlib/seaborn code, an HTML/SVG mock, choosing colours for scientific data, or preparing figures for journal submission. Trigger it for research-context plotting requests even when the user just says "plot this", "画个图", "make a figure", or names matplotlib/seaborn without saying "Nature". |
| user-invocable | true |
Nature-style scientific figures
This skill makes figures publication-ready in Nature's house style. It is
built around Python/matplotlib — palette.py is the primary artifact — but
the same design system also ships as CSS tokens and React/SVG components for
HTML figures and mocks.
Two rules govern everything, in any medium: the canvas is pure white, and
figures are static — no animation, hover, drop shadows, gradients-as-
decoration, rounded corners, or decorative chrome. references/design-system.md
holds the full design guide (content rules, visual foundations, iconography,
layout grid); read it when you need the reasoning behind a rule.
The house conventions in one place:
- Type: sans-serif only, sized in points at final print size — 6pt tick
numbers, 7pt body, 8pt axis titles and panel labels. Never below 5pt. Regular
and bold only; bold carries emphasis, not size.
- Strokes: in points, safe floor 0.5pt. Emphasised data line ~1.6pt, regular
~1.1pt, baselines/error bars ~0.8pt.
- Labels: sentence case everywhere (
Tumour volume (mm³), not Title Case).
SI units in parentheses. Panel labels lowercase bold a b c. Real Unicode
glyphs (µ, α, ±, ×, ≤), never spelled out.
- Colour: the Nature house palette leads; use the Wong/Okabe–Ito set when
strict colourblind safety is required. Colour groups and directs — the most
important series is the most saturated, context recedes to grey. Never
distinguish series by colour alone: pair with marker shape or line style so
the figure survives greyscale.
- Layout: journal grid — 1 column = 9 cm (3.54 in), 2 columns = 18.4 cm
(7.24 in), height ceiling ~24 cm. Captions start
Fig. 1. and define every
symbol, colour, n, and error type. Size the figure at its final width —
see the sizing section below; this is the step people most often get wrong.
Size to the space it will occupy — do this FIRST
This is the make-or-break step and the reason figures come out with unreadably
small text. A point is 1/72 inch of the saved file, so a 7 pt label is 7 pt
to the reader only if the file is shown at the size it was saved. The moment
the figure is scaled down on the page, every font, line and marker shrinks with
it.
The classic failure: generate a big figure (matplotlib's default is 6.4 in
wide; people often pick figsize=(10, 6)), then drop it into LaTeX with
\includegraphics[width=\columnwidth]. If the column is 3.3 in and the figure
is 10 in, LaTeX rescales by 3.3/10 ≈ 0.33× — your careful 7 pt labels render at
~2.3 pt and are illegible. apply_style()'s point-scale type is correct, but
only survives if you don't rescale.
So always work backwards from the destination:
- Find the final width first — how wide the figure will actually sit in the
document. Typical targets: two-column paper (arXiv/most journals) single
column ≈ 3.3–3.5 in / 8.4–9 cm; a two-column span ≈ 7 in / 18 cm; a
one-column
article-class text width ≈ 5.5 in. If unsure which, ask.
- Set
figsize to exactly that width. Height follows from aspect (usually
0.5–0.8× the width). For a multi-panel row, the whole figure gets the span
width and the panels divide it.
- Keep the point-scale fonts from
apply_style() (6–8 pt) — now they are
correct at print size. Don't inflate them to compensate for a too-big figure;
fix the width instead.
- Insert at 100 %.
\includegraphics{fig.pdf} with no width=, or
width=\columnwidth only if the figsize width already equals the column
width. Never draw big and let the document shrink it.
The one equation to keep in mind: if a figure saved at width W_fig is shown at
width W_doc, the on-page font size is font_pt × W_doc / W_fig. Keep that
ratio at 1. Everything else in this skill assumes it is.
(savefig(..., bbox_inches="tight") crops surrounding whitespace, which changes
the saved width — so a later fixed width= will still rescale. Prefer
constrained_layout=True / fig.tight_layout() at a known figsize, or accept
the tight crop and insert at natural size.)
references/matplotlib-recipes.md has a target-width table and the LaTeX
inclusion do/don't.
Primary path — matplotlib (palette.py)
First, find whether the project already has a palette.py — don't create a
second one. A repo that has used this skill before will already carry the
module, most often at src/palette.py, src/plot/palette.py, or next to the
plotting code. Search before writing:
find . -name palette.py -not -path '*/site-packages/*'
If one exists, import and reuse that file so every figure in the project
stays consistent — do not copy a fresh one over it (that risks diverging
palettes). Only if none exists, copy this skill's palette.py into the
project's plotting location (e.g. src/plot/palette.py) and import from there.
Import it however the project is laid out — a plain import palette when it sits
next to the script, from src.plot import palette for a package, or add its
directory to sys.path.
palette.py is a self-contained module (only needs matplotlib). Build every
figure on it rather than hand-picking hex codes or font sizes:
import matplotlib.pyplot as plt
import palette
palette.apply_style()
fig, ax = plt.subplots(figsize=(3.54, 2.4))
ax.plot(x, y, color=palette.NAT_BLUE, lw=palette.LW_PRIMARY, label="Wild type")
ax.plot(x, z, color=palette.NAT_RED, lw=palette.LW_SECONDARY,
ls="--", marker="s", ms=3, label="Mutant")
ax.set_xlabel("Time (h)")
ax.set_ylabel("Signal (a.u.)")
ax.legend(frameon=False)
palette.despine(ax)
palette.panel_label(ax, -0.18, 1.02, "a")
fig.savefig("fig1.pdf")
Call palette.apply_style() once at the top of the script; it sets sans type
(including mathtext, so $\chi$, $S^\phi$ match the body), point-scale font
sizes, a white canvas, outward ticks, light gridlines, and editable-text
PDF/SVG output. Do not re-set those rcParams by hand afterwards — that is the
whole point of the module.
What palette.py exposes (read the file before writing figure code — it is
short and every symbol is documented):
- Colours:
NAT_BLUE/RED/GREEN/ORANGE/PURPLE/TEAL/YELLOW/OLIVE/BROWN/SLATE;
categorical lists CAT6 and CAT10; the WONG colourblind-safe list and its
named members; NAT_BLUE_RAMP. Legacy role aliases (PRIMARY, SECONDARY,
ERROR, …) map onto the Nature hues.
- Colormaps:
SEQUENTIAL_CMAP (single-hue blues, ordered data),
DIVERGING_CMAP (blue–white–red for signed data — never red–green),
BLUE_CMAP (white→Nature blue).
- Weights & sizes:
LW_PRIMARY/SECONDARY/BASELINE/FINE/CONNECTOR,
FS_TICK/BODY/LABEL/PANEL.
- Helpers:
panel_label(target, x, y, letter) (bold lowercase letters on a
Figure or Axes), despine(ax, top=True, right=True) (open frame for line/bar
plots; keep the full box for imshow/heatmaps).
For concrete, copy-ready recipes (categorical bars with error bars + significance
brackets, multi-series scatter with distinct marker shapes, heatmap +
colourbar, multi-panel a b c layouts, greyscale-safe styling, seaborn), read
references/matplotlib-recipes.md.
Choosing colours
- 1 series →
NAT_BLUE. 2 → blue + red. Up to 6 → CAT6. Up to 10 → CAT10.
- Ordered/continuous data →
SEQUENTIAL_CMAP with a colourbar and units.
- Signed data around a midpoint →
DIVERGING_CMAP.
- Strict colourblind-safe requirement → the
WONG list, and still vary marker
shape or line style.
Secondary path — HTML / SVG / JSX figures and mocks
The identical design system is available for web output. Link styles.css
(it @imports everything under tokens/) and build on the CSS custom properties
instead of hard-coding values:
tokens/palette-nature.css — Nature house ramps (--nat-blue-400, …) and
--series-1…8; tokens/colors.css — neutrals, Wong set, sequential/diverging
ramps, semantic aliases; tokens/typography.css, tokens/spacing.css,
tokens/figure.css (stroke widths, column widths), tokens/fonts.css.
components/structure/ — Figure (white canvas at journal width + caption),
Panel (labelled sub-figure), Axes (SVG plot frame), Node (schematic box).
components/annotation/ — Legend, ScaleBar, FlowArrow, Callout, Colorbar,
SigBracket. Each component has a .prompt.md with a usage snippet.
ui_kits/figures/ — assembled examples (bar, scatter, heatmap, pathway) to
copy from; templates/publication-figure/ — a blank journal-ready canvas;
guidelines/ — visual specimen cards for colours, type, spacing, strokes.
For a throwaway mock or artifact, copy the assets out and produce a static HTML
file that is visually indistinguishable from a print figure (no interactivity).
When asked to build a figure with no other guidance
Prefer Python/matplotlib unless the user's context is clearly web/HTML. Ask what
figure they want (chart type, single- or multi-panel, 1- or 2-column) and a few
clarifying questions (data, units, series, strict-colourblind or greyscale
requirement), then act as an expert figure designer — always sentence-case
labels, SI units, point-scale type, safe strokes, the Nature palette with a
second non-colour cue, and a caption that defines every element.