236 lines
10 KiB
Markdown
236 lines
10 KiB
Markdown
---
|
||
name: plot_pub
|
||
description: 出版级 matplotlib 绘图(论文 / 报告 / 申报书用,中文字体 + viridis 配色 + dpi 设定一键到位)。✅ 触发:用户要画 XRD 谱、TG-DSC 曲线、应力-应变曲线、SEM 标注、多 panel 学术图、投稿级 / Nature 级复合图、要 SVG/PDF 矢量图给论文。⛔ 不触发:PPT 内嵌图(走 `ppt` skill 的 matplotlib 配图流程);只要快速看一眼数据(直接 `df.plot()` 即可,不用本 skill)。
|
||
---
|
||
|
||
# plot_pub
|
||
|
||
服务建材院的论文 / 申报书 / 调研报告出图。**核心是 `apply_pub_style()` 一键设置 rcParams**(中文字体、viridis 默认、字号、dpi、矢量优先),避免每次手动设置十几行还忘配中文字体。
|
||
|
||
## 何时用
|
||
|
||
- 写论文要投 *Cement and Concrete Research* / *J. Am. Ceram. Soc.* / *Construction and Building Materials* 等期刊
|
||
- 报告 / 申报书要中文标题 + 中文图例的高 dpi 图
|
||
- 多组样本数据对比(XRD 多谱叠图、强度发展曲线、温度场)
|
||
- 双 Y 轴(TG-DSC 是典型)
|
||
|
||
## 何时不用
|
||
|
||
- PPT 里要嵌图 → 走 `skills/ppt`(那边已经有配色 / 尺寸规范)
|
||
- 交互式探查数据 → 用 plotly / 直接 jupyter
|
||
- 只要看趋势不出版 → `df.plot()` 一行搞定,不用本 skill 的 ceremony
|
||
|
||
## 准备
|
||
|
||
```python
|
||
from skills.plot_pub.style import apply_pub_style
|
||
import matplotlib.pyplot as plt
|
||
import numpy as np
|
||
|
||
apply_pub_style() # 调一次,后面所有 plt 都按这个 style
|
||
```
|
||
|
||
## `apply_pub_style()` 参数
|
||
|
||
| 参数 | 默认 | 说明 |
|
||
|---|---|---|
|
||
| `chinese` | `True` | 加载 SimHei / Microsoft YaHei,中文不显示方块 |
|
||
| `dpi` | `150` | 屏幕显示 dpi;保存图用 `savefig(..., dpi=300)` 单独控 |
|
||
| `font_size` | `10` | 论文双栏图标准字号 |
|
||
| `linewidth` | `1.2` | 线粗,出版图细些显精致 |
|
||
| `cmap` | `"viridis"` | 默认 colormap,**审稿人不会喷**(jet 现在是雷区) |
|
||
|
||
## 典型工作流
|
||
|
||
### A. XRD pattern(多相叠图 + 标峰)
|
||
|
||
```python
|
||
from skills.plot_pub.style import apply_pub_style
|
||
import matplotlib.pyplot as plt
|
||
import numpy as np
|
||
|
||
apply_pub_style()
|
||
|
||
fig, ax = plt.subplots(figsize=(6, 4))
|
||
|
||
# 实测谱
|
||
ax.plot(two_theta_exp, intensity_exp, "k-", lw=1.0, label="实测")
|
||
|
||
# 理论谱(各物相,用 pymatgen XRDCalculator 算的)
|
||
for phase, pattern in pymatgen_patterns.items():
|
||
ax.vlines(pattern.x, 0, pattern.y / pattern.y.max() * intensity_exp.max() * 0.3,
|
||
label=phase, alpha=0.7)
|
||
|
||
ax.set_xlabel(r"$2\theta$ / °")
|
||
ax.set_ylabel("强度 / a.u.")
|
||
ax.set_xlim(5, 80)
|
||
ax.legend(frameon=False, loc="upper right")
|
||
fig.tight_layout()
|
||
fig.savefig("xrd_compare.pdf", bbox_inches="tight") # PDF 矢量,期刊偏好
|
||
fig.savefig("xrd_compare.png", dpi=300, bbox_inches="tight") # PNG 给 PPT
|
||
plt.close(fig) # 关键!run_python 多任务下不关会堆 figure 泄漏内存
|
||
```
|
||
|
||
### B. TG-DSC 双 Y 轴
|
||
|
||
```python
|
||
fig, ax_tg = plt.subplots(figsize=(6, 4))
|
||
ax_dsc = ax_tg.twinx()
|
||
|
||
ax_tg.plot(T, tg_mass, "b-", lw=1.2, label="TG")
|
||
ax_dsc.plot(T, dsc_flow, "r-", lw=1.2, label="DSC")
|
||
|
||
ax_tg.set_xlabel("温度 / °C")
|
||
ax_tg.set_ylabel("质量 / %", color="b")
|
||
ax_dsc.set_ylabel("热流 / mW·mg$^{-1}$", color="r")
|
||
ax_tg.tick_params(axis="y", labelcolor="b")
|
||
ax_dsc.tick_params(axis="y", labelcolor="r")
|
||
|
||
# 合并两个 ax 的 legend
|
||
lines1, labels1 = ax_tg.get_legend_handles_labels()
|
||
lines2, labels2 = ax_dsc.get_legend_handles_labels()
|
||
ax_tg.legend(lines1 + lines2, labels1 + labels2, loc="best", frameon=False)
|
||
|
||
fig.tight_layout()
|
||
fig.savefig("tg_dsc.pdf", bbox_inches="tight")
|
||
plt.close(fig)
|
||
```
|
||
|
||
### C. 多组样本对比(强度发展曲线)
|
||
|
||
```python
|
||
fig, ax = plt.subplots(figsize=(6, 4))
|
||
ages = [3, 7, 14, 28, 56, 90]
|
||
|
||
# 用 viridis 给多个样本上色,顺序色觉友好
|
||
import matplotlib.cm as cm
|
||
colors = cm.viridis(np.linspace(0.1, 0.9, len(samples)))
|
||
|
||
for (label, strengths), c in zip(samples.items(), colors):
|
||
ax.plot(ages, strengths, "o-", color=c, lw=1.2, ms=5, label=label)
|
||
|
||
ax.set_xlabel("龄期 / d")
|
||
ax.set_ylabel("抗压强度 / MPa")
|
||
ax.set_xscale("log") # 龄期用对数刻度更清楚
|
||
ax.legend(frameon=False)
|
||
ax.grid(True, ls="--", alpha=0.3)
|
||
|
||
fig.tight_layout()
|
||
fig.savefig("strength_dev.pdf", bbox_inches="tight")
|
||
plt.close(fig)
|
||
```
|
||
|
||
### D. 多 panel(2×2 figure,论文 figure 1 标配)
|
||
|
||
```python
|
||
fig, axes = plt.subplots(2, 2, figsize=(10, 7), constrained_layout=True)
|
||
|
||
axes[0, 0].plot(...) # (a) XRD
|
||
axes[0, 1].plot(...) # (b) FTIR
|
||
axes[1, 0].plot(...) # (c) TG
|
||
axes[1, 1].plot(...) # (d) 强度
|
||
|
||
# 子图编号(论文图 1 标准做法)
|
||
for ax, letter in zip(axes.flat, "abcd"):
|
||
ax.text(0.02, 0.95, f"({letter})", transform=ax.transAxes,
|
||
fontweight="bold", va="top")
|
||
|
||
fig.savefig("fig1_characterization.pdf", bbox_inches="tight")
|
||
plt.close(fig)
|
||
```
|
||
|
||
## 投稿级多 panel 复合图(Nature-grade composite)
|
||
|
||
要投高影响期刊、要一张"封面级"的多 panel figure 1 时,光排版正确不够 —— 还要**让图讲一个故事**。下面这套设计纪律移植自 `nature-figure` skill(MIT,[github.com/Yuan1z0825/nature-skills](https://github.com/Yuan1z0825/nature-skills)),砍掉其 R / 单细胞 / 在体生物那一套,只留可迁移的部分,适配建材领域。
|
||
|
||
### 动手前:五点 figure contract
|
||
|
||
画复合图前先在心里(或跟用户)把这五条对齐,**先想清楚再下笔**,避免画完六张图才发现讲不成一个故事:
|
||
|
||
1. **核心结论**:这张图要捍卫的**一句话**论断是什么?(例:"复合掺合料把 28d 强度提升 18% 且不牺牲流动度")
|
||
2. **证据链**:每个 panel 对应**唯一**一条证据,删掉冗余 panel —— 同一份数据别用两种图形再画一遍,同一组指标别排两次序
|
||
3. **图原型**:归类为 ① 定量网格(全是量化对比图)② schematic 主导(机理图 + 验证小图)③ 图像板 + 定量(SEM/光学 plate + 旁证曲线)④ 非对称混合;先定原型再排版
|
||
4. **后端**:本 skill 纯 matplotlib(Python),所有 panel / 预览 / 导出统一一套后端出
|
||
5. **期刊契约**:开画前定好尺寸(单栏 ~89mm / 双栏 ~183mm)、可编辑矢量(已由 `apply_pub_style` 设 `svg.fonttype='none'`)、source data、统计标注、导出格式
|
||
|
||
> 一句话原则:**图为科学逻辑服务**,美化 / 套模板 / 复杂排版都是次要的。
|
||
|
||
### 语义配色(比 viridis 更进一步)
|
||
|
||
单组数据用 viridis 没问题;但**多组对比 / 方法 vs 基线**时,颜色要承载语义,而不是随机区分。`style.py` 已内置一张语义色表:
|
||
|
||
```python
|
||
from skills.plot_pub.style import apply_pub_style, SEMANTIC_COLORS, clean_spines, ablation_alphas
|
||
|
||
apply_pub_style()
|
||
|
||
# 蓝=本方法/主角 绿=提升 红=baseline/对照 灰=参照 橙=少量强调
|
||
ax.bar(x0, y_base, color=SEMANTIC_COLORS["baseline"], label="基准配方")
|
||
ax.bar(x1, y_method, color=SEMANTIC_COLORS["method"], label="复合掺合料")
|
||
```
|
||
|
||
原则:**family consistency beats maximal hue separation** —— 相关的几条基线归一个色系,本方法的几个变体归另一个色系,别把颜色撒得到处都是。消融 / 梯度对比用**同色变 alpha**(0.25→1.0)而不是换色相:
|
||
|
||
```python
|
||
colors = ablation_alphas(len(掺量梯度)) # 同蓝色由浅到深
|
||
for (label, y), c in zip(掺量梯度.items(), colors):
|
||
ax.plot(x, y, color=c, label=label)
|
||
```
|
||
|
||
### 信息架构 + spine 纪律
|
||
|
||
- 多 panel 要**作为一个故事读**,不是六张互不相干的图拼一起
|
||
- 有 schematic / hero panel 时给它视觉主导,旁边的验证小图别喧宾夺主
|
||
- 每个 panel 调 `clean_spines(ax)` —— 只留左 + 下边框,去掉上 + 右,信噪比立刻上来
|
||
- legend 无框(`apply_pub_style` 已设)、网格抑制(只在需要时留稀疏 y 网格)
|
||
|
||
```python
|
||
fig, axes = plt.subplots(1, 3, figsize=(7.2, 2.6), constrained_layout=True) # 双栏宽 ~183mm
|
||
|
||
axes[0].bar(...) # (a) 概览:堆叠/分组柱
|
||
axes[1].imshow(...) # (b) 偏差:z-score 热图
|
||
axes[2].scatter(...) # (c) 关系:散点/气泡 —— 三级递进,各答一个不同的科学问题
|
||
|
||
for ax in axes:
|
||
clean_spines(ax)
|
||
for ax, letter in zip(axes, "abc"):
|
||
ax.text(-0.12, 1.02, f"({letter})", transform=ax.transAxes, fontweight="bold", va="bottom")
|
||
|
||
fig.savefig("fig1_composite.svg", bbox_inches="tight") # SVG 主格式,文字可编辑
|
||
fig.savefig("fig1_composite.png", dpi=300, bbox_inches="tight") # PNG 仅作 raster 预览
|
||
plt.close(fig)
|
||
```
|
||
|
||
### 导出纪律
|
||
|
||
- **SVG 为主格式**(文字可编辑,编辑部/作者能改),PDF 矢量并行,PNG 300dpi 仅作预览
|
||
- 别拿 PNG 当投稿正图(见反模式 #6)
|
||
|
||
## 中文字体配置(Windows 注意)
|
||
|
||
`apply_pub_style(chinese=True)` 默认按以下顺序找字体:
|
||
1. `SimHei`(Windows 自带,黑体)
|
||
2. `Microsoft YaHei`(Windows 自带,雅黑)
|
||
3. `WenQuanYi Micro Hei`(Linux 兜底)
|
||
|
||
都没有 → 退回 DejaVu Sans,中文显示方块。装字体方式:Windows 控制面板 → 字体,丢 .ttf 进去。
|
||
|
||
**LaTeX 公式渲染**:matplotlib 用 `$...$` 自动走 mathtext,中英文混排正常。要更精细的 LaTeX(自定义 macro)就 `plt.rcParams["text.usetex"] = True`,但要本地装 TeX Live。
|
||
|
||
## 反模式
|
||
|
||
1. **用 pyplot 状态机不用 OO 接口** —— 写 `plt.plot(); plt.xlabel(...)` 一时爽,多 figure 时变量乱;一律 `fig, ax = plt.subplots()`
|
||
2. **不 close figure** —— `run_python` 跑多次会堆 figure 内存涨,必须 `plt.close(fig)` 收尾
|
||
3. **用 jet colormap** —— 色觉障碍审稿人会喷,近年期刊都偏 viridis / plasma / cividis
|
||
4. **保存图不 `bbox_inches="tight"`** —— legend / 标题被裁掉,反复看图反复改尺寸
|
||
5. **同图 dpi 跟 figsize 不匹配** —— figsize=(10,7) dpi=300 给 PPT 嵌入太大,字会很小;论文图 figsize=(6,4) 双栏刚好
|
||
6. **PNG 给论文** —— 期刊要矢量,PDF / SVG / EPS 优先;PNG 只给 PPT / 网页
|
||
7. **中文显示方块还坚持跑** —— `apply_pub_style()` 失败会 warn,看到 warning 就停下查字体
|
||
8. **legend 框线 / 灰底** —— 出版图 `legend(frameon=False)`,干净
|
||
9. **轴标签没单位** —— `"温度"` 是错的,`"温度 / °C"` 才对(SI 推荐 `/` 分隔)
|
||
10. **`ax.set_xticks([1,2,3,...])` 手动写一堆** —— matplotlib 自动 tick 已经足够,手动覆盖只在特殊场景(对数轴 / 离散类别轴)
|
||
|
||
## 依赖
|
||
|
||
matplotlib 已经在 `requirements.txt`(>=3.8.0)。本 skill 无新增依赖。
|