---
name: publication-figure-conventions
source: https://app.decimal.ai/s/publication-figure-conventions@1/SKILL.md
source_sha256: 4e379ac32f18
---

# Publication figure house style

## Contract

Enforces the lab's publication house style on every matplotlib/seaborn figure: a fixed
Okabe-Ito color cycle, perceptually-uniform colormaps, explicit journal widths, vector PDF
export at 300 dpi, despining, sentence-case labels with units, and named uncertainty. Apply
whenever generating figure code intended for a manuscript, journal submission, or paper.
These are non-negotiable overrides of library defaults — the base model would otherwise emit
tab10 colors, a 6.4x4.8 figure, a framed legend, and a 100-dpi PNG.

## Rules (the complete spec)

### Color
1. Set the categorical color cycle to the Okabe-Ito palette — EXACTLY these 8 hex codes in
   THIS order, then install them as the prop cycle:
   ```python
   okabe_ito = ['#E69F00', '#56B4E9', '#009E73', '#F0E442',
                '#0072B2', '#D55E00', '#CC79A7', '#000000']
   plt.rcParams['axes.prop_cycle'] = plt.cycler(color=okabe_ito)
   ```
   Never rely on the default tab10 cycle. The order is fixed (orange first, sky-blue second).
2. Continuous / sequential data (heatmaps, intensity images): use a perceptually-uniform
   colormap — `viridis`, `plasma`, or `cividis`. NEVER `jet` or `rainbow`.
3. Diverging data (correlation matrices, signed differences): `cmap='RdBu_r'` with `center=0`.
   NEVER a red-green diverging map.
4. For colorblind/grayscale safety on line plots, add redundant non-color encoding: distinct
   `linestyle` and/or `marker` per series, not color alone.

### Size & format
5. Always pass `figsize` explicitly. Single-column default is `(3.5, 2.5)` inches.
6. Journal full widths in millimeters — single / double column:
   - Nature: 89 / 183 mm  (≈ 3.5 / 7.2 in)
   - Science: 55 / 175 mm (≈ 2.2 / 6.9 in)
   - Cell: 85 / 178 mm    (≈ 3.35 / 7.0 in)
   Convert mm to inches by dividing by 25.4.
7. Save as a VECTOR PDF at `dpi=300` with tight bounding box:
   `fig.savefig('figure1.pdf', dpi=300, bbox_inches='tight')`. Also acceptable: EPS, SVG.
   NEVER JPEG/JPG for plots or line art (compression artifacts). TIFF/PNG only for photos.

### Axes & labels
8. Remove the top and right spines on every axes:
   `ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)` — or `sns.despine()`.
9. Axis labels in SENTENCE CASE with the unit in parentheses: `ax.set_xlabel('Time (hours)')`,
   `ax.set_ylabel('Response (uM)')`. Never ALL-CAPS, never unit-less.
10. Legends carry no frame: `ax.legend(frameon=False)`.

### Multi-panel
11. Label each panel with a BOLD UPPERCASE letter (A, B, C, D) at the top-left, placed in axes
    coordinates: `ax.text(-0.15, 1.05, 'A', transform=ax.transAxes, fontsize=10, fontweight='bold')`.
12. Keep figsize, fonts, palette, and styling identical across all panels of one figure.

### Statistics
13. Always show uncertainty — error bars or a confidence band — and NAME the type
    (SD, SEM, or 95% CI) in a comment, label, or caption. Show individual points when feasible.

## Worked examples (BEFORE base default -> AFTER house style)

### Color cycle
BEFORE:
```python
ax.plot(x, y1, label='control')   # tab10 blue
ax.plot(x, y2, label='treated')   # tab10 orange
```
AFTER:
```python
okabe_ito = ['#E69F00', '#56B4E9', '#009E73', '#F0E442',
             '#0072B2', '#D55E00', '#CC79A7', '#000000']
plt.rcParams['axes.prop_cycle'] = plt.cycler(color=okabe_ito)
ax.plot(x, y1, label='control')   # now #E69F00
ax.plot(x, y2, label='treated')   # now #56B4E9
```

### Continuous colormap
BEFORE: `ax.imshow(arr, cmap='jet'); fig.colorbar(...)`
AFTER:  `im = ax.imshow(arr, cmap='viridis'); fig.colorbar(im, ax=ax)`

### Diverging colormap
BEFORE: `sns.heatmap(df.corr(), cmap='coolwarm')`  (uncentered, generic)
AFTER:  `sns.heatmap(df.corr(), cmap='RdBu_r', center=0, annot=True, fmt='.2f', square=True)`

### Figure size / journal width
BEFORE: `fig, ax = plt.subplots()`  (6.4 x 4.8 in default)
AFTER:  `fig, ax = plt.subplots(figsize=(3.5, 2.5))`  # single column
For a Nature double-column figure: `figsize=(183/25.4, 4)`  # 183 mm ≈ 7.2 in

### Vector export
BEFORE: `fig.savefig('fig.png')`  (raster, ~100 dpi)
AFTER:  `fig.savefig('figure1.pdf', dpi=300, bbox_inches='tight')`

### Despine
BEFORE: (all four spines drawn)
AFTER:  `ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)`

### Axis labels
BEFORE: `ax.set_xlabel('TIME'); ax.set_ylabel('amplitude')`
AFTER:  `ax.set_xlabel('Time (hours)'); ax.set_ylabel('Amplitude (mV)')`

### Legend
BEFORE: `ax.legend()`  (framed box)
AFTER:  `ax.legend(frameon=False)`

### Panel labels
BEFORE: `ax.set_title('1')`  (numeric, inside title)
AFTER:  `ax.text(-0.15, 1.05, 'A', transform=ax.transAxes, fontsize=10, fontweight='bold')`

### Uncertainty
BEFORE: `ax.bar(conditions, means)`  (no error)
AFTER:
```python
ax.errorbar(conditions, means, yerr=sems, fmt='o', capsize=3)  # error bars = SEM
```

### Colorblind redundant encoding
BEFORE: four lines distinguished by color only.
AFTER:
```python
styles, marks = ['-', '--', '-.', ':'], ['o', 's', '^', 'v']
for i, (yy, lbl) in enumerate(series):
    ax.plot(x, yy, linestyle=styles[i], marker=marks[i], label=lbl)  # color from Okabe-Ito cycle
```

## Edge cases & exceptions

- More than 8 categorical series: 8 Okabe-Ito colors are the ceiling — add linestyle/marker
  encoding rather than inventing a 9th color, or split into panels.
- Nature lowercases panel letters (a, b, c); most other journals use uppercase. Default to
  UPPERCASE unless the target is explicitly Nature.
- Interactive Plotly figures: still export a STATIC vector/high-res raster for the manuscript
  (`fig.write_image('figure.png', scale=3)` ≈ 300 dpi); the interactive version is not the deliverable.
- Bar charts: start the y-axis at 0 unless a non-zero baseline is scientifically justified and noted.
- A single-series heatmap that is purely positive (e.g. counts) uses `viridis`, NOT `RdBu_r` —
  `RdBu_r` + `center=0` is only for signed/diverging data.
- Photographs / microscopy: TIFF or PNG at 300-600 dpi is correct — the "no JPEG / vector PDF"
  rule is for plots and line art, not pixel images.

## Do / Don't

- DO install the Okabe-Ito prop cycle; DON'T leave the default tab10 cycle.
- DO use `viridis`/`plasma`/`cividis`; NEVER use `jet` or `rainbow`.
- DO center diverging maps at 0 with `RdBu_r`; NEVER use red-green diverging colors.
- DO set `figsize` explicitly; DON'T accept the 6.4x4.8 default.
- DO save vector PDF at `dpi=300`; NEVER save plots as JPEG.
- DO despine top+right; DON'T ship the full four-spine box.
- DO write `'Time (hours)'`; DON'T write `'TIME'` or a unit-less label.
- DO pass `frameon=False`; DON'T leave the boxed legend.
- DO label panels bold-uppercase in axes coords; DON'T number them in the title.
- DO show and name the uncertainty type; NEVER plot means with no error indication.

## Common mistakes (the base's wrong defaults)

1. Leaving the default tab10 color cycle instead of installing Okabe-Ito.
2. Reaching for `jet`/`rainbow`/`coolwarm` for heatmaps.
3. Using a diverging map without `center=0` (misleading neutral point).
4. Omitting `figsize`, producing an oversized 6.4x4.8 figure.
5. Saving `.png` at default dpi, or worse `.jpg`, for a line plot.
6. Drawing all four spines and a framed legend.
7. ALL-CAPS or unit-less axis labels.
8. Numbering panels in the title instead of bold-uppercase axes-coordinate labels.
9. Plotting summary means with no error bars / CI, or not stating the error type.
10. Distinguishing series by color alone, breaking grayscale and colorblind readability.

## Quick checklist
Okabe-Ito cycle - viridis/RdBu_r(center=0) not jet - explicit figsize / journal mm - vector
PDF @300 not JPEG - despine top+right - sentence-case labels with units - legend frameon=False -
bold-uppercase panel labels in axes coords - named uncertainty - redundant encoding for colorblind.
