From ec27fcae3ef65a76a24bb6092fa18c435d5ea893 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 15 Jun 2026 16:54:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(skill):=20plot=5Fpub=20=E5=90=B8=E6=94=B6?= =?UTF-8?q?=20nature-figure=20=E6=8A=95=E7=A8=BF=E7=BA=A7=E5=A4=8D?= =?UTF-8?q?=E5=90=88=E5=9B=BE=E8=AE=BE=E8=AE=A1=E7=BA=AA=E5=BE=8B=20+=20bu?= =?UTF-8?q?mp=200.12.15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调研 nature-figure skill(MIT)后只迁移可复用设计 IP,不整包移植 (避免与 plot_pub 重叠、R/生物内容不适配、多文件结构破坏单 SKILL.md 约定)。 - style.py: 补 svg.fonttype='none'(原只设 PDF Type 42,漏了 SVG 可编辑) + SEMANTIC_COLORS 语义色表 + clean_spines() + ablation_alphas() - SKILL.md: 新增「投稿级多 panel 复合图」段(五点 figure contract / 语义配色 / 信息架构 + spine 纪律 / 导出纪律),示例改建材领域 - SKILL_LIST.md / PROGRESS.md 同步;纯 Python 零新依赖,保留中文字体 Co-Authored-By: Claude Opus 4.8 (1M context) --- PROGRESS.md | 7 +++- SKILL_LIST.md | 6 ++-- core/__init__.py | 2 +- skills/plot_pub/SKILL.md | 69 +++++++++++++++++++++++++++++++++++++++- skills/plot_pub/style.py | 57 +++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 5 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index 4cc3508..7e0eb78 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-06-15(文件预览修滚动穿透 + 图片 Ctrl+滚轮缩放) +最后更新:2026-06-15(plot_pub 吸收 nature-figure 投稿级复合图设计纪律) --- @@ -21,6 +21,11 @@ ## 已完成关键能力 +### 2026-06-15 / plot_pub 吸收 nature-figure 投稿级复合图设计纪律 + +- 联网调研 `nature-figure` skill(MIT,github.com/Yuan1z0825/nature-skills):双层 manifest 路由 + Python/R 双后端 + 生物医学 gallery。判断不整包移植 —— 与已有 plot_pub 高度重叠、R/单细胞/在体内容跟建材院领域不沾边、多文件结构破坏 zcbot 单 SKILL.md 约定。 +- 只迁移可复用的设计 IP,折进 `skills/plot_pub`:`style.py` 补 `svg.fonttype='none'`(可编辑矢量,原本只设了 PDF Type 42 漏了 SVG)+ `SEMANTIC_COLORS` 语义色表 + `clean_spines()` spine 纪律 + `ablation_alphas()` 同色变 alpha;`SKILL.md` 新增「投稿级多 panel 复合图」段(五点 figure contract / 语义配色 / 信息架构 / 导出纪律),示例全改建材领域。纯 Python、零新依赖、保留中文字体。bump 0.12.14 → 0.12.15。 + ### 2026-06-15 / 消息分页:尾部窗口 + 向上滚动加载更早(切 task 提速) - 痛点:切 task 卡顿 —— `/v1/tasks/{id}/messages` 无分页一次拉全量,前端 `renderMessages` 又对每条跑 markdown+highlight+media 全量渲 DOM,消息多时两段成本都线性涨。 diff --git a/SKILL_LIST.md b/SKILL_LIST.md index c26e064..89eaae0 100644 --- a/SKILL_LIST.md +++ b/SKILL_LIST.md @@ -1,7 +1,7 @@ # zcbot Skill 清单 服务对象:中国建筑材料科学研究总院 —— 无机非金属材料 R&D(水泥 / 混凝土 / 玻璃 / 陶瓷 / 耐火 / 新型建材) -最后更新:2026-06-11 +最后更新:2026-06-15 Skill 总数:15 zcbot 的"skill"是一份可加载的工作流脚本(`skills//SKILL.md` + 配套 templates / scripts / Python helper),模型在识别用户意图后挂载对应 skill,按其内置的阶段化流程产出可交付物。本文档面向**使用方 / 协作方**,按"做什么、什么时候用、什么时候别用、典型产物"组织。 @@ -19,7 +19,7 @@ zcbot 的"skill"是一份可加载的工作流脚本(`skills//SKILL.md` + | 科研写作 | [patent](#patent) | 写发明专利技术交底书(供代理师转写) | | 科研写作 | [review](#review) | 审稿 / 润色 / 校对(中英文,长文档分段深审) | | 演示出图 | [ppt](#ppt) | 生成 PowerPoint 演示稿(商务红主题,大纲对齐后一脚本整建) | -| 演示出图 | [plot_pub](#plot_pub) | 出版级 matplotlib 学术图(中文 + viridis + 矢量) | +| 演示出图 | [plot_pub](#plot_pub) | 出版级 matplotlib 学术图(中文 + viridis + 矢量 + 投稿级复合图设计纪律) | | 文献检索 | [research](#research) | 查 paper_server(OpenAlex 元数据 + Sci-Hub 下载) | | 文献检索 | [documents](#documents) | 查内部 7 学科材料知识库(21W+ 论文,跨语言检索;host-side tool 持 key) | | 科研计算 | [pymatgen](#pymatgen) | 晶体结构 / XRD 模拟 / 相图 / Materials Project(host-side tool 持 key) | @@ -197,6 +197,8 @@ zcbot 的"skill"是一份可加载的工作流脚本(`skills//SKILL.md` + **默认配色**:viridis(jet 是现代审稿雷区)。 +**投稿级复合图**:要投高影响期刊的 figure 1 时,内置一套设计纪律(移植自 nature-figure skill,MIT)—— 五点 figure contract(核心结论 / 证据链 / 图原型 / 后端 / 期刊契约)、语义配色(蓝=本方法 / 绿=提升 / 红=baseline / 灰=参照,消融用同色变 alpha)、spine 纪律(`clean_spines` 只留左+下)、可编辑 SVG(`svg.fonttype='none'`)。 + --- ## 文献检索 diff --git a/core/__init__.py b/core/__init__.py index c22a2c9..3055b52 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.12.14" +__version__ = "0.12.15" diff --git a/skills/plot_pub/SKILL.md b/skills/plot_pub/SKILL.md index ffa830f..4b26326 100644 --- a/skills/plot_pub/SKILL.md +++ b/skills/plot_pub/SKILL.md @@ -1,6 +1,6 @@ --- name: plot_pub -description: 出版级 matplotlib 绘图(论文 / 报告 / 申报书用,中文字体 + viridis 配色 + dpi 设定一键到位)。✅ 触发:用户要画 XRD 谱、TG-DSC 曲线、应力-应变曲线、SEM 标注、多 panel 学术图、要 SVG/PDF 矢量图给论文。⛔ 不触发:PPT 内嵌图(走 `ppt` skill 的 matplotlib 配图流程);只要快速看一眼数据(直接 `df.plot()` 即可,不用本 skill)。 +description: 出版级 matplotlib 绘图(论文 / 报告 / 申报书用,中文字体 + viridis 配色 + dpi 设定一键到位)。✅ 触发:用户要画 XRD 谱、TG-DSC 曲线、应力-应变曲线、SEM 标注、多 panel 学术图、投稿级 / Nature 级复合图、要 SVG/PDF 矢量图给论文。⛔ 不触发:PPT 内嵌图(走 `ppt` skill 的 matplotlib 配图流程);只要快速看一眼数据(直接 `df.plot()` 即可,不用本 skill)。 --- # plot_pub @@ -139,6 +139,73 @@ 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)` 默认按以下顺序找字体: diff --git a/skills/plot_pub/style.py b/skills/plot_pub/style.py index aa05d88..8cf5fda 100644 --- a/skills/plot_pub/style.py +++ b/skills/plot_pub/style.py @@ -109,7 +109,64 @@ def apply_pub_style( rc["pdf.fonttype"] = 42 rc["ps.fonttype"] = 42 + # ---- SVG 文字可编辑(投稿级要求:导出后编辑部/作者能在 AI 里改文字) ---- + # 'none' = 文字以 保留,不转 path;配 PDF Type 42 一起,矢量两路都可编辑 + rc["svg.fonttype"] = "none" + def reset_style() -> None: """还原 matplotlib 默认 rcParams(测试 / 切换主题时用)。""" matplotlib.rcdefaults() + + +# ============================================================ +# Nature 级复合图辅助:语义配色 + spine 纪律 +# 思路源自 nature-figure skill(MIT, github.com/Yuan1z0825/nature-skills), +# 砍掉 R / 生物 gallery,只留可迁移的设计纪律,改为纯 Python + 建材领域。 +# ============================================================ + +# 语义配色:颜色承载"科学语义"而非随机区分。同族基线归一个冷色系, +# 本方法/主角归一个暖/蓝主色系 —— family consistency beats maximal hue separation。 +SEMANTIC_COLORS = { + "method": "#1f5fa8", # 蓝 = 本工作 / 提出的方法(主角) + "gain": "#2a8f5e", # 绿 = 提升 / 增益 / 正向 + "baseline": "#c0392b", # 红 = 对照 / baseline / 退化 + "neutral": "#8a8f99", # 灰 = 参照 / 背景 / 次要 + "accent": "#e08a1e", # 橙 = 强调 / 高亮少量点 +} + + +def clean_spines(ax, keep=("left", "bottom")) -> None: + """ + 出版图 spine 纪律:只留指定边框(默认左 + 下),去掉上 + 右。 + 复合图每个子 panel 都调一次,视觉更干净、信噪比更高。 + + Args: + ax: matplotlib Axes + keep: 保留哪几条 spine,默认 ("left", "bottom") + """ + for side in ("top", "right", "left", "bottom"): + ax.spines[side].set_visible(side in keep) + # 刻度只画在保留的边上 + ax.tick_params( + top="top" in keep, right="right" in keep, + bottom="bottom" in keep, left="left" in keep, + ) + + +def ablation_alphas(n: int, base_color: str = None): + """ + 消融 / 梯度对比:同一颜色变 alpha(0.25 → 1.0),而不是换色相。 + 返回 n 个 (color, alpha) 不便用,这里直接返回 n 个 RGBA。 + + Args: + n: 系列数 + base_color: 基色,默认用 SEMANTIC_COLORS["method"] + """ + import matplotlib.colors as mcolors + import numpy as np + + base = base_color or SEMANTIC_COLORS["method"] + rgb = mcolors.to_rgb(base) + alphas = np.linspace(0.25, 1.0, n) + return [(rgb[0], rgb[1], rgb[2], a) for a in alphas]