Back to skills
SKILL.md
Paper Figure Nature
ASecurityGenerate publication-ready matplotlib figures matching Nature journal standards. Use when user says 'Nature figure', 'publication figure', or asks for journal-grade plots.
- 10 stars
- 0 votes
- 0 copies
- 0 views
- Added September 24, 2026
Works with
Security analysis
100/100Pro scans all 8 files and shows the line behind each finding
npx -y skills add FOURTEEN1416/academic-agent-toolkit --skill paper-figure-nature --agent claude-codeAre you the author of Paper Figure Nature?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/fourteen1416-paper-figure-nature)---
name: paper-figure-nature
description: "Generate publication-ready matplotlib figures matching Nature journal standards. Use when user says 'Nature figure', 'publication figure', or asks for journal-grade plots."
argument-hint: [figure-plan-or-data-path]
allowed-tools: Bash(*), Read, Write, Edit, Grep, Glob, Agent
---
# Nature Figure: Publication-Quality Figures for Nature/High-Impact Journals
Generate Nature-style figures from: **$ARGUMENTS**
## Constants
- **FIG_DIR = `figures/`**
- **PRIMARY_FORMAT = `pdf`** (LaTeX embedding, vector)
- **DPI = 300**
- **CUSTOM_REQUIREMENTS** — User-specified requirements, highest priority.
## 📊 Recipe Library Reference (for layout inspiration only — colors stay Nature)
If `PAPER_PLAN.md`'s FIGURE_MANIFEST contains recipe annotations like `fig_q1 // empirical#8`,
you **may** read the corresponding recipe's *layout / annotation style* as a starting point.
However, **colors, fonts, font sizes, line widths, and figure dimensions must strictly follow
Nature's `PALETTE_NATURE` and rcParams** defined below — do **not** copy recipe colors/styles.
```bash
# Read a recipe for layout reference (NOT for colors)
python3 _utils/get_recipe.py empirical 8 2>/dev/null \
|| cat skills/shared-scripts/figure_recipes_empirical.md
```
**Recipe libraries available** (browse for layout inspiration only):
- `basic` / `advanced` / `empirical` / `academic` — generally suitable for Nature-style charts
- `competition` — ⛔ avoid: contest-style charts (Pareto fronts, convergence curves) do not match Nature aesthetics
**Override checklist when using a recipe as starting point:**
- Replace all colors with `PALETTE_NATURE`
- Set `plt.rcParams` per Nature spec (font: Arial/Helvetica 7pt, line width 0.5pt, single-column 89mm)
- Strip recipe-specific decorations (no gradient fills unless single-column heatmap; no Rain Cloud violins)
- Remove `plt.title()` (Nature figures use external captions)
---
## ⛔⛔⛔ Figure Completeness (HIGHEST PRIORITY — prevents "broken / partial" figures)
Nature style is minimal, but **minimal ≠ broken**. Users reported figures that came out as **floating colored blocks with no axes, no ticks, no curves** (e.g. a lone green shaded area, or scattered rectangles). Every figure MUST stay fully readable. Hard rules:
1. **Y-axis MUST keep numeric ticks.** Never call `ax.set_yticks([])` on a data plot — an axis with no scale is unreadable. Sparse (3–5 ticks) is fine, empty is forbidden.
2. **Both `ax.set_xlabel(...)` and `ax.set_ylabel(...)` are mandatory**, with units (e.g. `Time (h)`, `RMSE`). No bare/unlabeled axes.
3. **If you hide x-ticks (`ax.set_xticks([])`), you MUST directly label the data** (`ax.bar_label`, `ax.text`, or `annotate`). Hiding ticks WITHOUT direct labels = broken figure.
4. **Every `fill_between` / confidence band MUST be drawn together with its main line** (`ax.plot(...)`). A standalone shaded area with no curve and no axis is meaningless — this is exactly the "green blob" users complained about.
5. **`ax.set_frame_on(False)` is allowed ONLY for heatmaps / image plates**, never for line / bar / scatter plots — those keep left + bottom spines.
6. **Multi-panel: every subplot must have its own visible axes + labels.** Never leave bare colored rectangles floating with no axis.
⛔ **竞赛 / 中文论文场景**:评委需要完整可读的图(坐标轴 + 刻度 + 单位 + 图例或直接标注齐全)。Nature 的"省略图例 / 隐藏刻度 / 直接标注"只在仍能保证可读时才用,**绝不能产出只剩色块的残图**。每画完一张图,肉眼自检:去掉 caption 后,单看这张图能不能读懂坐标含义?不能就是残图,必须补全。
⛔⛔ **防标注遮挡(硬性,多点/轨迹/3D 图必守——"一堆文字糊成一团"是最常见的丑):** 只要一张图上有 ≥2 个文字标注(点名/事件名/坐标注记等),就必须保证**标注之间、标注与数据点之间不重叠**:
- **2D 图**:用 `adjustText`(`from adjustText import adjust_text`,环境已装)自动排开,或手动给每个 `annotate` 设**不同方向的 `xytext` 偏移 + 带 `arrowprops` 引线**指回目标点。禁止把多个 `ax.text` 堆在同一坐标附近不管重叠。
- **3D 图(`adjustText` 对 3D 无效,必须手动)**:多个点挤在视觉中心时(正是本反馈的翻车——"M1初始/FY1初始/遮蔽/爆炸"糊成一团),**不要在每个点旁硬塞长文字**。改用以下任一:① 点旁只放**短代号**(`M1`/`F1`/`E`),代号与全称的对照放进**图例**;② 给标注加**明显 `xytext` 偏移 + 引线**,各标注朝不同方向拉开;③ 点极密时干脆**只标图例、点上不标字**。关键是"**看得清每个字属于哪个点、字不互相压**"。
- **出图后自检**:放大看标注区——有没有两段文字叠在一起、字压在数据点上看不清?有就按上面改,别交付"糊成一团"的图。这条对 nature 风格和竞赛风格**一律适用**。
## Mandatory rcParams (apply at top of EVERY script)
```python
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
plt.rcParams['svg.fonttype'] = 'none' # editable text in SVG/PDF
plt.rcParams['font.size'] = 16 # 24 for large bar panels
plt.rcParams['axes.spines.right'] = False
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.linewidth'] = 2.5 # 3 for big bars, 2 for compact
plt.rcParams['legend.frameon'] = False
```
### Integration with plot_utils.py
Try `setup_style(palette='nature')` first. If unavailable, use inline rcParams above as fallback:
```python
import os, sys, shutil
os.makedirs('_utils', exist_ok=True)
for src in ['plot_utils.py']:
for search in ['skills/shared-scripts', '../skills/shared-scripts']:
p = os.path.join(search, src)
if os.path.isfile(p):
shutil.copy2(p, f'_utils/{src}')
break
sys.path.insert(0, '.')
try:
from _utils.plot_utils import setup_style, save_fig, PALETTE
setup_style(palette='nature')
except (ImportError, TypeError):
# Fallback: apply Nature rcParams directly
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
plt.rcParams['svg.fonttype'] = 'none'
plt.rcParams['font.size'] = 16
plt.rcParams['axes.spines.right'] = False
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.linewidth'] = 2.5
plt.rcParams['legend.frameon'] = False
```
## Nature Color Palette
```python
PALETTE_NATURE = {
"blue_main": "#0F4D92", # deep blue — hero method
"blue_secondary": "#3775BA", # medium blue
"green_1": "#DDF3DE", # light positive
"green_2": "#AADCA9", # mid positive
"green_3": "#8BCF8B", # strong positive
"red_1": "#F6CFCB", # light baseline
"red_2": "#E9A6A1", # mid baseline
"red_strong": "#B64342", # strong baseline/negative
"neutral_light": "#CFCECE",
"neutral_mid": "#767676",
"neutral_dark": "#4D4D4D",
"neutral_black": "#272727",
"gold": "#FFD700",
"teal": "#42949E",
"violet": "#9A4D8E",
}
# For unified-family figures (NMI-style dense pages)
PALETTE_NMI_PASTEL = {
"baseline_dark": "#484878",
"baseline_mid": "#7884B4",
"baseline_soft": "#B4C0E4",
"ours_tiny": "#E4E4F0",
"ours_base": "#E4CCD8",
"ours_large": "#F0C0CC",
"delta_up": "#2E9E44",
"delta_down": "#E53935",
}
```
Semantic rules:
- Blue = proposed/hero method
- Green = positive variants/improvements
- Red/pink = baselines/contrast
- Neutral grays = reference/background
- Use NMI pastel when comparing method families on dense pages
### ⛔⛔⛔ 取色必须调 `nature_palette()`,不要把上面的 hex 抄成字面量
上面的字典只是**默认值参考**,脚本里不要复制它。正确取色方式:
```python
from _utils.plot_utils import nature_palette, nature_markers
C = nature_palette() # 15 键语义字典;尊重用户色系,未固定色系时同族微调
M = nature_markers() # marker 顺序,也按工作区轮转
ax.plot(x, y, color=C['blue_main'], marker=M[0], label='本文方法')
ax.plot(x, y2, color=C['red_strong'], marker=M[1], label='基线')
ax.fill_between(x, lo, hi, color=C['green_1'], alpha=0.6)
```
**用户指定优先**:黑白或自定义色系会覆盖默认 Nature 色值;下面的同族微调和蓝/绿/红角色只适用于未固定色系的 Nature 默认色板。文字墨色可为对比度同色相加深,不能引入色系外强调色。
**为什么不能抄字面量**:`nature_palette()` 会按工作区名的种子对配色做**同族微调**(色相/饱和/明度小幅偏移,蓝还是蓝、红还是红,语义不变),这是**去指纹**机制——不同论文的图配色略有差异,不会一眼看出同一个工具产出。把 hex 抄成 `C = {"blue_main": "#0F4D92", ...}` 就绕过了微调,两篇论文的图**一模一样**。实测跨工作区色差 ΔE=11.1(肉眼可辨阈值 2.3 的 4.8 倍),而抄字面量则恒为 0。
**微调不会破坏可读性**(`plot_utils` 里有不变量兜底,300 种子验证):
- 墨色(画线/文字用)对白底对比恒 ≥3.2(WCAG 图形元素线是 3.0)
- 填充色恒保持"浅"(对比 ≤2.6),不会窜进墨色区间
- 同族相邻色(如 `blue_main` vs `blue_secondary`)色差恒 ≥9.3,画两条线分得开
- 中性灰只动明度不动色相(动了会把灰染上颜色)
- 色相锁在族内区间(如蓝锁 195–235°),不会漂成青或紫
**语义角色不变**(微调只改色值,不改角色分配):蓝 = 主方法/本文 · 绿 = 正向/达标 · 红 = 基线/对照/负向 · 灰 = 辅助/参考线
⛔ 装饰风格(网格有无 / 线宽档)由 `setup_style(palette='nature')` 按工作区种子处理,脚本不要无理由覆盖 `axes.grid` / `lines.linewidth`。图例位置不参与随机,由实际净空和标签宽高决定;可给 `auto_legend` 一个优先位置,但仍需验证实际遮挡。Nature 的身份项(左下两边框 / 刻度朝外 / 图例无框 / 白底 / 字号)恒定不变。
## Default Operating Stance
1. **Classify** the figure into one of 5 Nature page archetypes (see below)
2. **Hero panel** concept: one dominant panel + subordinate evidence panels
3. **Direct labels** over legends when categories are spatially fixed
4. **White background** for plots; black only for microscopy/imaging plates
5. **One restrained palette** per figure: neutral + signal + accent families
6. **Panel labels**: small bold lowercase (a, b, c) near top-left edge
## 5 Nature Page Archetypes
| Archetype | Layout | When to use |
|-----------|--------|-------------|
| Schematic-led composite | Wide story panel + smaller quant panels below | Method explanation + validation |
| Dark image plate | Black tiles with fluorescent channels | Microscopy, imaging, volume rendering |
| Clinical triptych | Top longitudinal, middle forest, bottom summary | Clinical/longitudinal studies |
| Dense categorical | Grid of equal panels, unified palette | Multi-metric comparisons |
| Asymmetric hero | One dominant panel spanning grid cells + small supports | Single key result + context |
## Layout Rules
- Hero panel gets visual hierarchy; support panels validate, not compete
- Panel labels: `ax.set_title('a', loc='left', pad=3, fontsize=14, fontweight='bold')`
- Tight gutters; increase spacing when dark/light modalities touch
- Prefer shared legend strip above a row over per-panel legends
- Dynamic y-axis: tighten to data range, never fixed 0–100 for narrow bands
- figsize guidance: journal-width composite (7.0–7.4, 5.5–7.8); bar panels (28–45, 6–12)
## Export Policy
**根据工作流模式选择输出格式(查看 AGENTS.md 末尾的格式指令):**
```python
import os
os.makedirs('./figures/', exist_ok=True)
fig.tight_layout(pad=0.5)
# 默认(LaTeX 模式)— 只输出 PDF(矢量、给 \includegraphics 用)
save_fig(fig, './figures/name.pdf')
# Word 模式(AGENTS.md 含「⛔ 输出格式:仅 PNG」时)— 只输出 PNG(350 DPI)
# save_fig(fig, './figures/name.png')
```
- **LaTeX 模式:只输出 PDF**(不要同时存 PNG,避免冗余)
- **Word 模式:只输出 PNG**(DPI 350 防中文糊;不要存 PDF,Word 不能嵌 PDF)
- `save_fig()` 自动加 `bbox_inches='tight'` 并 `plt.close(fig)`,无需手写
- 检查 AGENTS.md 末尾决定用哪种格式
## Workflow
### Step 1: Read data + classify figure type
Read PAPER_PLAN.md and data files. For each figure, classify into archetype and choose palette.
### Step 2: Read references + Generate scripts
**⛔ 必须在写任何绑图脚本之前,先读取以下参考文件:**
```bash
# 必读:配色方案和 helper 函数
cat references/api.md
# 必读:根据图表类型选择对应教程
cat references/tutorials.md
# 按需读取(多面板/复杂布局时)
cat references/common-patterns.md
# 按需读取(需要了解 Nature 真实页面风格时)
cat references/nature-2026-observations.md
# 按需读取(雷达图/3D/特殊图表时)
cat references/chart-types.md
```
One script per figure. Each starts with Nature rcParams setup (`setup_style(palette='nature')` or inline rcParams). Follow the patterns from `references/tutorials.md` as starting point.
### Step 3: Execute and validate
Run each script. Verify PDF output exists in `figures/`. Check:
- No `plt.title()` (captions in LaTeX only)
- Font ≥ 9pt final size
- Grayscale-distinguishable
- Panel labels present for multi-panel figures
- Colors from Nature palette, not matplotlib defaults
- ⛔ **Completeness (anti-broken图)**: y-axis has numeric ticks (NOT empty); both `set_xlabel` & `set_ylabel` present with units; if x-ticks are hidden then data is directly labeled; every `fill_between` has an accompanying `plot` line; `set_frame_on(False)` only on heatmaps; no subplot is a bare colored rectangle. **Open each PNG/PDF and confirm it is not just floating color blocks — if it is, fix and re-run before continuing.**
### 最终版面优先(数据图必须)
- 新建 figure 后调用 `set_paper_placement`,按最终栏宽反算字号;不靠超大画布整体缩小。
- 单图用 `auto_legend` 保留安全原位置,先尝试图内净空,放不下才测量顶部/右侧区域;多面板共同系列用 `consolidate_shared_legends` 生成紧凑公共区域。不固定顶部栏高度、不为短图例横向撑满,也不按随机种子选择位置。不可通过缩字、删数据或取消误差带来腾位置。
- 图例不仅不得压数据,也不得压 panel 编号、直接标签或统计框;`loc='best'` 不是通过证明。
- 重复/随机实验用 `uncertainty_band` 表示波动,图中不逐点标数。
- 带单元格数字的热力图使用 `draw_vector_heatmap`,由真实背景对比选取黑/白字;密集矩阵不标每格数值。
- 连续轴用 `dynamic_limits`,框线与网格用 `declutter_axes`。只保留必要的读数参照,不把"极简"做成缺失量纲或刻度。
- 同一 panel 的 inset、统计框、图例最多一个留在绘图区;需要两个及以上时用 GridSpec 建专用区域。
- 对数轴跨多个 decade 时次刻度只保留 2、5 两档;白色文字底板只改善对比度,不得遮住曲线。
### Step 3.4: ⛔⛔⛔ 脚本闸(必跑,退出码 0 才算过)
上面 Step 3 全是**肉眼自检**。肉眼会漏,也会累——本步骤是机器闸,**不跑不算完成**。
⛔ **这一步曾有真实事故**:某次 Nature 配色出图,`figure_check.sh` 从未在本 SKILL 里被调用过,图内躺着 30 处整段说明文字(最长一条 118 显示宽、四行、含口径注解),用户一眼看出"图表里还是有文字"。而默认配色路径(paper-figure)有三道闸,所以**只有选 Nature 配色的用户会中招**。
```bash
if [ -f _utils/figure_check.sh ]; then
bash _utils/figure_check.sh
elif [ -f skills/shared-scripts/figure_check.sh ]; then
bash skills/shared-scripts/figure_check.sh
else
echo "❌ Figure checker missing — restore the current runtime tools" >&2
false
fi
FIGURE_CHECK_RC=$?
if [ "$FIGURE_CHECK_RC" -ne 0 ]; then
echo "⛔ figure_check.sh 退出码 $FIGURE_CHECK_RC — 有 $FIGURE_CHECK_RC 处 CRITICAL 违规必须修完"
else
echo "✅ figure_check.sh 通过"
fi
```
**通过标准是退出码 0,不是"跑过了"。**
⛔⛔ **报告存档 ≠ 通过。** 那次事故里,AI 自己跑过体检、把输出存进了临时目录,然后**没有去改**就往下走了。存档只是留痕,闸的意义在于**改到零**。看到违规必须:① 打开报告指出的文件与行号 ② 逐条改 ③ **重跑** ④ 循环到退出码 0。
本闸包含的检查里,Nature 配色最容易踩的两条:
| 报告标签 | 含义 | 怎么改 |
|---|---|---|
| `图内文字超标` | 把说明/结论画进了图 | 见下面 Step 3.45 的两档规则 |
| `多行文字框压在绘图区` | 文字框盖住数据 | 先按上一条精简文字;仍需要就移到轴外 |
### Final file check(每张 PDF 生成后立即跑,不调用模型)
After generating each PDF, run `python _utils/figure_pdf_quality_check.py figures --paper paper --only fig_xxx.pdf`. Repair only the identified figure. Warnings about complex backgrounds mean visual review is needed, not automatic failure or a free pass. The workflow also checks the final PDF; do not publish a malformed image just because its script exited zero. 全部图片完成后再整批复核一次:`python _utils/figure_pdf_quality_check.py figures --paper paper`。
### Step 3.45: ⛔⛔ 图内文字两档规则(Nature 语境)
Nature 的图**信息密度靠图形承载,不靠图内文字**。正刊图里出现的中文/英文短语几乎只有两类:给线/点/区域起名,和标数值。**成段的说明、口径、结论一律在 caption 与正文里**——这既是期刊规范,也是防遮挡的根本手段(文字越少越不会撞)。
**放行**(`figure_check.sh` 不会拦):
- 纯数值:`8188.06`、`45%`、`n=30`、`1007 张`、`$q^*$=0.47`
- **短锚点标签**(给东西起名,≤45 显示宽 / ≤3 行 / 汉字 ≤12,每行"有数字"或"汉字 ≤5"):`预算绑定区`、`肘部拐点 $k$=8`、`ROI 下限 3.0`、`膝点 $B^\ast\approx$3.2 万元`、`Youden: J=0.42, θ*=0.31`、`加权 R²=0.87 / RMSE=1.2`
- 纯公式:`$\tau(d)=p(d)-p_0$`
- panel 标号:`a` / `(b)`
**违规**(必须移出图外):
- 结论/因果:`全区间贴死下限 → ROI 始终绑定`、`因此最优解取 8 个点`、`加预算无用`
- 导读:`题给 $B$=500000 元 → 在图右`(行首箭头、或箭头后紧跟中文)
- 成段说明:`面额 5→50 涨 10 倍⏎人均增量 GMV 仅涨 2.3 倍⏎预算占用却涨 18.3 倍⏎(全池均值口径)`
- 多标签堆叠:`最优解 · 收敛区间 · 预算上限 3271 元`(拆成多个 annotate 或移进正文)
**三条改法**(按优先级):
1. **结论换成能推出结论的数值**——别写"始终贴死下限",写 `ROI 3.000–3.016`,让读者自己看出来,结论进 caption。这是最好的改法:图更硬,字更少。
2. **解释图上元素 → 走图例**——"双箭头 = 净提升"这种话的正确出口是 `label=` 进 legend,不是 `ax.text`。图内只留纯公式。
3. **口径/条件 → 进 caption**——"全池均值口径"、"该档实验样本仅 530 条"这类限定语,全部写在 LaTeX caption 里。
⛔ **不要因为闸报违规就把标签删光**——阈值线不说明是什么线、最优点不标是最优点,图就没法读了(这也是违规,Step 3 的 anti-broken 检查会抓)。删的是**说明和结论**,留的是**名字和数值**。
### Step 3.5: 数据图视觉质检(可选,默认关 · 仅当用户在高级选项开启时才跑)
⛔ **这一步默认不执行**。只有工作区 AGENTS.md 含 `MH_DATA_FIG_VISION=1` 标记(用户在前端「高级选项」开启了「数据图视觉质检」)时才跑。它会对每张数据图由宿主独立窗口的视觉模型看图,检查坐标轴标签截断 / 图例压数据 / 刻度重叠等**肉眼硬伤**(Step 3 的静态检查抓不到这些渲染层问题)。**每张图每轮都占一次独立窗口审核轮次**,所以默认关。
先跑下面这段**检测脚本**,它会对每张数据图生成独立窗口审核任务卡/收集审核结论,并记进独立账本 `_tmp/datafig_vision_*.txt`:
```bash
# ⛔ 门 1:默认关。AGENTS.md 无 MH_DATA_FIG_VISION=1 标记就整段跳过(一个字不打,静默)
if ! grep -q 'MH_DATA_FIG_VISION=1' AGENTS.md 2>/dev/null; then
: # 用户没开数据图视觉质检 → 跳过(默认行为,省额度)
# ⛔ 门 2:快速模式让位。省额度优先,即使开了数据图 vision 也跳过
elif grep -q 'MH_FAST_MODE=1' AGENTS.md 2>/dev/null; then
echo "⚡ 快速模式:跳过数据图视觉质检(省额度)"
else
mkdir -p _tmp
PYTHON=""; for _c in "$MH_PYTHON" python python3; do [ -z "$_c" ] && continue; if $_c -c "import sys" >/dev/null 2>&1; then PYTHON="$_c"; break; fi; done; [ -z "$PYTHON" ] && PYTHON=python
# 定位数据图 vision 脚本:_utils/ 优先,兜底 $MH_TOOLS_DIR,再兜底 tools/
DFV=""
for _p in "$MH_TOOLS_DIR/data_fig_vision_check.py" "tools/data_fig_vision_check.py"; do
[ -n "$_p" ] && [ -f "$_p" ] && { DFV="$_p"; break; }
done
# ⛔ 收集数据图:靠【产物来源】判定,不靠前缀猜(前缀既会误伤真数据图 fig_error_dist,
# 又会误收流程图 → 用数据图 PROMPT 检流程图/插画会得到牛头不对马嘴的反馈、误导修图)。
# 数据图 = matplotlib gen_fig 脚本产的;流程/架构图有同名 .drawio(归 paper-figure-drawio 的
# vision,已单独质检,本步不重复检);TikZ 有同名 .tex 含 tikzpicture;GPT Image 插画是
# fig_scene*/fig_gptimg*(AI 生成的场景图,非数据图)。判据:
# 正向铁证:存在同名 gen_fig 脚本(规范 one gen_fig script per figure)→ 一定是数据图
# 负向兜底:无 .drawio、无 tikz .tex、非 GPT 前缀 → 可能是「一脚本产多图」的数据图,也检
DF_LIST=""
for pdf in figures/fig_*.pdf; do
[ -f "$pdf" ] || continue
bn=$(basename "$pdf" .pdf)
# 已判过 PASS 的不再调(复核循环省额度)
grep -q "^${bn} PASS" _tmp/datafig_vision_passed.txt 2>/dev/null && continue
is_data=0
if [ -f "figures/gen_${bn}.py" ]; then
is_data=1 # 正向:有同名 gen_fig 脚本 = 铁定数据图
else
# 兜底:排除 drawio 流程图 / TikZ / GPT Image 插画,其余当数据图(覆盖一脚本多图)
_skip=0
[ -f "figures/${bn}.drawio" ] && _skip=1
[ -f "figures/${bn}.tex" ] && grep -q '\\begin{tikzpicture}' "figures/${bn}.tex" 2>/dev/null && _skip=1
case "$bn" in fig_scene*|fig_gptimg*) _skip=1 ;; esac
[ "$_skip" = "0" ] && is_data=1
fi
[ "$is_data" = "1" ] && DF_LIST="$DF_LIST $pdf"
done
if [ -z "$DF_LIST" ]; then
echo "ℹ 数据图视觉质检:无待检数据图(或都已 PASS)"
elif [ -z "$DFV" ]; then
echo "🟥 开了数据图视觉质检但找不到 data_fig_vision_check.py(_utils/ 与 tools/ 均无)——本轮跳过,不阻断"
for pdf in $DF_LIST; do echo "$(basename "$pdf" .pdf) (找不到 data_fig_vision_check.py)" >> _tmp/datafig_vision_skipped.txt; done
else
for pdf in $DF_LIST; do
bn=$(basename "$pdf" .pdf)
echo "=== 数据图视觉质检: $bn ==="
PNG_OK=0
# PyMuPDF(fitz) 优先:纯 wheel、不依赖 poppler,打包 runtime 必有
$PYTHON -c "
import fitz
d=fitz.open('$pdf'); d[0].get_pixmap(matrix=fitz.Matrix(200/72,200/72)).save('_tmp/${bn}_dfv.png')
" 2>/dev/null && [ -f "_tmp/${bn}_dfv.png" ] && PNG_OK=1
if [ "$PNG_OK" = "0" ] && command -v pdftoppm >/dev/null 2>&1; then
pdftoppm -png -r 200 -singlefile "$pdf" "_tmp/${bn}_dfv" && PNG_OK=1
fi
if [ "$PNG_OK" = "0" ] && $PYTHON -c "from pdf2image import convert_from_path" 2>/dev/null; then
$PYTHON -c "
from pdf2image import convert_from_path
convert_from_path('$pdf', dpi=200, first_page=1, last_page=1)[0].save('_tmp/${bn}_dfv.png','PNG')
" 2>/dev/null && [ -f "_tmp/${bn}_dfv.png" ] && PNG_OK=1
fi
[ "$PNG_OK" = "0" ] && { echo "🟥 $bn: PDF→PNG 均失败,本图未审(不阻断)"; echo "$bn (PDF→PNG 转换失败)" >> _tmp/datafig_vision_skipped.txt; continue; }
DVOUT=$($PYTHON "$DFV" "_tmp/${bn}_dfv.png" 2>&1); DVEXIT=$?
echo "$DVOUT"
if [ "$DVEXIT" -eq 0 ]; then
echo "✅ $bn 视觉通过"; echo "$bn PASS" >> _tmp/datafig_vision_passed.txt
elif [ "$DVEXIT" -eq 2 ]; then
echo "⚠ 独立窗口证据未就绪,跳过 $bn(不阻断)"; echo "$bn (独立窗口证据未就绪/未回写 verdict)" >> _tmp/datafig_vision_skipped.txt
else
# DVEXIT=1:有硬伤 → 记 pending,交给上面散文里的修复循环(AI 改脚本重跑后重跑本检测块复核)
echo "⛔ $bn 有视觉硬伤(见上),按修复循环改 gen_fig 脚本重跑"
echo "$bn" >> _tmp/datafig_vision_pending.txt
fi
rm -f "_tmp/${bn}_dfv.png" # 临时 PNG 只喂 vision 用完即弃,检完即删避免 _tmp/ 堆积
done
# 汇总(供 AI 判断还剩几张要修):pending.txt 跨轮累积,需剔除已 PASS 的图才是真待修数
_pend=0
if [ -f _tmp/datafig_vision_pending.txt ]; then
for _b in $(sort -u _tmp/datafig_vision_pending.txt); do
grep -q "^${_b} PASS" _tmp/datafig_vision_passed.txt 2>/dev/null || _pend=$((_pend+1))
done
fi
echo "=== 数据图视觉质检小结:真待修 $_pend 张(已 PASS 的不计;passed/skipped 见 _tmp/datafig_vision_*.txt)==="
echo " (待修的图改完 gen_fig 脚本、重跑出图后,重新执行本检测块复核;最多 3 轮,之后警告不阻断)"
fi
fi
```
**⛔ 修复循环(AI 执行,最多 3 轮,不阻断出稿)**:
上面脚本若打印出某张图的 `ISSUE ...`,你必须逐张修复——数据图的修复是**改 `gen_fig_xxx.py` 的绘图代码**(不是改 LaTeX):
1. 用 Read 读 vision 反馈里点名的那张图对应的 `figures/gen_fig_xxx.py`
2. 按反馈用 Edit 改:标签被截断 → 调 `figsize`/`fontsize`(`save_fig` 已带 `bbox_inches='tight'`);图例压数据 → 改 `legend(loc=...)` 或 `bbox_to_anchor` 移到画布外;刻度重叠 → `plt.xticks(rotation=30, ha='right')` 或减少刻度数;子图挤压 → `fig.tight_layout()` 或调 `figsize`
3. 重跑该脚本:`$PYTHON figures/gen_fig_xxx.py`,确认新 PDF 生成
4. **重新执行上面的检测脚本复核**(它只对还没 PASS 的图再调 vision)
5. 每张图最多修 3 轮。3 轮后小结里「真待修」仍 > 0 → 这些图就是没修好的,**警告即可、不阻断**,直接继续 Step 4(用户会自己复核)
⛔ **绝不能因为数据图 vision 没修好就卡在这里不往下走**——这是可选增值检查,警告即可。API 不可用 / 转图失败等环境问题一律记 skipped 跳过,同样不阻断。
### Step 4: Generate latex_includes.tex
Include all figures with `[H]` float specifier and English captions.
### Step 5: ⛔ FIGURE_MANIFEST 对账(按规划数量逐张核对,必跑)
**PAPER_PLAN.md 里规划了几张数据图,本步骤就必须产出几张。** 防止 context 中途爆掉只画了 1-2 张就退出的死循环 bug。
产出结构、存在性和最低完整性由 `finish` 按模板中的 `output_contract` 自动核验;修复返回的具体问题,不复制执行验证脚本。
## 执行与产出
使用当前执行会话完成本步工作;产物路径按当前步骤合同。程序采集真实操作、输入输出、版本与运行清单,模型只负责实质成果和领域质量。
建议额外记录:每张图的数据源、生成脚本、colormap、参数。图表溯源门禁 figure_provenance 要求图有来源证据。
## Key Rules
- ⛔ Never use `svg.fonttype = 'path'` — breaks text editability
- ⛔ No `plt.title()` — captions belong in LaTeX
- ⛔ No matplotlib default colors — always use Nature palette
- ⛔ No grid lines by default — sparse y-ticks guide the eye
- Active voice in axis labels; concise legend entries
- For ablation: single color with varying alpha (0.2–1.0)
- Error bars: `elinewidth=2, capthick=2, capsize=10`
- Heatmap text contrast: white on dark cells, black on light cells
## Related Files
| File | Open when |
|------|-----------|
| [references/api.md](references/api.md) | Palette constants, helper function signatures, validation rules |
| [references/design-theory.md](references/design-theory.md) | Typography, color theory, layout rationale |
| [references/chart-types.md](references/chart-types.md) | Radar, 3D sphere, fill_between, scatter patterns |
| [references/common-patterns.md](references/common-patterns.md) | Ultra-wide panels, legend-only axes, print-safe bars |
| [references/nature-2026-observations.md](references/nature-2026-observations.md) | Real Nature page archetypes from 2026 issues |
| [references/tutorials.md](references/tutorials.md) | End-to-end walkthroughs: bars, trends, heatmaps |
| `_utils/plot_utils.py` | Shared plotting infrastructure |
---
## modex-3 增补节(2026-09-10 同源对照吸收)
> 来源:Modex v3 paper-figure-nature。本节为本仓原版未覆盖的强制样式布局合同,与本仓上方规范并行生效。
## Mandatory style and layout contract
Call `setup_style(palette='nature')` before creating figures; call `set_paper_placement` immediately after creation. This project's final-print minimum is **8 pt**, default target **8.25 pt**. These are Chinese-paper product defaults, not a claim that all Nature journals require this exact size.
Plan the actual insertion width, not a fixed 5.5-inch minimum or an oversized source canvas. Reserve separate GridSpec rows/columns for shared legends, colorbars and dense numeric labels. Increase height or split related panels when necessary; never shrink essential labels below the final-print minimum. Keep only short names and necessary values inside plots; explanations belong in the caption/body according to the project format.
Use the shared font tiers. Small local overrides are allowed only if final print size remains valid. Keep Chinese fallback fonts; do not overwrite them with an Arial-only list.
### 推荐:把样板抽进 `figures/_figcommon.py`(一次写好,21 个脚本共用)
图多了以后每个 `gen_fig_*.py` 顶部都要 `sys.path` + `setup_style` + 配色字典 +
存图收尾,抄 20 遍必然抄歪(漏一处 `setup_style` 就是一张默认蓝的图)。
**建议在 `figures/` 下建一个公用模块**,各脚本 `from _figcommon import *`:
```python
# figures/_figcommon.py — Nature 风格公用设施
import os, sys, json
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
import matplotlib
matplotlib.use('Agg')
from _utils.plot_utils import setup_style, save_fig, nature_palette, nature_markers
setup_style(palette='nature') # ← 全局只调一次(含版式随机)
import matplotlib.pyplot as plt
import numpy as np
FIG_DIR = os.path.join(ROOT, 'figures')
OUT_DIR = os.path.join(ROOT, 'output')
# ⛔ 取色调函数,不要抄 hex 字面量 —— 抄了就绕过去指纹微调(见 Nature Color Palette 一节)
C = nature_palette() # 15 键语义字典(blue_main / red_strong / green_3 / …)
M = nature_markers() # marker 顺序(按工作区轮转)
def load(name):
"""读工作区 JSON(figures/ 或 output/)。⛔ 找不到就 raise,不许兜底成空值 ——
数据缺失必须当场崩,静默返回 {} 会让图上印出 0 而没人发现。"""
for d in (FIG_DIR, OUT_DIR):
p = os.path.join(d, name)
if os.path.isfile(p):
return json.load(open(p, encoding='utf-8'))
raise FileNotFoundError(name)
def finish(fig, path):
"""统一收尾:存 PDF(save_fig 自动 close)。"""
save_fig(fig, path)
```
⛔ **只放在 `figures/` 目录下、只放"样板"**(样式初始化、配色、读数据、存图)。
`figure_check.sh` 会展开**同目录**的本地 import 来做存在性检查,所以
`setup_style` / `save_fig` 写在这里**不会**被误判成"缺失"。放到别处(如 `_utils/`)
则不在展开范围内,闸会报 CRITICAL。
⛔⛔ **文件名不能以 `gen_fig` 开头** —— 必须叫 `_figcommon.py`(或任何不以 `gen_fig`
起头的名字)。原因:闸和"出图数量对账"都用 `figures/gen_fig*.py` 这个 glob 找出图
脚本,若样板模块叫 `gen_fig_common.py`,它会被当成一张图的脚本:脚本数比 PDF 数多
一个,"所有脚本都产出了 PDF"这条检查**恒定失败**且无法修复。前导下划线还有个额外
好处 —— 一眼看出它是内部模块不是出图脚本。
⛔ **展开只做一层、只认同目录**:`_figcommon.py` 自己再 `from _base import *`
的第二层不会被展开(那层里的 `setup_style` 闸看不见 → 报 CRITICAL)。
样板就一层,别套娃。
⛔ `load()` 里那句 `raise FileNotFoundError` 必须留着。若改成
`return {}` 或 `.get(name, {})`,键写错时不会报错,图会照画、数值全是 0 或空 —— 而
`figure_check.sh` 只查语法与文字规范、查不出"数值是不是真算出来的",这种错会一路
流进论文。**数据缺失当场崩,比静默出错好得多。**
### Integration with plot_utils.py
Use the prepared shared runtime, including its save-time checks:
```python
from _utils.plot_utils import setup_style, save_fig, set_paper_placement
setup_style(palette='nature')
```
If the runtime is missing, report the dependency failure; do not replace it with inline rcParams or catch a quality failure and retry via an unguarded exporter.
Files in this skill
- SKILL.md
- references/UPSTREAM.md
- references/api.md
- references/chart-types.md
- references/common-patterns.md
- references/design-theory.md
- references/nature-2026-observations.md
- references/tutorials.md
Attribution
Comments
Loading comments…