From 19bd4edf01f8ca7b20e173a0443ea6314c6006e0 Mon Sep 17 00:00:00 2001 From: wehub-skill-sync Date: Mon, 13 Jul 2026 21:35:36 +0800 Subject: [PATCH] chore: import zh skill scientific-visualization --- README.wehub.md | 9 + SKILL.md | 777 +++++++++++++++++++++++++++ assets/color_palettes.py | 197 +++++++ assets/nature.mplstyle | 63 +++ assets/presentation.mplstyle | 61 +++ assets/publication.mplstyle | 68 +++ references/color_palettes.md | 348 ++++++++++++ references/journal_requirements.md | 320 +++++++++++ references/matplotlib_examples.md | 620 +++++++++++++++++++++ references/publication_guidelines.md | 205 +++++++ scripts/figure_export.py | 343 ++++++++++++ scripts/style_presets.py | 416 ++++++++++++++ 12 files changed, 3427 insertions(+) create mode 100644 README.wehub.md create mode 100644 SKILL.md create mode 100644 assets/color_palettes.py create mode 100644 assets/nature.mplstyle create mode 100644 assets/presentation.mplstyle create mode 100644 assets/publication.mplstyle create mode 100644 references/color_palettes.md create mode 100644 references/journal_requirements.md create mode 100644 references/matplotlib_examples.md create mode 100644 references/publication_guidelines.md create mode 100644 scripts/figure_export.py create mode 100644 scripts/style_presets.py diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..6820ea8 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,9 @@ +# WeHub 来源说明 + +- Skill 名称:`scientific-visualization` +- 中文类目:Python科学数据绘图 +- 上游仓库:`K-Dense-AI__scientific-agent-skills` +- 上游路径:`skills/scientific-visualization/SKILL.md` +- 上游链接:https://github.com/K-Dense-AI/scientific-agent-skills/blob/HEAD/skills/scientific-visualization/SKILL.md +- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理 +- 原作者、版权和许可证信息以上游仓库为准 diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..553ea21 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,777 @@ +--- +name: scientific-visualization +description: 用于制作达到发表质量图形的元技能。在需要创建期刊投稿图形时使用,要求包含多面板布局、显著性标注、误差线、色盲友好调色板以及特定期刊(Nature、Science、Cell)格式。使用 matplotlib/seaborn/plotly 配合发表样式进行编排。快速探索请直接使用 seaborn 或 plotly。 +license: MIT license +metadata: + version: "1.0" + skill-author: K-Dense Inc. +--- + +# Scientific Visualization + +## 概述 + +科学可视化将数据转换为清晰、准确的发表级图形。创建具有多面板布局、误差线、显著性标记和色盲友好调色板的期刊就绪图表。使用 matplotlib、seaborn 和 plotly 导出为 PDF/EPS/TIFF 格式,用于学术手稿。 + +## 何时使用本技能 + +以下情况应使用本技能: +- 为科学手稿创建图形或可视化 +- 为期刊投稿准备图表(Nature、Science、Cell、PLOS 等) +- 确保图形色盲友好且无障碍可读 +- 制作风格一致的多面板图形 +- 以正确的分辨率和格式导出图形 +- 遵循特定的出版指南 +- 改进现有图形以符合发表标准 +- 创建需在彩色和灰度下均能清晰显示的图形 + +## 快速入门指南 + +### 基本发表质量图形 + +```python +import matplotlib.pyplot as plt +import numpy as np + +# 应用发表样式(来自 scripts/style_presets.py) +from style_presets import apply_publication_style +apply_publication_style('default') + +# 创建合适尺寸的图形(单栏 = 3.5 英寸) +fig, ax = plt.subplots(figsize=(3.5, 2.5)) + +# 绘制数据 +x = np.linspace(0, 10, 100) +ax.plot(x, np.sin(x), label='sin(x)') +ax.plot(x, np.cos(x), label='cos(x)') + +# 正确标注单位 +ax.set_xlabel('Time (seconds)') +ax.set_ylabel('Amplitude (mV)') +ax.legend(frameon=False) + +# 移除不必要的边框 +ax.spines['top'].set_visible(False) +ax.spines['right'].set_visible(False) + +# 以发表格式保存(来自 figure_export.py) +from figure_export import save_publication_figure +save_publication_figure(fig, 'figure1', formats=['pdf', 'png'], dpi=300) +``` + +### 使用预配置样式 + +使用 `assets/` 中的 matplotlib 样式文件应用特定期刊样式: + +```python +import matplotlib.pyplot as plt + +# 选项 1:直接使用样式文件 +plt.style.use('assets/nature.mplstyle') + +# 选项 2:使用 style_presets.py 辅助函数 +from style_presets import configure_for_journal +configure_for_journal('nature', figure_width='single') + +# 现在创建图形——它们将自动符合 Nature 规格 +fig, ax = plt.subplots() +# ... 你的绘图代码 ... +``` + +### 使用 Seaborn 快速入门 + +对于统计图形,使用 seaborn 配合发表样式: + +```python +import seaborn as sns +import matplotlib.pyplot as plt +from style_presets import apply_publication_style + +# 应用发表样式 +apply_publication_style('default') +sns.set_theme(style='ticks', context='paper', font_scale=1.1) +sns.set_palette('colorblind') + +# 创建统计比较图 +fig, ax = plt.subplots(figsize=(3.5, 3)) +sns.boxplot(data=df, x='treatment', y='response', + order=['Control', 'Low', 'High'], palette='Set2', ax=ax) +sns.stripplot(data=df, x='treatment', y='response', + order=['Control', 'Low', 'High'], + color='black', alpha=0.3, size=3, ax=ax) +ax.set_ylabel('Response (μM)') +sns.despine() + +# 保存图形 +from figure_export import save_publication_figure +save_publication_figure(fig, 'treatment_comparison', formats=['pdf', 'png'], dpi=300) +``` + +## 核心原则与最佳实践 + +### 1. 分辨率与文件格式 + +**关键要求**(详见 `references/publication_guidelines.md`): +- **栅格图像**(照片、显微镜):300-600 DPI +- **线条图**(图表、曲线图):600-1200 DPI 或矢量格式 +- **矢量格式**(首选):PDF、EPS、SVG +- **栅格格式**:TIFF、PNG(科学数据切勿使用 JPEG) + +**实现方式:** +```python +# 使用 figure_export.py 脚本设置正确参数 +from figure_export import save_publication_figure + +# 以多种格式保存,DPI 正确 +save_publication_figure(fig, 'myfigure', formats=['pdf', 'png'], dpi=300) + +# 或按特定期刊要求保存 +from figure_export import save_for_journal +save_for_journal(fig, 'figure1', journal='nature', figure_type='combination') +``` + +### 2. 颜色选择——色盲无障碍 + +**始终使用色盲友好调色板**(详见 `references/color_palettes.md`): + +**推荐:Okabe-Ito 调色板**(所有类型的色盲均可区分): +```python +# 选项 1:使用 assets/color_palettes.py +from color_palettes import OKABE_ITO_LIST, apply_palette +apply_palette('okabe_ito') + +# 选项 2:手动指定 +okabe_ito = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7', '#000000'] +plt.rcParams['axes.prop_cycle'] = plt.cycler(color=okabe_ito) +``` + +**对于热力图/连续数据:** +- 使用感知均匀的颜色映射:`viridis`、`plasma`、`cividis` +- 避免红-绿发散型映射(改用 `PuOr`、`RdBu`、`BrBG`) +- 切勿使用 `jet` 或 `rainbow` 颜色映射 + +**始终在灰度模式下测试图形**,确保可读性。 + +### 3. 字体与文本 + +**字体指南**(详见 `references/publication_guidelines.md`): +- 无衬线字体:Arial、Helvetica、Calibri +- **最终印刷尺寸**下的最小字号: + - 轴标签:7-9 pt + - 刻度标签:6-8 pt + - 面板标签:8-12 pt(粗体) +- 标签使用句首大写:写 "Time (hours)" 而非 "TIME (HOURS)" +- 始终在括号内包含单位 + +**实现方式:** +```python +# 全局设置字体 +import matplotlib as mpl +mpl.rcParams['font.family'] = 'sans-serif' +mpl.rcParams['font.sans-serif'] = ['Arial', 'Helvetica'] +mpl.rcParams['font.size'] = 8 +mpl.rcParams['axes.labelsize'] = 9 +mpl.rcParams['xtick.labelsize'] = 7 +mpl.rcParams['ytick.labelsize'] = 7 +``` + +### 4. 图形尺寸 + +**期刊特定宽度**(详见 `references/journal_requirements.md`): +- **Nature**:单栏 89 mm,双栏 183 mm +- **Science**:单栏 55 mm,双栏 175 mm +- **Cell**:单栏 85 mm,双栏 178 mm + +**检查图形尺寸合规性:** +```python +from figure_export import check_figure_size + +fig = plt.figure(figsize=(3.5, 3)) # Nature 的 89 mm +check_figure_size(fig, journal='nature') +``` + +### 5. 多面板图形 + +**最佳实践:** +- 使用粗体字母标注面板:**A**、**B**、**C**(大多数期刊用大写,Nature 用小写) +- 所有面板保持风格一致 +- 尽可能沿边缘对齐面板 +- 面板之间保留适当的空白 + +**示例实现**(完整代码见 `references/matplotlib_examples.md`): +```python +from string import ascii_uppercase + +fig = plt.figure(figsize=(7, 4)) +gs = fig.add_gridspec(2, 2, hspace=0.4, wspace=0.4) + +ax1 = fig.add_subplot(gs[0, 0]) +ax2 = fig.add_subplot(gs[0, 1]) +# ... 创建其他面板 ... + +# 添加面板标签 +for i, ax in enumerate([ax1, ax2, ...]): + ax.text(-0.15, 1.05, ascii_uppercase[i], transform=ax.transAxes, + fontsize=10, fontweight='bold', va='top') +``` + +## 常见任务 + +### 任务 1:创建可用于发表的线图 + +完整代码见 `references/matplotlib_examples.md` 示例 1。 + +**关键步骤:** +1. 应用发表样式 +2. 为目标期刊设置合适的图形尺寸 +3. 使用色盲友好颜色 +4. 添加正确表示的误差线(SEM、SD 或 CI) +5. 标注轴及单位 +6. 移除不必要的边框 +7. 以矢量格式保存 + +**使用 seaborn 自动计算置信区间:** +```python +import seaborn as sns +fig, ax = plt.subplots(figsize=(5, 3)) +sns.lineplot(data=timeseries, x='time', y='measurement', + hue='treatment', errorbar=('ci', 95), + markers=True, ax=ax) +ax.set_xlabel('Time (hours)') +ax.set_ylabel('Measurement (AU)') +sns.despine() +``` + +### 任务 2:创建多面板图形 + +完整代码见 `references/matplotlib_examples.md` 示例 2。 + +**关键步骤:** +1. 使用 `GridSpec` 实现灵活布局 +2. 确保所有面板风格一致 +3. 添加粗体面板标签(A、B、C 等) +4. 对齐相关面板 +5. 验证所有文本在最终尺寸下可读 + +### 任务 3:创建使用正确颜色映射的热力图 + +完整代码见 `references/matplotlib_examples.md` 示例 4。 + +**关键步骤:** +1. 使用感知均匀的颜色映射(`viridis`、`plasma`、`cividis`) +2. 包含带标注的颜色条 +3. 对于发散型数据,使用色盲安全的发散映射(`RdBu_r`、`PuOr`) +4. 为发散映射设置合适的中心值 +5. 测试灰度显示效果 + +**使用 seaborn 绘制相关矩阵:** +```python +import seaborn as sns +fig, ax = plt.subplots(figsize=(5, 4)) +corr = df.corr() +mask = np.triu(np.ones_like(corr, dtype=bool)) +sns.heatmap(corr, mask=mask, annot=True, fmt='.2f', + cmap='RdBu_r', center=0, square=True, + linewidths=1, cbar_kws={'shrink': 0.8}, ax=ax) +``` + +### 任务 4:为特定期刊准备图形 + +**工作流程:** +1. 查看期刊要求:`references/journal_requirements.md` +2. 为期刊配置 matplotlib: + ```python + from style_presets import configure_for_journal + configure_for_journal('nature', figure_width='single') + ``` +3. 创建图形(将自动调整尺寸) +4. 按期刊规格导出: + ```python + from figure_export import save_for_journal + save_for_journal(fig, 'figure1', journal='nature', figure_type='line_art') + ``` + +### 任务 5:修复现有图形使其符合发表标准 + +**清单式方法**(完整清单见 `references/publication_guidelines.md`): + +1. **检查分辨率**:确认 DPI 符合期刊要求 +2. **检查文件格式**:图形用矢量格式,图像用 TIFF/PNG +3. **检查颜色**:确保色盲友好 +4. **检查字体**:最终尺寸下最小 6-7 pt,无衬线字体 +5. **检查标签**:所有轴标注单位 +6. **检查尺寸**:匹配期刊栏宽 +7. **测试灰度**:无颜色也能解读图形 +8. **去除图表垃圾**:不要不必要的网格、3D 效果、阴影 + +### 任务 6:创建色盲友好的可视化 + +**策略:** +1. 使用 `assets/color_palettes.py` 中的认可调色板 +2. 添加冗余编码(线型、标记、图案) +3. 使用色盲模拟器测试 +4. 确保灰度兼容 + +**示例:** +```python +from color_palettes import apply_palette +import matplotlib.pyplot as plt + +apply_palette('okabe_ito') + +# 在颜色之外添加冗余编码 +line_styles = ['-', '--', '-.', ':'] +markers = ['o', 's', '^', 'v'] + +for i, (data, label) in enumerate(datasets): + plt.plot(x, data, linestyle=line_styles[i % 4], + marker=markers[i % 4], label=label) +``` + +## 统计严谨性 + +**始终包含:** +- 误差线(SD、SEM 或 CI——在说明中注明具体类型) +- 样本量(n)在图中或说明中 +- 统计显著性标记(*、**、***) +- 尽可能显示个体数据点(而不仅仅是汇总统计量) + +**含统计信息的示例:** +```python +# 显示个体数据点及汇总统计量 +ax.scatter(x_jittered, individual_points, alpha=0.4, s=8) +ax.errorbar(x, means, yerr=sems, fmt='o', capsize=3) + +# 标记显著性 +ax.text(1.5, max_y * 1.1, '***', ha='center', fontsize=8) +``` + +## 使用不同的绘图库 + +### Matplotlib +- 对发表细节的控制最全面 +- 最适合复杂的多面板图形 +- 使用提供的样式文件实现统一格式 +- 更多示例见 `references/matplotlib_examples.md` + +### Seaborn + +Seaborn 提供了一种高级的、面向数据集的统计图形接口,构建于 matplotlib 之上。它在使用最少代码创建发表质量的统计可视化方面表现出色,同时与 matplotlib 自定义功能完全兼容。 + +**科学可视化的主要优势:** +- 自动统计估计和置信区间 +- 内置多面板图形支持(分面) +- 默认使用色盲友好调色板 +- 基于 pandas DataFrame 的面向数据集 API +- 变量到视觉属性的语义映射 + +#### 发表样式快速入门 + +始终先应用 matplotlib 发表样式,然后配置 seaborn: + +```python +import seaborn as sns +import matplotlib.pyplot as plt +from style_presets import apply_publication_style + +# 应用发表样式 +apply_publication_style('default') + +# 为发表配置 seaborn +sns.set_theme(style='ticks', context='paper', font_scale=1.1) +sns.set_palette('colorblind') # 使用色盲安全调色板 + +# 创建图形 +fig, ax = plt.subplots(figsize=(3.5, 2.5)) +sns.scatterplot(data=df, x='time', y='response', + hue='treatment', style='condition', ax=ax) +sns.despine() # 移除顶部和右侧边框 +``` + +#### 用于发表的常见图形类型 + +**统计比较:** +```python +# 带个体数据点的箱线图,增加透明度 +fig, ax = plt.subplots(figsize=(3.5, 3)) +sns.boxplot(data=df, x='treatment', y='response', + order=['Control', 'Low', 'High'], palette='Set2', ax=ax) +sns.stripplot(data=df, x='treatment', y='response', + order=['Control', 'Low', 'High'], + color='black', alpha=0.3, size=3, ax=ax) +ax.set_ylabel('Response (μM)') +sns.despine() +``` + +**分布分析:** +```python +# 带分组比较的提琴图 +fig, ax = plt.subplots(figsize=(4, 3)) +sns.violinplot(data=df, x='timepoint', y='expression', + hue='treatment', split=True, inner='quartile', ax=ax) +ax.set_ylabel('Gene Expression (AU)') +sns.despine() +``` + +**相关矩阵:** +```python +# 使用正确颜色映射和标注的热力图 +fig, ax = plt.subplots(figsize=(5, 4)) +corr = df.corr() +mask = np.triu(np.ones_like(corr, dtype=bool)) # 仅显示下三角 +sns.heatmap(corr, mask=mask, annot=True, fmt='.2f', + cmap='RdBu_r', center=0, square=True, + linewidths=1, cbar_kws={'shrink': 0.8}, ax=ax) +plt.tight_layout() +``` + +**带置信带的时序图:** +```python +# 自动计算置信区间的线图 +fig, ax = plt.subplots(figsize=(5, 3)) +sns.lineplot(data=timeseries, x='time', y='measurement', + hue='treatment', style='replicate', + errorbar=('ci', 95), markers=True, dashes=False, ax=ax) +ax.set_xlabel('Time (hours)') +ax.set_ylabel('Measurement (AU)') +sns.despine() +``` + +#### 使用 Seaborn 创建多面板图形 + +**使用 FacetGrid 自动分面:** +```python +# 创建分面图 +g = sns.relplot(data=df, x='dose', y='response', + hue='treatment', col='cell_line', row='timepoint', + kind='line', height=2.5, aspect=1.2, + errorbar=('ci', 95), markers=True) +g.set_axis_labels('Dose (μM)', 'Response (AU)') +g.set_titles('{row_name} | {col_name}') +sns.despine() + +# 以正确 DPI 保存 +from figure_export import save_publication_figure +save_publication_figure(g.figure, 'figure_facets', + formats=['pdf', 'png'], dpi=300) +``` + +**将 seaborn 与 matplotlib 子图结合:** +```python +# 创建自定义多面板布局 +fig, axes = plt.subplots(2, 2, figsize=(7, 6)) + +# 面板 A:带回归的散点图 +sns.regplot(data=df, x='predictor', y='response', ax=axes[0, 0]) +axes[0, 0].text(-0.15, 1.05, 'A', transform=axes[0, 0].transAxes, + fontsize=10, fontweight='bold') + +# 面板 B:分布比较 +sns.violinplot(data=df, x='group', y='value', ax=axes[0, 1]) +axes[0, 1].text(-0.15, 1.05, 'B', transform=axes[0, 1].transAxes, + fontsize=10, fontweight='bold') + +# 面板 C:热力图 +sns.heatmap(correlation_data, cmap='viridis', ax=axes[1, 0]) +axes[1, 0].text(-0.15, 1.05, 'C', transform=axes[1, 0].transAxes, + fontsize=10, fontweight='bold') + +# 面板 D:时序图 +sns.lineplot(data=timeseries, x='time', y='signal', + hue='condition', ax=axes[1, 1]) +axes[1, 1].text(-0.15, 1.05, 'D', transform=axes[1, 1].transAxes, + fontsize=10, fontweight='bold') + +plt.tight_layout() +sns.despine() +``` + +#### 发表用配色方案 + +Seaborn 包含多个色盲安全调色板: + +```python +# 使用内置色盲调色板(推荐) +sns.set_palette('colorblind') + +# 或指定自定义色盲安全颜色(Okabe-Ito) +okabe_ito = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7', '#000000'] +sns.set_palette(okabe_ito) + +# 用于热力图和连续数据 +sns.heatmap(data, cmap='viridis') # 感知均匀 +sns.heatmap(corr, cmap='RdBu_r', center=0) # 发散型,居中 +``` + +#### 在轴级与图级函数之间选择 + +**轴级函数**(如 `scatterplot`、`boxplot`、`heatmap`): +- 在构建自定义多面板布局时使用 +- 接受 `ax=` 参数进行精确定位 +- 与 matplotlib 子图集成更好 +- 对图形组成有更多控制 + +```python +fig, ax = plt.subplots(figsize=(3.5, 2.5)) +sns.scatterplot(data=df, x='x', y='y', hue='group', ax=ax) +``` + +**图级函数**(如 `relplot`、`catplot`、`displot`): +- 用于按分类变量自动分面 +- 创建风格一致的完整图形 +- 非常适合探索性分析 +- 使用 `height` 和 `aspect` 控制尺寸 + +```python +g = sns.relplot(data=df, x='x', y='y', col='category', kind='scatter') +``` + +#### 使用 Seaborn 实现统计严谨性 + +Seaborn 自动计算并显示不确定性: + +```python +# 线图:默认显示均值 ± 95% CI +sns.lineplot(data=df, x='time', y='value', hue='treatment', + errorbar=('ci', 95)) # 可改为 'sd'、'se' 等 + +# 条形图:显示均值及自助法 CI +sns.barplot(data=df, x='treatment', y='response', + errorbar=('ci', 95), capsize=0.1) + +# 始终在图注中注明误差类型: +# "误差线表示 95% 置信区间" +``` + +#### 发表级 Seaborn 图形的最佳实践 + +1. **始终先设置发表主题:** + ```python + sns.set_theme(style='ticks', context='paper', font_scale=1.1) + ``` + +2. **使用色盲安全调色板:** + ```python + sns.set_palette('colorblind') + ``` + +3. **移除不必要的元素:** + ```python + sns.despine() # 移除顶部和右侧边框 + ``` + +4. **适当控制图形尺寸:** + ```python + # 轴级:使用 matplotlib figsize + fig, ax = plt.subplots(figsize=(3.5, 2.5)) + + # 图级:使用 height 和 aspect + g = sns.relplot(..., height=3, aspect=1.2) + ``` + +5. **尽可能显示个体数据点:** + ```python + sns.boxplot(...) # 汇总统计 + sns.stripplot(..., alpha=0.3) # 个体数据点 + ``` + +6. **包含带单位的正确标签:** + ```python + ax.set_xlabel('Time (hours)') + ax.set_ylabel('Expression (AU)') + ``` + +7. **以正确分辨率导出:** + ```python + from figure_export import save_publication_figure + save_publication_figure(fig, 'figure_name', + formats=['pdf', 'png'], dpi=300) + ``` + +#### 高级 Seaborn 技巧 + +**探索性分析的成对关系图:** +```python +# 快速查看所有关系 +g = sns.pairplot(data=df, hue='condition', + vars=['gene1', 'gene2', 'gene3'], + corner=True, diag_kind='kde', height=2) +``` + +**层次聚类热力图:** +```python +# 对样本和特征进行聚类 +g = sns.clustermap(expression_data, method='ward', + metric='euclidean', z_score=0, + cmap='RdBu_r', center=0, + figsize=(10, 8), + row_colors=condition_colors, + cbar_kws={'label': 'Z-score'}) +``` + +**带边缘分布的联合图:** +```python +# 双变量分布及上下文 +g = sns.jointplot(data=df, x='gene1', y='gene2', + hue='treatment', kind='scatter', + height=6, ratio=4, marginal_kws={'kde': True}) +``` + +#### 常见 Seaborn 问题及解决方案 + +**问题:图例超出绘图区域** +```python +g = sns.relplot(...) +g._legend.set_bbox_to_anchor((0.9, 0.5)) +``` + +**问题:标签重叠** +```python +plt.xticks(rotation=45, ha='right') +plt.tight_layout() +``` + +**问题:最终尺寸下文字过小** +```python +sns.set_context('paper', font_scale=1.2) # 根据需要增大 +``` + +#### 其他资源 + +更多 seaborn 详细信息,请参见: +- `skills/seaborn/SKILL.md` —— 全面的 seaborn 文档 +- `skills/seaborn/references/examples.md` —— 实际用例 +- `skills/seaborn/references/function_reference.md` —— 完整 API 参考 +- `skills/seaborn/references/objects_interface.md` —— 现代声明式 API + +### Plotly +- 交互式图形用于探索 +- 导出静态图像用于发表 +- 配置为发表质量: +```python +fig.update_layout( + font=dict(family='Arial, sans-serif', size=10), + plot_bgcolor='white', + # ... 详见 matplotlib_examples.md 示例 8 +) +fig.write_image('figure.png', scale=3) # scale=3 约等于 300 DPI +``` + +## 资源 + +### 参考文档目录 + +**根据需要加载以下文件获取详细信息:** + +- **`publication_guidelines.md`**:综合最佳实践 + - 分辨率和文件格式要求 + - 排版指南 + - 布局与构图规则 + - 统计严谨性要求 + - 完整发表检查清单 + +- **`color_palettes.md`**:颜色使用指南 + - 色盲友好调色板规格及 RGB 值 + - 顺序型和发散型颜色映射推荐 + - 无障碍测试流程 + - 领域特定调色板(基因组学、显微镜) + +- **`journal_requirements.md`**:期刊特定规格 + - 各出版社的技术要求 + - 文件格式和 DPI 规格 + - 图形尺寸要求 + - 快速参考表 + +- **`matplotlib_examples.md`**:实用代码示例 + - 10 个完整的工作示例 + - 线图、条形图、热力图、多面板图形 + - 期刊特定图形示例 + - 各库(matplotlib、seaborn、plotly)的使用技巧 + +### 脚本目录 + +**使用以下辅助脚本实现自动化:** + +- **`figure_export.py`**:导出工具 + - `save_publication_figure()`:以多种格式保存,DPI 正确 + - `save_for_journal()`:自动应用期刊特定要求 + - `check_figure_size()`:验证尺寸是否符合期刊规格 + - 直接运行:`python scripts/figure_export.py` 查看示例 + +- **`style_presets.py`**:预配置样式 + - `apply_publication_style()`:应用预设样式(default、nature、science、cell) + - `set_color_palette()`:快速切换调色板 + - `configure_for_journal()`:一键期刊配置 + - 直接运行:`python scripts/style_presets.py` 查看示例 + +### 资源文件目录 + +**在图形中使用以下文件:** + +- **`color_palettes.py`**:可导入的颜色定义 + - 所有推荐调色板作为 Python 常量 + - `apply_palette()` 辅助函数 + - 可直接导入到笔记本/脚本中 + +- **Matplotlib 样式文件**:使用 `plt.style.use()` 加载 + - `publication.mplstyle`:通用发表质量 + - `nature.mplstyle`:Nature 期刊规格 + - `presentation.mplstyle`:海报/幻灯片用大字体 + +## 工作流程总结 + +**创建发表级图形的推荐工作流程:** + +1. **规划**:确定目标期刊、图形类型和内容 +2. **配置**:为期刊应用合适的样式 + ```python + from style_presets import configure_for_journal + configure_for_journal('nature', 'single') + ``` +3. **创建**:使用正确的标签、颜色和统计信息构建图形 +4. **验证**:检查尺寸、字体、颜色和无障碍性 + ```python + from figure_export import check_figure_size + check_figure_size(fig, journal='nature') + ``` +5. **导出**:以所需格式保存 + ```python + from figure_export import save_for_journal + save_for_journal(fig, 'figure1', 'nature', 'combination') + ``` +6. **审阅**:在手稿上下文中以最终尺寸查看 + +## 应避免的常见陷阱 + +1. **字体过小**:以最终尺寸打印时文字不可读 +2. **JPEG 格式**:切勿在图表中使用 JPEG(会产生伪影) +3. **红-绿配色**:约 8% 的男性无法区分 +4. **低分辨率**:发表时出现像素化图形 +5. **缺少单位**:始终为轴标注单位 +6. **3D 效果**:扭曲感知,应完全避免 +7. **图表垃圾**:移除不必要的网格线和装饰 +8. **截断坐标轴**:条形图从零开始,除非有科学依据 +9. **风格不一致**:同一手稿中不同图形使用不同字体/颜色 +10. **没有误差线**:始终显示不确定性 + +## 最终检查清单 + +在提交图形之前,请确认: + +- [ ] 分辨率符合期刊要求(300+ DPI) +- [ ] 文件格式正确(图形用矢量,图像用 TIFF) +- [ ] 图形尺寸符合期刊规格 +- [ ] 所有文字在最终尺寸下可读(≥6 pt) +- [ ] 颜色色盲友好 +- [ ] 图形在灰度下可用 +- [ ] 所有轴已标注单位 +- [ ] 误差线已标注且说明中已定义 +- [ ] 面板标签存在且一致 +- [ ] 无图表垃圾或 3D 效果 +- [ ] 所有图形字体一致 +- [ ] 统计显著性已清晰标记 +- [ ] 图例清晰完整 + +使用本技能,确保科学图形达到最高发表标准,同时对所有读者保持无障碍可读。 diff --git a/assets/color_palettes.py b/assets/color_palettes.py new file mode 100644 index 0000000..be4f0e9 --- /dev/null +++ b/assets/color_palettes.py @@ -0,0 +1,197 @@ +""" +Colorblind-Friendly Color Palettes for Scientific Visualization + +This module provides carefully curated color palettes optimized for +scientific publications and accessibility. + +Usage: + from color_palettes import OKABE_ITO, apply_palette + import matplotlib.pyplot as plt + + apply_palette('okabe_ito') + plt.plot([1, 2, 3], [1, 4, 9]) +""" + +# Okabe-Ito Palette (2008) +# The most widely recommended colorblind-friendly palette +OKABE_ITO = { + 'orange': '#E69F00', + 'sky_blue': '#56B4E9', + 'bluish_green': '#009E73', + 'yellow': '#F0E442', + 'blue': '#0072B2', + 'vermillion': '#D55E00', + 'reddish_purple': '#CC79A7', + 'black': '#000000' +} + +OKABE_ITO_LIST = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7', '#000000'] + +# Wong Palette (Nature Methods) +WONG = ['#000000', '#E69F00', '#56B4E9', '#009E73', + '#F0E442', '#0072B2', '#D55E00', '#CC79A7'] + +# Paul Tol Palettes (https://personal.sron.nl/~pault/) +TOL_BRIGHT = ['#4477AA', '#EE6677', '#228833', '#CCBB44', + '#66CCEE', '#AA3377', '#BBBBBB'] + +TOL_MUTED = ['#332288', '#88CCEE', '#44AA99', '#117733', + '#999933', '#DDCC77', '#CC6677', '#882255', '#AA4499'] + +TOL_LIGHT = ['#77AADD', '#EE8866', '#EEDD88', '#FFAABB', + '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#DDDDDD'] + +TOL_HIGH_CONTRAST = ['#004488', '#DDAA33', '#BB5566'] + +# Sequential colormaps (for continuous data) +SEQUENTIAL_COLORMAPS = [ + 'viridis', # Default, perceptually uniform + 'plasma', # Perceptually uniform + 'inferno', # Perceptually uniform + 'magma', # Perceptually uniform + 'cividis', # Optimized for colorblind viewers + 'YlOrRd', # Yellow-Orange-Red + 'YlGnBu', # Yellow-Green-Blue + 'Blues', # Single hue + 'Greens', # Single hue + 'Purples', # Single hue +] + +# Diverging colormaps (for data with meaningful center) +DIVERGING_COLORMAPS_SAFE = [ + 'RdYlBu', # Red-Yellow-Blue (reversed is common) + 'RdBu', # Red-Blue + 'PuOr', # Purple-Orange (excellent for colorblind) + 'BrBG', # Brown-Blue-Green (good for colorblind) + 'PRGn', # Purple-Green (use with caution) + 'PiYG', # Pink-Yellow-Green (use with caution) +] + +# Diverging colormaps to AVOID (red-green combinations) +DIVERGING_COLORMAPS_AVOID = [ + 'RdGn', # Red-Green (problematic!) + 'RdYlGn', # Red-Yellow-Green (problematic!) +] + +# Fluorophore colors (traditional - use with caution) +FLUOROPHORES_TRADITIONAL = { + 'DAPI': '#0000FF', # Blue + 'GFP': '#00FF00', # Green (problematic for colorblind) + 'RFP': '#FF0000', # Red + 'Cy5': '#FF00FF', # Magenta + 'YFP': '#FFFF00', # Yellow +} + +# Fluorophore colors (colorblind-friendly alternatives) +FLUOROPHORES_ACCESSIBLE = { + 'Channel1': '#0072B2', # Blue + 'Channel2': '#E69F00', # Orange (instead of green) + 'Channel3': '#D55E00', # Vermillion (instead of red) + 'Channel4': '#CC79A7', # Magenta + 'Channel5': '#F0E442', # Yellow +} + +# Genomics/Bioinformatics +DNA_BASES = { + 'A': '#00CC00', # Green + 'C': '#0000CC', # Blue + 'G': '#FFB300', # Orange + 'T': '#CC0000', # Red +} + +DNA_BASES_ACCESSIBLE = { + 'A': '#009E73', # Bluish Green + 'C': '#0072B2', # Blue + 'G': '#E69F00', # Orange + 'T': '#D55E00', # Vermillion +} + + +def apply_palette(palette_name='okabe_ito'): + """ + Apply a color palette to matplotlib's default color cycle. + + Parameters + ---------- + palette_name : str + Name of the palette to apply. Options: + 'okabe_ito', 'wong', 'tol_bright', 'tol_muted', + 'tol_light', 'tol_high_contrast' + + Returns + ------- + list + List of colors in the palette + + Examples + -------- + >>> apply_palette('okabe_ito') + >>> plt.plot([1, 2, 3], [1, 4, 9]) # Uses Okabe-Ito colors + """ + try: + import matplotlib.pyplot as plt + except ImportError: + print("matplotlib not installed") + return None + + palettes = { + 'okabe_ito': OKABE_ITO_LIST, + 'wong': WONG, + 'tol_bright': TOL_BRIGHT, + 'tol_muted': TOL_MUTED, + 'tol_light': TOL_LIGHT, + 'tol_high_contrast': TOL_HIGH_CONTRAST, + } + + if palette_name not in palettes: + available = ', '.join(palettes.keys()) + raise ValueError(f"Palette '{palette_name}' not found. Available: {available}") + + colors = palettes[palette_name] + plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors) + return colors + + +def get_palette(palette_name='okabe_ito'): + """ + Get a color palette as a list. + + Parameters + ---------- + palette_name : str + Name of the palette + + Returns + ------- + list + List of color hex codes + """ + palettes = { + 'okabe_ito': OKABE_ITO_LIST, + 'wong': WONG, + 'tol_bright': TOL_BRIGHT, + 'tol_muted': TOL_MUTED, + 'tol_light': TOL_LIGHT, + 'tol_high_contrast': TOL_HIGH_CONTRAST, + } + + if palette_name not in palettes: + available = ', '.join(palettes.keys()) + raise ValueError(f"Palette '{palette_name}' not found. Available: {available}") + + return palettes[palette_name] + + +if __name__ == "__main__": + print("Available colorblind-friendly palettes:") + print(f" - Okabe-Ito: {len(OKABE_ITO_LIST)} colors") + print(f" - Wong: {len(WONG)} colors") + print(f" - Tol Bright: {len(TOL_BRIGHT)} colors") + print(f" - Tol Muted: {len(TOL_MUTED)} colors") + print(f" - Tol Light: {len(TOL_LIGHT)} colors") + print(f" - Tol High Contrast: {len(TOL_HIGH_CONTRAST)} colors") + + print("\nOkabe-Ito palette (most recommended):") + for name, color in OKABE_ITO.items(): + print(f" {name:15s}: {color}") diff --git a/assets/nature.mplstyle b/assets/nature.mplstyle new file mode 100644 index 0000000..bd8386d --- /dev/null +++ b/assets/nature.mplstyle @@ -0,0 +1,63 @@ +# Nature journal style +# Usage: plt.style.use('nature.mplstyle') +# +# Optimized for Nature journal specifications: +# - Single column: 89 mm +# - Double column: 183 mm +# - High resolution requirements + +# Figure properties +figure.dpi: 100 +figure.facecolor: white +figure.constrained_layout.use: True +figure.figsize: 3.5, 2.625 # 89 mm single column, 3:4 aspect + +# Font properties (Nature prefers smaller fonts) +font.size: 7 +font.family: sans-serif +font.sans-serif: Arial, Helvetica + +# Axes properties +axes.linewidth: 0.5 +axes.labelsize: 8 +axes.titlesize: 8 +axes.labelweight: normal +axes.spines.top: False +axes.spines.right: False +axes.edgecolor: black +axes.axisbelow: True +axes.grid: False +axes.prop_cycle: cycler('color', ['E69F00', '56B4E9', '009E73', 'F0E442', '0072B2', 'D55E00', 'CC79A7']) + +# Tick properties +xtick.major.size: 2.5 +xtick.minor.size: 1.5 +xtick.major.width: 0.5 +xtick.minor.width: 0.4 +xtick.labelsize: 6 +xtick.direction: out +ytick.major.size: 2.5 +ytick.minor.size: 1.5 +ytick.major.width: 0.5 +ytick.minor.width: 0.4 +ytick.labelsize: 6 +ytick.direction: out + +# Line properties +lines.linewidth: 1.2 +lines.markersize: 3 +lines.markeredgewidth: 0.4 + +# Legend properties +legend.fontsize: 6 +legend.frameon: False + +# Save properties (Nature requirements) +savefig.dpi: 600 # 1000 for line art, 600 for combination +savefig.format: pdf +savefig.bbox: tight +savefig.pad_inches: 0.05 +savefig.facecolor: white + +# Image properties +image.cmap: viridis diff --git a/assets/presentation.mplstyle b/assets/presentation.mplstyle new file mode 100644 index 0000000..d435fef --- /dev/null +++ b/assets/presentation.mplstyle @@ -0,0 +1,61 @@ +# Presentation/Poster style +# Usage: plt.style.use('presentation.mplstyle') +# +# Larger fonts and thicker lines for presentations, +# posters, and projected displays + +# Figure properties +figure.dpi: 100 +figure.facecolor: white +figure.constrained_layout.use: True +figure.figsize: 8, 6 + +# Font properties (larger for visibility) +font.size: 14 +font.family: sans-serif +font.sans-serif: Arial, Helvetica, Calibri + +# Axes properties +axes.linewidth: 1.5 +axes.labelsize: 16 +axes.titlesize: 18 +axes.labelweight: normal +axes.spines.top: False +axes.spines.right: False +axes.edgecolor: black +axes.axisbelow: True +axes.grid: False +axes.prop_cycle: cycler('color', ['E69F00', '56B4E9', '009E73', 'F0E442', '0072B2', 'D55E00', 'CC79A7']) + +# Tick properties +xtick.major.size: 6 +xtick.minor.size: 4 +xtick.major.width: 1.5 +xtick.minor.width: 1.0 +xtick.labelsize: 12 +xtick.direction: out +ytick.major.size: 6 +ytick.minor.size: 4 +ytick.major.width: 1.5 +ytick.minor.width: 1.0 +ytick.labelsize: 12 +ytick.direction: out + +# Line properties +lines.linewidth: 2.5 +lines.markersize: 8 +lines.markeredgewidth: 1.0 + +# Legend properties +legend.fontsize: 12 +legend.frameon: False + +# Save properties +savefig.dpi: 300 +savefig.format: png +savefig.bbox: tight +savefig.pad_inches: 0.1 +savefig.facecolor: white + +# Image properties +image.cmap: viridis diff --git a/assets/publication.mplstyle b/assets/publication.mplstyle new file mode 100644 index 0000000..fe224c4 --- /dev/null +++ b/assets/publication.mplstyle @@ -0,0 +1,68 @@ +# Publication-quality matplotlib style +# Usage: plt.style.use('publication.mplstyle') +# +# This style provides clean, professional formatting suitable +# for most scientific journals + +# Figure properties +figure.dpi: 100 +figure.facecolor: white +figure.autolayout: False +figure.constrained_layout.use: True +figure.figsize: 3.5, 2.5 + +# Font properties +font.size: 8 +font.family: sans-serif +font.sans-serif: Arial, Helvetica, DejaVu Sans + +# Axes properties +axes.linewidth: 0.5 +axes.labelsize: 9 +axes.titlesize: 9 +axes.labelweight: normal +axes.spines.top: False +axes.spines.right: False +axes.spines.left: True +axes.spines.bottom: True +axes.edgecolor: black +axes.labelcolor: black +axes.axisbelow: True +axes.grid: False +axes.prop_cycle: cycler('color', ['E69F00', '56B4E9', '009E73', 'F0E442', '0072B2', 'D55E00', 'CC79A7', '000000']) + +# Tick properties +xtick.major.size: 3 +xtick.minor.size: 2 +xtick.major.width: 0.5 +xtick.minor.width: 0.5 +xtick.labelsize: 7 +xtick.direction: out +ytick.major.size: 3 +ytick.minor.size: 2 +ytick.major.width: 0.5 +ytick.minor.width: 0.5 +ytick.labelsize: 7 +ytick.direction: out + +# Line properties +lines.linewidth: 1.5 +lines.markersize: 4 +lines.markeredgewidth: 0.5 + +# Legend properties +legend.fontsize: 7 +legend.frameon: False +legend.loc: best + +# Save properties +savefig.dpi: 300 +savefig.format: pdf +savefig.bbox: tight +savefig.pad_inches: 0.05 +savefig.transparent: False +savefig.facecolor: white + +# Image properties +image.cmap: viridis +image.aspect: auto diff --git a/references/color_palettes.md b/references/color_palettes.md new file mode 100644 index 0000000..4dcf98f --- /dev/null +++ b/references/color_palettes.md @@ -0,0 +1,348 @@ +# 科学配色方案与使用指南 + +## 概述 + +科学可视化中的颜色选择对于可访问性、清晰度以及准确的数据表示至关重要。本参考资料提供了色盲友好的配色方案和颜色使用的最佳实践。 + +## 色盲友好配色方案 + +### Okabe-Ito 配色方案(推荐用于分类数据) + +Okabe-Ito 配色方案专门设计为让所有形式的色盲人群都能区分。 + +```python +# Okabe-Ito 颜色(RGB 值) +okabe_ito = { + 'orange': '#E69F00', # RGB: (230, 159, 0) + 'sky_blue': '#56B4E9', # RGB: (86, 180, 233) + 'bluish_green': '#009E73', # RGB: (0, 158, 115) + 'yellow': '#F0E442', # RGB: (240, 228, 66) + 'blue': '#0072B2', # RGB: (0, 114, 178) + 'vermillion': '#D55E00', # RGB: (213, 94, 0) + 'reddish_purple': '#CC79A7', # RGB: (204, 121, 167) + 'black': '#000000' # RGB: (0, 0, 0) +} +``` + +**在 Matplotlib 中使用:** +```python +import matplotlib.pyplot as plt + +colors = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7', '#000000'] +plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors) +``` + +**在 Seaborn 中使用:** +```python +import seaborn as sns + +okabe_ito_palette = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7'] +sns.set_palette(okabe_ito_palette) +``` + +**在 Plotly 中使用:** +```python +import plotly.graph_objects as go + +okabe_ito_plotly = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7'] +fig = go.Figure() +# 应用于离散色阶 +``` + +### Wong 配色方案(分类数据的替代方案) + +另一个优秀的色盲友好配色方案,由 Bang Wong 设计(Nature Methods 期刊)。 + +```python +wong_palette = { + 'black': '#000000', + 'orange': '#E69F00', + 'sky_blue': '#56B4E9', + 'green': '#009E73', + 'yellow': '#F0E442', + 'blue': '#0072B2', + 'vermillion': '#D55E00', + 'purple': '#CC79A7' +} +``` + +### Paul Tol 配色方案 + +Paul Tol 设计了多个针对不同使用场景优化过的科学配色方案。 + +**明亮色板(最多 7 个类别):** +```python +tol_bright = ['#4477AA', '#EE6677', '#228833', '#CCBB44', + '#66CCEE', '#AA3377', '#BBBBBB'] +``` + +**柔和色板(最多 9 个类别):** +```python +tol_muted = ['#332288', '#88CCEE', '#44AA99', '#117733', + '#999933', '#DDCC77', '#CC6677', '#882255', '#AA4499'] +``` + +**高对比度(仅 3 个类别):** +```python +tol_high_contrast = ['#004488', '#DDAA33', '#BB5566'] +``` + +## 顺序色图(连续数据) + +顺序色图使用单一色相表示从低到高的数据值。 + +### 感知均匀色图 + +这些色图在整个色阶上具有均匀的感知变化。 + +**Viridis(Matplotlib 默认色图):** +- 色盲友好 +- 灰度打印效果好 +- 感知均匀 +```python +plt.imshow(data, cmap='viridis') +``` + +**Cividis:** +- 针对色盲观看者优化 +- 专为绿色盲/红色盲设计 +```python +plt.imshow(data, cmap='cividis') +``` + +**Plasma、Inferno、Magma:** +- viridis 的感知均匀替代方案 +- 适合不同的审美偏好 +```python +plt.imshow(data, cmap='plasma') +``` + +### 何时使用顺序色图 +- 显示强度的热力图 +- 地理高程数据 +- 概率分布 +- 任何单变量连续数据(低 → 高) + +## 发散色图(负值到正值) + +发散色图使用中性的中间色,两端使用两种对比色。 + +### 色盲安全的发散色图 + +**RdYlBu(红-黄-蓝):** +```python +plt.imshow(data, cmap='RdYlBu_r') # _r 反转:蓝色(低)到红色(高) +``` + +**PuOr(紫-橙):** +- 非常适合色盲观看者 +```python +plt.imshow(data, cmap='PuOr') +``` + +**BrBG(棕-蓝-绿):** +- 色盲可访问性好 +```python +plt.imshow(data, cmap='BrBG') +``` + +### 应避免的发散色图 +- **RdGn(红-绿)**:对红绿色盲人群有问题 +- **RdYlGn(红-黄-绿)**:同样的问题 + +### 何时使用发散色图 +- 相关矩阵 +- 变化/差异数据(正值 vs. 负值) +- 偏离中心值的数据 +- 温度异常 + +## 专用配色方案 + +### 基因组学/生物信息学 + +**序列类型标识:** +```python +# DNA/RNA 碱基 +nucleotide_colors = { + 'A': '#00CC00', # 绿色 + 'C': '#0000CC', # 蓝色 + 'G': '#FFB300', # 橙色 + 'T': '#CC0000', # 红色 + 'U': '#CC0000' # 红色(RNA) +} +``` + +**基因表达:** +- 使用顺序色图(viridis、YlOrRd)表示表达水平 +- 使用发散色图(RdBu)表示 log2 倍数变化 + +### 显微镜成像 + +**荧光通道:** +```python +# 传统荧光染料颜色(谨慎使用) +fluorophore_colors = { + 'DAPI': '#0000FF', # 蓝色 — DNA + 'GFP': '#00FF00', # 绿色(对色盲人群有问题) + 'RFP': '#FF0000', # 红色 + 'Cy5': '#FF00FF' # 品红色 +} + +# 色盲友好替代方案 +fluorophore_alt = { + 'Channel1': '#0072B2', # 蓝色 + 'Channel2': '#E69F00', # 橙色(替代绿色) + 'Channel3': '#D55E00', # 朱红色 + 'Channel4': '#CC79A7' # 品红色 +} +``` + +## 颜色使用最佳实践 + +### 分类数据(定性配色方案) + +**应遵循:** +- 使用 Okabe-Ito 或 Wong 配色方案中鲜明、可区分的颜色 +- 单个图表中最多限制在 7–8 个类别 +- 跨图表对同一类别使用一致的颜色 +- 当仅凭颜色可能不足以区分时,添加图案/标记 + +**应避免:** +- 使用红/绿组合 +- 使用彩虹(jet)色图表示分类数据 +- 使用难以区分的相近色相 + +### 连续数据(顺序/发散方案) + +**应遵循:** +- 使用感知均匀色图(viridis、plasma、cividis) +- 当数据具有有意义的中心点时选择发散色图 +- 包含带标注刻度的色标 +- 测试灰度下的显示效果 + +**应避免:** +- 使用彩虹(jet)色图 — 感知不均匀 +- 使用红-绿发散色图 +- 在热力图中省略色标 + +## 色盲可访问性测试 + +### 在线模拟器 +- **Coblis**:https://www.color-blindness.com/coblis-color-blindness-simulator/ +- **Color Oracle**:免费可下载工具,支持 Windows/Mac/Linux +- **Sim Daltonism**:Mac 应用程序 + +### 色觉缺陷类型 +- **绿色盲**(约 5% 的男性):无法区分绿色 +- **红色盲**(约 2% 的男性):无法区分红色 +- **蓝色盲**(低于 1%):无法区分蓝色(罕见) + +### Python 工具 +```python +# 使用 colorspacious 模拟色盲视觉 +from colorspacious import cspace_convert + +def simulate_deuteranopia(image_rgb): + from colorspacious import cspace_convert + # 转换为色盲模拟 + # (实现需要 colorspacious 库) + pass +``` + +## 实现示例 + +### 设置全局 Matplotlib 样式 +```python +import matplotlib.pyplot as plt +import matplotlib as mpl + +# 将 Okabe-Ito 设置为默认颜色循环 +okabe_ito_colors = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7'] +mpl.rcParams['axes.prop_cycle'] = mpl.cycler(color=okabe_ito_colors) + +# 将默认色图设置为 viridis +mpl.rcParams['image.cmap'] = 'viridis' +``` + +### 使用自定义色板的 Seaborn +```python +import seaborn as sns + +# 设置 Paul Tol 柔和色板 +tol_muted = ['#332288', '#88CCEE', '#44AA99', '#117733', + '#999933', '#DDCC77', '#CC6677', '#882255', '#AA4499'] +sns.set_palette(tol_muted) + +# 用于热力图 +sns.heatmap(data, cmap='viridis', annot=True) +``` + +### 带离散颜色的 Plotly +```python +import plotly.express as px + +# 使用 Okabe-Ito 表示分类数据 +okabe_ito_plotly = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7'] + +fig = px.scatter(df, x='x', y='y', color='category', + color_discrete_sequence=okabe_ito_plotly) +``` + +## 灰度兼容性 + +所有图表应保持灰度下的可读性。通过转换为灰度进行测试: + +```python +# 将图表转换为灰度以便测试 +fig.savefig('figure_gray.png', dpi=300, colormap='gray') +``` + +**灰度兼容性策略:** +1. 使用不同的线型(实线、虚线、点线) +2. 使用不同的标记形状(圆形、方形、三角形) +3. 为条形添加填充图案 +4. 确保颜色之间有足够的亮度对比度 + +## 色彩空间 + +### RGB 与 CMYK +- **RGB(红、绿、蓝)**:用于数字/屏幕显示 +- **CMYK(青、品红、黄、黑)**:用于印刷 + +**重要提示:** 印刷品与屏幕上的颜色呈现不同。准备印刷材料时: +1. 转换为 CMYK 色彩空间 +2. 在 CMYK 预览中检查颜色表现 +3. 确保保留足够的对比度 + +### Matplotlib 色彩空间 +```python +# 保存用于印刷(CMYK) +# 注意:直接 CMYK 支持有限;使用 PDF 并让出版方转换 +fig.savefig('figure.pdf', dpi=300) + +# 用于 RGB(数字显示) +fig.savefig('figure.png', dpi=300) +``` + +## 常见错误 + +1. **使用 jet/rainbow 色图**:感知不均匀,应避免 +2. **红-绿组合**:约 8% 的男性无法区分 +3. **颜色过多**:超过 7–8 种颜色后难以区分 +4. **颜色含义不一致**:同一颜色在不同图表中应表示相同含义 +5. **缺少色标**:连续数据始终应包含色标 +6. **对比度过低**:确保颜色之间有足够的差异 +7. **仅依赖颜色区分**:应添加纹理、图案或标记 + +## 资源 + +- **ColorBrewer**:http://colorbrewer2.org/ — 通过色盲安全选项选择配色方案 +- **Paul Tol 配色方案**:https://personal.sron.nl/~pault/ +- **Okabe-Ito 配色方案来源**:《Color Universal Design》(Okabe & Ito, 2008) +- **Matplotlib 色图**:https://matplotlib.org/stable/tutorials/colors/colormaps.html +- **Seaborn 调色板**:https://seaborn.pydata.org/tutorial/color_palettes.html diff --git a/references/journal_requirements.md b/references/journal_requirements.md new file mode 100644 index 0000000..e58c628 --- /dev/null +++ b/references/journal_requirements.md @@ -0,0 +1,320 @@ +# 期刊专属图片要求 + +## 概述 + +不同期刊对图片有特定的技术要求。本参考文档汇总了主要学术出版商的常见要求。**请务必查阅具体期刊的作者指南以获取最新要求。** + +## Nature 系列(Nature、Nature Methods 等) + +### 技术规格 +- **文件格式**: + - 矢量图:PDF、EPS、AI(图表首选) + - 位图:TIFF、PNG(用于图像) + - 禁止使用:PowerPoint、Word、JPEG + +- **分辨率**: + - 线条图:1000–1200 DPI + - 组合图(线条图 + 图像):600 DPI + - 照片/显微图像:最低 300 DPI + +- **色彩模式**:RGB(Nature 以数字版优先) + +- **尺寸**: + - 单栏:89 mm(3.5 英寸) + - 1.5 栏:120 mm(4.7 英寸) + - 双栏:183 mm(7.2 英寸) + - 最大高度:247 mm(9.7 英寸) + +- **字体**: + - Arial 或 Helvetica(或类似无衬线字体) + - 最终尺寸下最小 5–7 pt + - 所有 PDF/EPS 文件需嵌入字体 + +### Nature 特定指南 +- 面板标签:a、b、c(小写加粗),位于左上角 +- 显微图像必须带有比例尺 +- 凝胶图像:需包含分子量标记 +- 裁剪:需用分隔线标示 +- 统计:标明显著性;在图例中定义符号 +- 源数据:所有图表均需提供 + +### 文件命名 +格式:`FirstAuthorLastName_FigureNumber.ext` +示例:`Smith_Fig1.pdf` + +## Science(AAAS) + +### 技术规格 +- **文件格式**: + - 矢量图:EPS、PDF(首选) + - 位图:TIFF + - 可接受:AI、PSD(Photoshop) + +- **分辨率**: + - 线条图:最低 1000 DPI + - 照片:最低 300 DPI + - 组合图:最低 600 DPI + +- **色彩模式**:RGB + +- **尺寸**: + - 单栏:5.5 cm(2.17 英寸) + - 1.5 栏:12 cm(4.72 英寸) + - 全宽:17.5 cm(6.89 英寸) + - 最大高度:23.3 cm(9.17 英寸) + +- **字体**: + - Helvetica(或 Arial) + - 最终尺寸下最小 6–8 pt + - 所有图片字体须一致 + +### Science 特定指南 +- 面板标签:(A)、(B)、(C) 加括号 +- 图中文字尽量精简(详细信息放图注) +- 兼顾网页与印刷的高对比度 +- 必须标注误差线;在图注中说明 +- 避免过多空白 + +### 文件命名 +格式:`Manuscript#_Fig#.ext` +示例:`abn1234_Fig1.eps` + +## Cell Press(Cell、Neuron、Molecular Cell 等) + +### 技术规格 +- **文件格式**: + - 矢量图:PDF、EPS(图表/示意图首选) + - 位图:TIFF(用于照片) + +- **分辨率**: + - 线条图:1000 DPI + - 照片:300 DPI + - 组合图:600 DPI + +- **色彩模式**:RGB + +- **尺寸**: + - 单栏:85 mm(3.35 英寸) + - 双栏:178 mm(7.01 英寸) + - 最大高度:230 mm(9.06 英寸) + +- **字体**: + - 仅限 Arial 或 Helvetica + - 坐标轴标签:8–12 pt + - 刻度标签:6–8 pt + +### Cell Press 特定指南 +- 面板标签:(A)、(B)、(C) 或 A、B、C,位于左上角 +- 相关面板尺寸应一致 +- 显微图片必须带比例尺 +- Western blot:需包含分子量标记 +- 箭头/箭头符号:最小线宽 2 pt +- 线条宽度:数据线条 1–2 pt + +## PLOS(公共科学图书馆) + +### 技术规格 +- **文件格式**: + - 矢量图:EPS、PDF(首选) + - 位图:TIFF、PNG + - TIFF 可使用 LZW 压缩 + +- **分辨率**: + - 最终尺寸下所有图片类型最低 300 DPI + - 线条图建议 600 DPI + +- **色彩模式**:RGB + +- **尺寸**: + - 单栏:8.3 cm(3.27 英寸) + - 1.5 栏:11.4 cm(4.49 英寸) + - 双栏:17.3 cm(6.81 英寸) + - 最大高度:23.3 cm(9.17 英寸) + +- **字体**: + - 推荐无衬线字体(Arial、Helvetica) + - 最终尺寸下标签 8–12 pt + +### PLOS 特定指南 +- 图片应在不依赖图注的情况下可理解 +- 仅在能增加信息量时使用彩色 +- 所有图片应可转换为灰度 +- 面板标签可选但建议使用 +- 开放获取:图片必须采用 CC-BY 许可 +- 鼓励提供源数据文件 + +## ACS(美国化学会) + +### 技术规格 +- **文件格式**: + - 首选:TIFF、PDF、EPS + - 应用程序文件:AI、CDX(ChemDraw)、CDL + - 可接受:PNG(不可用于正式出版) + +- **分辨率**: + - 最终尺寸下最低 300 DPI + - 线条图与化学结构式:600 DPI + - 精细结构式:1200 DPI + +- **色彩模式**:RGB 或 CMYK(请查阅具体期刊要求) + +- **尺寸**: + - 单栏:3.25 英寸(8.25 cm) + - 双栏:7 英寸(17.78 cm) + +- **字体**: + - 必须嵌入字体 + - 所有图片字体大小保持一致 + +### ACS 特定指南 +- 化学结构式:使用 ChemDraw 或同等软件 +- 原子标签:10–12 pt +- 键线粗细:2 pt +- 面板标签:小写加粗(a、b、c) +- 需要高对比度(许多 ACS 期刊为灰度印刷) + +## Elsevier 期刊(因期刊而异) + +### 技术规格 +- **文件格式**: + - 矢量图:EPS、PDF + - 位图:TIFF、JPEG(仅用于照片) + +- **分辨率**: + - 线条图:最低 1000 DPI + - 照片:最低 300 DPI + - 组合图:最低 600 DPI + +- **色彩模式**:RGB(在线版);CMYK(印刷版期刊) + +- **尺寸**:因期刊而异 + - 常见单栏宽度:90 mm + - 常见双栏宽度:190 mm + +- **字体**: + - 推荐:Arial、Times、Symbol + - 最终尺寸下最小 6 pt + +### Elsevier 特定指南 +- 请查阅各期刊的具体指南(差异较大) +- 部分期刊对彩色印刷收费 +- 面板标签通常为 (A)、(B)、(C) 或 A、B、C +- 通常需要图文摘要(与正文图片分开提交) + +## IEEE(工程/计算机科学) + +### 技术规格 +- **文件格式**: + - 矢量图:PDF、EPS(首选) + - 位图:TIFF、PNG + +- **分辨率**: + - 照片/图形:最终尺寸下最低 300 DPI + - 线条图:最低 600 DPI + +- **色彩模式**:RGB(在线版);CMYK(印刷版) + +- **尺寸**: + - 单栏:3.5 英寸(8.9 cm) + - 双栏:7.16 英寸(18.2 cm) + +- **字体**: + - 推荐无衬线字体 + - 最终尺寸下最小 8–10 pt + +### IEEE 特定指南 +- 图片应在黑白模式下可读 +- 彩色图片不另行收费(在线出版) +- 面板标签:(a)、(b)、(c) 小写 +- 图注位于图片下方(不另起一页) +- 投稿前请使用 IEEE 图片检查工具 + +## BMC(BioMed Central)——开放获取 + +### 技术规格 +- **文件格式**: + - 接受所有标准格式 + - 首选:TIFF、PDF、EPS、PNG + +- **分辨率**: + - 线条图最低 600 DPI + - 照片最低 300 DPI + +- **色彩模式**:RGB + +- **尺寸**: + - 较灵活,但需考虑可读性 + - 最大宽度通常为 140 mm + +- **字体**: + - 需嵌入且清晰可读 + +### BMC 特定指南 +- 开放获取:必须采用 CC-BY 许可 +- 图片文件单独上传 +- 面板标签根据学科惯例标注 +- 鼓励提供源数据 +- 注重无障碍性(色盲友好) + +## 期刊通用要求 + +### 通用最佳实践 +1. **切勿对图表使用 JPEG**:压缩会产生伪影 +2. **嵌入所有字体**:PDF/EPS 文件中 +3. **图层结构**:展平图像(在 Photoshop 中合并图层) +4. **RGB 与 CMYK**:大多数期刊现已采用 RGB(以数字版优先) +5. **高分辨率**:起步时始终设高分辨率,必要时再降低 +6. **一致性**:稿件中所有图片风格统一 +7. **文件大小**:在品质与合理文件大小之间取得平衡(通常每张图片不超过 10 MB) + +### 图片提交 +- **初稿提交**:通常可接受较低分辨率(供审稿用) +- **修改/接收阶段**:需提交高分辨率版本 +- **单独文件**:每张图片作为单独文件提交 +- **文件命名**:清晰、系统化的命名方式 +- **补充材料**:可能有不同要求 + +## 快速参考表 + +| 出版商 | 单栏 | 双栏 | 照片最低 DPI | 线条图最低 DPI | 首选格式 | +|--------|------|------|-------------|--------------|---------| +| Nature | 89 mm | 183 mm | 300 | 1000 | EPS、PDF | +| Science | 5.5 cm | 17.5 cm | 300 | 1000 | EPS、PDF | +| Cell Press | 85 mm | 178 mm | 300 | 1000 | EPS、PDF | +| PLOS | 8.3 cm | 17.3 cm | 300 | 600 | EPS、TIFF | +| ACS | 3.25 英寸 | 7 英寸 | 300 | 600 | TIFF、EPS | + +## 检查要求 + +### 投稿前检查清单 +1. 阅读期刊作者指南中的图片部分 +2. 检查文件格式要求 +3. 确认分辨率要求 +4. 核对尺寸规格(宽 × 高) +5. 检查字体要求 +6. 确认色彩模式(RGB 与 CMYK) +7. 检查面板标签样式 +8. 审阅补充材料要求 +9. 确认文件命名规范 +10. 检查文件大小限制 + +### 实用工具 +- **ImageJ/Fiji**:检查/调整 DPI +- **Adobe Acrobat**:验证嵌入字体,检查 PDF 属性 +- **GIMP**:免费的位图编辑替代软件(Photoshop 替代品) +- **Inkscape**:免费的矢量图形编辑器 + +## 资源 + +- **期刊网站**:始终查阅"作者指南"或"投稿须知" +- **出版商资源**:许多出版商提供模板和工具 +- **格式转换**:使用可靠的转换工具;检查输出质量 +- **帮助台**:如有疑问请联系期刊工作人员 + +## 备注 + +- 要求会定期变更——请始终核实当前指南 +- 预印本服务器(bioRxiv、arXiv)通常有不同的要求 +- 会议论文集可能有单独的要求 +- 部分期刊提供图片制作服务(通常为收费项目) +- 补充图片的要求可能比正文图片宽松 diff --git a/references/matplotlib_examples.md b/references/matplotlib_examples.md new file mode 100644 index 0000000..b8df85f --- /dev/null +++ b/references/matplotlib_examples.md @@ -0,0 +1,620 @@ +# 可发表级别的 Matplotlib 示例 + +## 概述 + +本参考文档提供了使用 Matplotlib、Seaborn 和 Plotly 创建可发表级别的科学示意图的实用代码示例。所有示例均遵循 `publication_guidelines.md` 中的最佳实践,并使用 `color_palettes.md` 中的色盲友好调色板。 + +## 设置与配置 + +### 发表级别 Matplotlib 配置 + +```python +import matplotlib.pyplot as plt +import matplotlib as mpl +import numpy as np + +# 设置发表质量参数 +mpl.rcParams['figure.dpi'] = 300 +mpl.rcParams['savefig.dpi'] = 300 +mpl.rcParams['font.size'] = 8 +mpl.rcParams['font.family'] = 'sans-serif' +mpl.rcParams['font.sans-serif'] = ['Arial', 'Helvetica'] +mpl.rcParams['axes.labelsize'] = 9 +mpl.rcParams['axes.titlesize'] = 9 +mpl.rcParams['xtick.labelsize'] = 7 +mpl.rcParams['ytick.labelsize'] = 7 +mpl.rcParams['legend.fontsize'] = 7 +mpl.rcParams['axes.linewidth'] = 0.5 +mpl.rcParams['xtick.major.width'] = 0.5 +mpl.rcParams['ytick.major.width'] = 0.5 +mpl.rcParams['lines.linewidth'] = 1.5 + +# 使用色盲友好颜色(Okabe-Ito 调色板) +okabe_ito = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', + '#0072B2', '#D55E00', '#CC79A7', '#000000'] +mpl.rcParams['axes.prop_cycle'] = mpl.cycler(color=okabe_ito) + +# 使用感知均匀的色图 +mpl.rcParams['image.cmap'] = 'viridis' +``` + +### 保存辅助函数 + +```python +def save_publication_figure(fig, filename, formats=['pdf', 'png'], dpi=300): + """ + 以多种格式保存图形以供发表。 + + 参数 + ----------- + fig : matplotlib.figure.Figure + 要保存的图形 + filename : str + 基础文件名(不含扩展名) + formats : list + 要保存的文件格式列表 ['pdf', 'png', 'eps', 'svg'] + dpi : int + 光栅格式的分辨率 + """ + for fmt in formats: + output_file = f"{filename}.{fmt}" + fig.savefig(output_file, dpi=dpi, bbox_inches='tight', + facecolor='white', edgecolor='none', + transparent=False, format=fmt) + print(f"已保存: {output_file}") +``` + +## 示例 1:带误差线的折线图 + +```python +import matplotlib.pyplot as plt +import numpy as np + +# 生成示例数据 +x = np.linspace(0, 10, 50) +y1 = 2 * x + 1 + np.random.normal(0, 1, 50) +y2 = 1.5 * x + 2 + np.random.normal(0, 1.2, 50) + +# 计算分箱数据的均值和标准误差 +bins = np.linspace(0, 10, 11) +y1_mean = [y1[(x >= bins[i]) & (x < bins[i+1])].mean() for i in range(len(bins)-1)] +y1_sem = [y1[(x >= bins[i]) & (x < bins[i+1])].std() / + np.sqrt(len(y1[(x >= bins[i]) & (x < bins[i+1])])) + for i in range(len(bins)-1)] +x_binned = (bins[:-1] + bins[1:]) / 2 + +# 创建合适大小的图形(单栏宽度 = 3.5 英寸) +fig, ax = plt.subplots(figsize=(3.5, 2.5)) + +# 绘制带误差线的图 +ax.errorbar(x_binned, y1_mean, yerr=y1_sem, + marker='o', markersize=4, capsize=3, capthick=0.5, + label='条件 A', linewidth=1.5) + +# 添加带单位的标签 +ax.set_xlabel('时间(小时)') +ax.set_ylabel('荧光强度(a.u.)') + +# 添加图例 +ax.legend(frameon=False, loc='upper left') + +# 移除顶部和右侧的轴脊线 +ax.spines['top'].set_visible(False) +ax.spines['right'].set_visible(False) + +# 紧凑布局 +fig.tight_layout() + +# 保存 +save_publication_figure(fig, 'line_plot_with_errors') +plt.show() +``` + +## 示例 2:多面板图形 + +```python +import matplotlib.pyplot as plt +import numpy as np +from string import ascii_uppercase + +# 创建多面板图形(双栏宽度 = 7 英寸) +fig = plt.figure(figsize=(7, 4)) + +# 定义面板网格 +gs = fig.add_gridspec(2, 3, hspace=0.4, wspace=0.4, + left=0.08, right=0.98, top=0.95, bottom=0.08) + +# 面板 A:折线图 +ax_a = fig.add_subplot(gs[0, :2]) +x = np.linspace(0, 10, 100) +for i, offset in enumerate([0, 0.5, 1.0]): + ax_a.plot(x, np.sin(x) + offset, label=f'数据集 {i+1}') +ax_a.set_xlabel('时间(秒)') +ax_a.set_ylabel('振幅(V)') +ax_a.legend(frameon=False, fontsize=6) +ax_a.spines['top'].set_visible(False) +ax_a.spines['right'].set_visible(False) + +# 面板 B:柱状图 +ax_b = fig.add_subplot(gs[0, 2]) +categories = ['对照', '处理\nA', '处理\nB'] +values = [100, 125, 140] +errors = [5, 8, 6] +ax_b.bar(categories, values, yerr=errors, capsize=3, + color=['#0072B2', '#E69F00', '#009E73'], alpha=0.8) +ax_b.set_ylabel('响应(%)') +ax_b.spines['top'].set_visible(False) +ax_b.spines['right'].set_visible(False) +ax_b.set_ylim(0, 160) + +# 面板 C:散点图 +ax_c = fig.add_subplot(gs[1, 0]) +x = np.random.randn(100) +y = 2*x + np.random.randn(100) +ax_c.scatter(x, y, s=10, alpha=0.6, color='#0072B2') +ax_c.set_xlabel('变量 X') +ax_c.set_ylabel('变量 Y') +ax_c.spines['top'].set_visible(False) +ax_c.spines['right'].set_visible(False) + +# 面板 D:热图 +ax_d = fig.add_subplot(gs[1, 1:]) +data = np.random.randn(10, 20) +im = ax_d.imshow(data, cmap='viridis', aspect='auto') +ax_d.set_xlabel('样本编号') +ax_d.set_ylabel('特征') +cbar = plt.colorbar(im, ax=ax_d, fraction=0.046, pad=0.04) +cbar.set_label('强度(a.u.)', rotation=270, labelpad=12) + +# 添加面板标签 +panels = [ax_a, ax_b, ax_c, ax_d] +for i, ax in enumerate(panels): + ax.text(-0.15, 1.05, ascii_uppercase[i], transform=ax.transAxes, + fontsize=10, fontweight='bold', va='top') + +save_publication_figure(fig, 'multi_panel_figure') +plt.show() +``` + +## 示例 3:带散点的箱线图 + +```python +import matplotlib.pyplot as plt +import numpy as np + +# 生成示例数据 +np.random.seed(42) +data = [np.random.normal(100, 15, 30), + np.random.normal(120, 20, 30), + np.random.normal(140, 18, 30), + np.random.normal(110, 22, 30)] + +fig, ax = plt.subplots(figsize=(3.5, 3)) + +# 创建箱线图 +bp = ax.boxplot(data, widths=0.5, patch_artist=True, + showfliers=False, # 我们将手动添加散点 + boxprops=dict(facecolor='lightgray', edgecolor='black', linewidth=0.8), + medianprops=dict(color='black', linewidth=1.5), + whiskerprops=dict(linewidth=0.8), + capprops=dict(linewidth=0.8)) + +# 叠加个体散点 +colors = ['#0072B2', '#E69F00', '#009E73', '#D55E00'] +for i, (d, color) in enumerate(zip(data, colors)): + # 为 x 位置添加抖动 + x = np.random.normal(i+1, 0.04, size=len(d)) + ax.scatter(x, d, alpha=0.4, s=8, color=color) + +# 自定义 +ax.set_xticklabels(['对照', '处理 A', '处理 B', '处理 C']) +ax.set_ylabel('细胞计数') +ax.spines['top'].set_visible(False) +ax.spines['right'].set_visible(False) +ax.set_ylim(50, 200) + +fig.tight_layout() +save_publication_figure(fig, 'boxplot_with_points') +plt.show() +``` + +## 示例 4:带颜色条的热图 + +```python +import matplotlib.pyplot as plt +import numpy as np + +# 生成相关矩阵 +np.random.seed(42) +n = 10 +A = np.random.randn(n, n) +corr_matrix = np.corrcoef(A) + +# 创建图形 +fig, ax = plt.subplots(figsize=(4, 3.5)) + +# 绘制热图 +im = ax.imshow(corr_matrix, cmap='RdBu_r', vmin=-1, vmax=1, aspect='auto') + +# 添加颜色条 +cbar = plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04) +cbar.set_label('相关系数', rotation=270, labelpad=15) + +# 设置刻度和标签 +gene_names = [f'基因{i+1}' for i in range(n)] +ax.set_xticks(np.arange(n)) +ax.set_yticks(np.arange(n)) +ax.set_xticklabels(gene_names, rotation=45, ha='right') +ax.set_yticklabels(gene_names) + +# 添加网格 +ax.set_xticks(np.arange(n)-.5, minor=True) +ax.set_yticks(np.arange(n)-.5, minor=True) +ax.grid(which='minor', color='white', linestyle='-', linewidth=0.5) + +fig.tight_layout() +save_publication_figure(fig, 'correlation_heatmap') +plt.show() +``` + +## 示例 5:Seaborn 小提琴图 + +```python +import matplotlib.pyplot as plt +import seaborn as sns +import pandas as pd +import numpy as np + +# 生成示例数据 +np.random.seed(42) +data = pd.DataFrame({ + 'condition': np.repeat(['对照', '药物 A', '药物 B'], 50), + 'value': np.concatenate([ + np.random.normal(100, 15, 50), + np.random.normal(120, 20, 50), + np.random.normal(140, 18, 50) + ]) +}) + +# 设置样式 +sns.set_style('ticks') +sns.set_palette(['#0072B2', '#E69F00', '#009E73']) + +fig, ax = plt.subplots(figsize=(3.5, 3)) + +# 创建小提琴图 +sns.violinplot(data=data, x='condition', y='value', ax=ax, + inner='box', linewidth=0.8) + +# 添加带状图 +sns.stripplot(data=data, x='condition', y='value', ax=ax, + size=2, alpha=0.3, color='black') + +# 自定义 +ax.set_xlabel('') +ax.set_ylabel('表达水平(AU)') +ax.spines['top'].set_visible(False) +ax.spines['right'].set_visible(False) + +fig.tight_layout() +save_publication_figure(fig, 'violin_plot') +plt.show() +``` + +## 示例 6:带回归线的科学散点图 + +```python +import matplotlib.pyplot as plt +import numpy as np +from scipy import stats + +# 生成具有相关性的数据 +np.random.seed(42) +x = np.random.randn(100) +y = 2.5 * x + np.random.randn(100) * 0.8 + +# 计算回归 +slope, intercept, r_value, p_value, std_err = stats.linregress(x, y) + +# 创建图形 +fig, ax = plt.subplots(figsize=(3.5, 3.5)) + +# 散点图 +ax.scatter(x, y, s=15, alpha=0.6, color='#0072B2', edgecolors='none') + +# 回归线 +x_line = np.array([x.min(), x.max()]) +y_line = slope * x_line + intercept +ax.plot(x_line, y_line, 'r-', linewidth=1.5, label=f'y = {slope:.2f}x + {intercept:.2f}') + +# 添加统计文本 +stats_text = f'$R^2$ = {r_value**2:.3f}\n$p$ < 0.001' if p_value < 0.001 else f'$R^2$ = {r_value**2:.3f}\n$p$ = {p_value:.3f}' +ax.text(0.05, 0.95, stats_text, transform=ax.transAxes, + verticalalignment='top', fontsize=7, + bbox=dict(boxstyle='round', facecolor='white', alpha=0.8, edgecolor='gray', linewidth=0.5)) + +# 自定义 +ax.set_xlabel('预测变量') +ax.set_ylabel('响应变量') +ax.spines['top'].set_visible(False) +ax.spines['right'].set_visible(False) + +fig.tight_layout() +save_publication_figure(fig, 'scatter_regression') +plt.show() +``` + +## 示例 7:带阴影误差的时间序列图 + +```python +import matplotlib.pyplot as plt +import numpy as np + +# 生成时间序列数据 +np.random.seed(42) +time = np.linspace(0, 24, 100) +n_replicates = 5 + +# 模拟多个重复 +data = np.array([10 * np.exp(-time/10) + np.random.normal(0, 0.5, 100) + for _ in range(n_replicates)]) + +# 计算均值和标准误差 +mean = data.mean(axis=0) +sem = data.std(axis=0) / np.sqrt(n_replicates) + +# 创建图形 +fig, ax = plt.subplots(figsize=(4, 2.5)) + +# 绘制均值线 +ax.plot(time, mean, linewidth=1.5, color='#0072B2', label='均值 ± SEM') + +# 添加阴影误差区域 +ax.fill_between(time, mean - sem, mean + sem, + alpha=0.3, color='#0072B2', linewidth=0) + +# 自定义 +ax.set_xlabel('时间(小时)') +ax.set_ylabel('浓度(μM)') +ax.legend(frameon=False, loc='upper right') +ax.spines['top'].set_visible(False) +ax.spines['right'].set_visible(False) +ax.set_xlim(0, 24) +ax.set_ylim(0, 12) + +fig.tight_layout() +save_publication_figure(fig, 'timeseries_shaded') +plt.show() +``` + +## 示例 8:Plotly 交互式图形 + +```python +import plotly.graph_objects as go +import numpy as np + +# 生成数据 +np.random.seed(42) +x = np.random.randn(100) +y = 2*x + np.random.randn(100) +colors = np.random.choice(['组 A', '组 B'], 100) + +# Plotly 的 Okabe-Ito 颜色 +okabe_ito_plotly = ['#E69F00', '#56B4E9'] + +# 创建图形 +fig = go.Figure() + +for group, color in zip(['组 A', '组 B'], okabe_ito_plotly): + mask = colors == group + fig.add_trace(go.Scatter( + x=x[mask], y=y[mask], + mode='markers', + name=group, + marker=dict(size=6, color=color, opacity=0.6) + )) + +# 更新布局以达到发表质量 +fig.update_layout( + width=500, + height=400, + font=dict(family='Arial, sans-serif', size=10), + plot_bgcolor='white', + xaxis=dict( + title='变量 X', + showgrid=False, + showline=True, + linewidth=1, + linecolor='black', + mirror=False + ), + yaxis=dict( + title='变量 Y', + showgrid=False, + showline=True, + linewidth=1, + linecolor='black', + mirror=False + ), + legend=dict( + x=0.02, + y=0.98, + bgcolor='rgba(255,255,255,0.8)', + bordercolor='gray', + borderwidth=0.5 + ) +) + +# 保存为静态图像(需要 kaleido) +fig.write_image('plotly_scatter.png', width=500, height=400, scale=3) # scale=3 约等于 300 DPI +fig.write_html('plotly_scatter.html') # 交互式版本 + +fig.show() +``` + +## 示例 9:带显著性标记的分组柱状图 + +```python +import matplotlib.pyplot as plt +import numpy as np + +# 数据 +categories = ['WT', '突变体 A', '突变体 B'] +control_means = [100, 85, 70] +control_sem = [5, 6, 5] +treatment_means = [100, 120, 140] +treatment_sem = [6, 8, 9] + +x = np.arange(len(categories)) +width = 0.35 + +fig, ax = plt.subplots(figsize=(3.5, 3)) + +# 创建柱状条 +bars1 = ax.bar(x - width/2, control_means, width, yerr=control_sem, + capsize=3, label='对照', color='#0072B2', alpha=0.8) +bars2 = ax.bar(x + width/2, treatment_means, width, yerr=treatment_sem, + capsize=3, label='处理', color='#E69F00', alpha=0.8) + +# 添加显著性标记 +def add_significance_bar(ax, x1, x2, y, h, text): + """在两个柱状条之间添加显著性标记线""" + ax.plot([x1, x1, x2, x2], [y, y+h, y+h, y], linewidth=0.8, c='black') + ax.text((x1+x2)/2, y+h, text, ha='center', va='bottom', fontsize=7) + +# 标记显著差异 +add_significance_bar(ax, x[1]-width/2, x[1]+width/2, 135, 3, '***') +add_significance_bar(ax, x[2]-width/2, x[2]+width/2, 155, 3, '***') + +# 自定义 +ax.set_ylabel('活性(占 WT 对照的百分比)') +ax.set_xticks(x) +ax.set_xticklabels(categories) +ax.legend(frameon=False, loc='upper left') +ax.spines['top'].set_visible(False) +ax.spines['right'].set_visible(False) +ax.set_ylim(0, 180) + +# 添加显著性注释 +ax.text(0.98, 0.02, '*** p < 0.001', transform=ax.transAxes, + ha='right', va='bottom', fontsize=6) + +fig.tight_layout() +save_publication_figure(fig, 'grouped_bar_significance') +plt.show() +``` + +## 示例 10:Nature 级别可发表图形 + +```python +import matplotlib.pyplot as plt +import numpy as np +from string import ascii_lowercase + +# Nature 规格:89mm 单栏 +inch_per_mm = 0.0393701 +width_mm = 89 +height_mm = 110 +figsize = (width_mm * inch_per_mm, height_mm * inch_per_mm) + +fig = plt.figure(figsize=figsize) +gs = fig.add_gridspec(3, 2, hspace=0.5, wspace=0.4, + left=0.12, right=0.95, top=0.96, bottom=0.08) + +# 面板 a:时间过程 +ax_a = fig.add_subplot(gs[0, :]) +time = np.linspace(0, 48, 100) +for i, label in enumerate(['对照', '处理']): + y = (1 + i*0.5) * np.exp(-time/20) * (1 + 0.3*np.sin(time/5)) + ax_a.plot(time, y, linewidth=1.2, label=label) +ax_a.set_xlabel('时间(小时)', fontsize=7) +ax_a.set_ylabel('生长(OD$_{600}$)', fontsize=7) +ax_a.legend(frameon=False, fontsize=6) +ax_a.tick_params(labelsize=6) +ax_a.spines['top'].set_visible(False) +ax_a.spines['right'].set_visible(False) + +# 面板 b:柱状图 +ax_b = fig.add_subplot(gs[1, 0]) +categories = ['A', 'B', 'C'] +values = [1.0, 1.5, 2.2] +errors = [0.1, 0.15, 0.2] +ax_b.bar(categories, values, yerr=errors, capsize=2, width=0.6, + color='#0072B2', alpha=0.8) +ax_b.set_ylabel('倍数变化', fontsize=7) +ax_b.tick_params(labelsize=6) +ax_b.spines['top'].set_visible(False) +ax_b.spines['right'].set_visible(False) + +# 面板 c:热图 +ax_c = fig.add_subplot(gs[1, 1]) +data = np.random.randn(8, 6) +im = ax_c.imshow(data, cmap='viridis', aspect='auto') +ax_c.set_xlabel('样本', fontsize=7) +ax_c.set_ylabel('基因', fontsize=7) +ax_c.tick_params(labelsize=6) + +# 面板 d:散点图 +ax_d = fig.add_subplot(gs[2, :]) +x = np.random.randn(50) +y = 2*x + np.random.randn(50)*0.5 +ax_d.scatter(x, y, s=8, alpha=0.6, color='#E69F00') +ax_d.set_xlabel('基因 X 表达量', fontsize=7) +ax_d.set_ylabel('基因 Y 表达量', fontsize=7) +ax_d.tick_params(labelsize=6) +ax_d.spines['top'].set_visible(False) +ax_d.spines['right'].set_visible(False) + +# 添加小写面板标签(Nature 风格) +for i, ax in enumerate([ax_a, ax_b, ax_c, ax_d]): + ax.text(-0.2, 1.1, f'{ascii_lowercase[i]}', transform=ax.transAxes, + fontsize=9, fontweight='bold', va='top') + +# 以 Nature 偏好的格式保存 +fig.savefig('nature_figure.pdf', dpi=1000, bbox_inches='tight', + facecolor='white', edgecolor='none') +fig.savefig('nature_figure.png', dpi=300, bbox_inches='tight', + facecolor='white', edgecolor='none') + +plt.show() +``` + +## 各库的使用技巧 + +### Matplotlib +- 使用 `fig.tight_layout()` 或 `constrained_layout=True` 防止重叠 +- 将 DPI 设置为 300–600 以用于发表 +- 折线图使用矢量格式(PDF、EPS) +- 在 PDF/EPS 文件中嵌入字体 + +### Seaborn +- 基于 matplotlib 构建,因此所有 matplotlib 的自定义功能均适用 +- 使用 `sns.set_style('ticks')` 或 `'whitegrid'` 以获得简洁外观 +- `sns.despine()` 移除顶部和右侧的轴脊线 +- 使用 `sns.set_palette()` 设置自定义调色板 + +### Plotly +- 非常适合交互式探索性分析 +- 使用 `fig.write_image()` 导出静态图像(需要 kaleido 包) +- 使用 `scale` 参数控制 DPI(scale=3 ≈ 300 DPI) +- 大量调整布局以达到发表质量 + +## 通用工作流程 + +1. **使用默认设置进行探索** +2. **应用发表配置**(参见「设置」部分) +3. **创建合适大小的图形**(查看期刊要求) +4. **自定义颜色**(使用色盲友好调色板) +5. **调整字体和线宽**(在最终尺寸下可读) +6. **去除图表垃圾元素**(顶部/右侧轴脊线、过多网格线) +7. **添加清晰的标签及单位** +8. **用灰度测试** +9. **以多种格式保存**(矢量用 PDF,光栅用 PNG) +10. **在最终上下文中验证**(导入稿件中检查尺寸) + +## 参考资料 + +- Matplotlib 文档:https://matplotlib.org/ +- Seaborn 画廊:https://seaborn.pydata.org/examples/index.html +- Plotly 文档:https://plotly.com/python/ +- Nature Methods Points of View:数据可视化专栏存档 diff --git a/references/publication_guidelines.md b/references/publication_guidelines.md new file mode 100644 index 0000000..9128a17 --- /dev/null +++ b/references/publication_guidelines.md @@ -0,0 +1,205 @@ +# 可投稿级别的图片制作指南 + +## 核心原则 + +科学图片必须清晰、准确且易于理解。可投稿级别的图片遵循以下基本原则: + +1. **清晰性**:信息应当能够被立即理解 +2. **准确性**:数据呈现必须真实且未经篡改 +3. **可访问性**:图片应能被所有读者解读,包括有视觉障碍的人士 +4. **专业性**:外观整洁精致,适合同行评审期刊 + +## 分辨率与文件格式 + +### 分辨率要求 +- **光栅图像(照片、显微图像)**:最终印刷尺寸下 300-600 DPI +- **线条图与图表**:600-1200 DPI(或矢量格式) +- **组合图片**:300-600 DPI + +### 文件格式 +- **矢量格式(图表/绘图首选)**:PDF、EPS、SVG + - 无限缩放,不损失质量 + - 线条图文件体积更小 + - 最佳用途:图表、示意图、结构图 + +- **光栅格式**:TIFF、PNG(科学数据绝不使用 JPEG) + - 用于:照片、显微图像、连续色调图像 + - TIFF:无损,被广泛接受 + - PNG:无损,适合网络和补充材料 + - **绝不使用 JPEG**:有损压缩会引入伪影 + +### 尺寸规范 +- **单栏**:宽 85-90 mm(3.35-3.54 英寸) +- **1.5 栏**:宽 114-120 mm(4.49-4.72 英寸) +- **双栏**:宽 174-180 mm(6.85-7.08 英寸) +- **最大高度**:通常为 230-240 mm(9-9.5 英寸) + +## 排版 + +### 字体指南 +- **字体系列**:大部分期刊使用无衬线字体(Arial、Helvetica、Calibri) + - 部分期刊要求特定字体(请查阅其投稿指南) + - 稿件中所有图片的字体应保持一致 + +- **最终印刷尺寸下的字号**: + - 坐标轴标签:最低 7-9 pt + - 刻度标签:最低 6-8 pt + - 图例:6-8 pt + - 面板标签(A、B、C):8-12 pt,加粗 + - 标题:多面板图片中通常不推荐使用 + +- **字重**:大部分文本使用常规字重;仅面板标签使用加粗 + +### 文本最佳实践 +- 坐标轴标签使用句首大写("Time (hours)",而非"TIME (HOURS)") +- 单位放在括号内 +- 除非空间受限,否则避免缩写(在图注中说明其全称) +- 最终尺寸下文本不得小于 5-6 pt + +## 颜色使用 + +### 颜色选择原则 +1. **色盲友好**:约 8% 的男性存在色觉障碍 + - 避免使用红/绿搭配 + - 使用蓝/橙、蓝/黄,或增加纹理/图案 + - 使用色盲模拟器进行测试 + +2. **有目的性的颜色**:颜色应传达含义,而非仅为美观 + - 使用颜色区分类别或突出关键数据 + - 跨图片保持颜色一致性(相同的处理 = 相同的颜色) + +3. **印刷考量**: + - 颜色在印刷品与屏幕上的显示效果可能不同 + - 印刷品使用 CMYK 色彩空间,数字版使用 RGB + - 确保足够的对比度(尤其要考虑灰度转换) + +### 推荐色板 +- **定性(分类)**:ColorBrewer、Okabe-Ito 色板 +- **顺序(从低到高)**:Viridis、Cividis、Blues、Oranges +- **发散(从负到正)**:RdBu、PuOr、BrBG(确保色盲安全) + +### 灰度兼容性 +- 所有图片应在灰度模式下仍可解读 +- 使用不同的线型(实线、虚线、点线)和标记 +- 为条形和区域添加填充纹理/斜线 + +## 布局与构图 + +### 多面板图片 +- **面板标签**:在左上角使用加粗大写字母(A、B、C) +- **间距**:面板之间留足空白 +- **对齐**:尽可能沿面板边缘或坐标轴对齐 +- **尺寸**:相关面板的尺寸应保持一致 +- **排列**:逻辑顺序(从左到右、从上到下) + +### 图表元素 + +#### 坐标轴 +- **轴线**:粗细 0.5-1 pt +- **刻度线**:一致地指向内侧或外侧 +- **刻度密度**:足以读取数值,不宜过密(通常 4-7 个主刻度) +- **坐标轴标签**:所有图表必须标注,且标明单位 +- **坐标轴范围**:条形图应从零开始(除非在科学上不合理) + +#### 线条与标记 +- **线宽**:数据线 1-2 pt;参考线 0.5-1 pt +- **标记大小**:3-6 pt,大于线宽 +- **标记类型**:多个数据系列时使用不同标记加以区分(圆形、方形、三角形) +- **误差棒**:宽 0.5-1 pt;若合适则加上端帽 + +#### 图例 +- **位置**:空间允许时放在绘图区域内,否则放在外部 +- **边框**:可选;如使用,则用细线(0.5 pt) +- **顺序**:与数据出现的顺序一致(从上到下或从左到右) +- **内容**:简洁描述;详细信息放在图注中 + +### 空白与边距 +- 去除图表周围不必要的空白 +- 保持一致的边距 +- 在 matplotlib 中使用 `tight_layout()` 或 `constrained_layout=True` + +## 数据呈现最佳实践 + +### 统计严谨性 +- **误差棒**:始终显示不确定性(SD、SEM、CI),并在图注中说明使用的是哪种 +- **样本量**:在图片或图注中注明 n +- **显著性**:清晰标注统计显著性(*、**、***) +- **重复实验**:尽可能展示单个数据点,而非仅显示汇总统计量 + +### 合适的图表类型 +- **条形图**:比较离散类别;纵轴始终从零开始 +- **折线图**:时间序列或连续关系 +- **散点图**:变量之间的相关性;若合适则添加回归线 +- **箱线图**:分布比较;显示异常值 +- **热力图**:矩阵数据、相关性、表达模式 +- **小提琴图**:分布形状比较(对于双峰数据优于箱线图) + +### 避免失真 +- **不使用 3D 效果**:会扭曲数值感知 +- **不使用不必要的装饰**:不使用渐变、阴影或图表垃圾 +- **一致的尺度**:可比对的面板使用相同的尺度 +- **不截断坐标轴**:除非有清晰标注且具备科学依据 +- **线性 vs 对数尺度**:选择合适的尺度;始终清晰标注 + +## 可访问性 + +### 色盲考量 +- 使用在线模拟器测试(如 Coblis、Color Oracle) +- 除颜色外,同时使用图案/纹理 +- 如有需要,在补充材料中提供替代呈现方式 + +### 视觉障碍 +- 元素之间高对比度 +- 线条足够粗(最低 0.5 pt) +- 布局清晰简洁 + +### 数据可用性 +- 在补充材料中包含数据表 +- 提供图表对应的源数据文件 +- 对于在线补充材料,可考虑交互式图片 + +## 需避免的常见错误 + +1. **字号太小**:文本在最终印刷尺寸下难以辨认 +2. **分辨率过低**:图像出现像素化或模糊 +3. **图表垃圾**:不必要的网格线、3D 效果、装饰 +4. **颜色选择不当**:红/绿搭配、对比度低 +5. **元素缺失**:缺少坐标轴标签、单位、误差棒 +6. **样式不一致**:同一图片内或跨图片使用不同的字体/字号 +7. **数据失真**:截断坐标轴、尺度不当、3D 效果 +8. **JPEG 压缩**:文字和线条周围出现伪影 +9. **信息过密**:在一张图表中塞入过多数据系列 +10. **图例不可访问**:导出后图例超出图片边界 + +## 图片检查清单 + +投稿前,请确认: + +- [ ] 分辨率满足期刊要求(光栅图像 300+ DPI) +- [ ] 文件格式可被接受(图表用矢量格式,图像用 TIFF/PNG) +- [ ] 图片尺寸符合期刊规范 +- [ ] 所有文本在最终尺寸下可读(最低 6-7 pt) +- [ ] 字体保持一致且已嵌入(针对 PDF/EPS) +- [ ] 颜色为色盲友好 +- [ ] 图片在灰度模式下仍可解读 +- [ ] 所有坐标轴均已标注且带有单位 +- [ ] 误差棒或不确定性指标已呈现 +- [ ] 统计显著性已标注(如适用) +- [ ] 面板标签已标注且保持一致(A、B、C) +- [ ] 图例清晰完整 +- [ ] 无图表垃圾或不必要元素 +- [ ] 文件命名遵循期刊惯例 +- [ ] 图片图注内容完整 +- [ ] 源数据已提供 + +## 各期刊特有注意事项 + +务必查阅具体期刊的作者投稿指南。常见差异包括: + +- **Nature 系列期刊**:RGB、最低 300 DPI、特定的尺寸要求 +- **Science**:EPS 或高分辨率 TIFF、特定的字体要求 +- **Cell Press**:首选 PDF 或 EPS、使用 Arial 或 Helvetica 字体 +- **PLOS**:TIFF 或 EPS、特定的色彩空间要求 +- **ACS 期刊**:应用程序文件(AI、EPS)或高分辨率 TIFF + +详细规范请参阅 `journal_requirements.md`。 diff --git a/scripts/figure_export.py b/scripts/figure_export.py new file mode 100644 index 0000000..0a643ff --- /dev/null +++ b/scripts/figure_export.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +""" +Figure Export Utilities for Publication-Ready Scientific Figures + +This module provides utilities to export matplotlib figures in publication-ready +formats with appropriate settings for various journals. +""" + +import matplotlib.pyplot as plt +from pathlib import Path +from typing import List, Optional, Union + + +def save_publication_figure( + fig: plt.Figure, + filename: Union[str, Path], + formats: List[str] = ['pdf', 'png'], + dpi: int = 300, + transparent: bool = False, + bbox_inches: str = 'tight', + pad_inches: float = 0.1, + facecolor: str = 'white', + **kwargs +) -> List[Path]: + """ + Save a matplotlib figure in multiple formats with publication-quality settings. + + Parameters + ---------- + fig : matplotlib.figure.Figure + The figure to save + filename : str or Path + Base filename (without extension) + formats : list of str, default ['pdf', 'png'] + List of file formats to save. Options: 'pdf', 'png', 'eps', 'svg', 'tiff' + dpi : int, default 300 + Resolution for raster formats (png, tiff). 300 DPI is minimum for most journals + transparent : bool, default False + If True, save with transparent background + bbox_inches : str, default 'tight' + Bounding box specification. 'tight' removes excess whitespace + pad_inches : float, default 0.1 + Padding around the figure when bbox_inches='tight' + facecolor : str, default 'white' + Background color (ignored if transparent=True) + **kwargs + Additional keyword arguments passed to fig.savefig() + + Returns + ------- + list of Path + List of paths to saved files + + Examples + -------- + >>> fig, ax = plt.subplots() + >>> ax.plot([1, 2, 3], [1, 4, 9]) + >>> save_publication_figure(fig, 'my_plot', formats=['pdf', 'png'], dpi=600) + ['my_plot.pdf', 'my_plot.png'] + """ + filename = Path(filename) + base_name = filename.stem + output_dir = filename.parent if filename.parent.exists() else Path.cwd() + + saved_files = [] + + for fmt in formats: + output_file = output_dir / f"{base_name}.{fmt}" + + # Set format-specific parameters + save_kwargs = { + 'dpi': dpi, + 'bbox_inches': bbox_inches, + 'pad_inches': pad_inches, + 'facecolor': facecolor if not transparent else 'none', + 'edgecolor': 'none', + 'transparent': transparent, + 'format': fmt, + } + + # Update with user-provided kwargs + save_kwargs.update(kwargs) + + # Adjust DPI for vector formats (DPI less relevant) + if fmt in ['pdf', 'eps', 'svg']: + save_kwargs['dpi'] = min(dpi, 300) # Lower DPI for embedded rasters in vector + + try: + fig.savefig(output_file, **save_kwargs) + saved_files.append(output_file) + print(f"✓ Saved: {output_file}") + except Exception as e: + print(f"✗ Failed to save {output_file}: {e}") + + return saved_files + + +def save_for_journal( + fig: plt.Figure, + filename: Union[str, Path], + journal: str, + figure_type: str = 'combination' +) -> List[Path]: + """ + Save figure with journal-specific requirements. + + Parameters + ---------- + fig : matplotlib.figure.Figure + The figure to save + filename : str or Path + Base filename (without extension) + journal : str + Journal name. Options: 'nature', 'science', 'cell', 'plos', 'acs', 'ieee' + figure_type : str, default 'combination' + Type of figure. Options: 'line_art', 'photo', 'combination' + + Returns + ------- + list of Path + List of paths to saved files + + Examples + -------- + >>> fig, ax = plt.subplots() + >>> ax.plot([1, 2, 3], [1, 4, 9]) + >>> save_for_journal(fig, 'figure1', journal='nature', figure_type='line_art') + """ + journal = journal.lower() + + # Define journal-specific requirements + journal_specs = { + 'nature': { + 'line_art': {'formats': ['pdf', 'eps'], 'dpi': 1000}, + 'photo': {'formats': ['tiff'], 'dpi': 300}, + 'combination': {'formats': ['pdf'], 'dpi': 600}, + }, + 'science': { + 'line_art': {'formats': ['eps', 'pdf'], 'dpi': 1000}, + 'photo': {'formats': ['tiff'], 'dpi': 300}, + 'combination': {'formats': ['eps'], 'dpi': 600}, + }, + 'cell': { + 'line_art': {'formats': ['pdf', 'eps'], 'dpi': 1000}, + 'photo': {'formats': ['tiff'], 'dpi': 300}, + 'combination': {'formats': ['pdf'], 'dpi': 600}, + }, + 'plos': { + 'line_art': {'formats': ['pdf', 'eps'], 'dpi': 600}, + 'photo': {'formats': ['tiff', 'png'], 'dpi': 300}, + 'combination': {'formats': ['tiff'], 'dpi': 300}, + }, + 'acs': { + 'line_art': {'formats': ['tiff', 'pdf'], 'dpi': 600}, + 'photo': {'formats': ['tiff'], 'dpi': 300}, + 'combination': {'formats': ['tiff'], 'dpi': 600}, + }, + 'ieee': { + 'line_art': {'formats': ['pdf', 'eps'], 'dpi': 600}, + 'photo': {'formats': ['tiff'], 'dpi': 300}, + 'combination': {'formats': ['pdf'], 'dpi': 300}, + }, + } + + if journal not in journal_specs: + available = ', '.join(journal_specs.keys()) + raise ValueError(f"Journal '{journal}' not recognized. Available: {available}") + + if figure_type not in journal_specs[journal]: + available = ', '.join(journal_specs[journal].keys()) + raise ValueError(f"Figure type '{figure_type}' not valid. Available: {available}") + + specs = journal_specs[journal][figure_type] + + print(f"Saving for {journal.upper()} ({figure_type}):") + print(f" Formats: {', '.join(specs['formats'])}") + print(f" DPI: {specs['dpi']}") + + return save_publication_figure( + fig=fig, + filename=filename, + formats=specs['formats'], + dpi=specs['dpi'] + ) + + +def check_figure_size(fig: plt.Figure, journal: str = 'nature') -> dict: + """ + Check if figure dimensions are appropriate for journal requirements. + + Parameters + ---------- + fig : matplotlib.figure.Figure + The figure to check + journal : str, default 'nature' + Journal name + + Returns + ------- + dict + Dictionary with figure dimensions and compliance status + + Examples + -------- + >>> fig = plt.figure(figsize=(3.5, 3)) + >>> info = check_figure_size(fig, journal='nature') + >>> print(info) + """ + journal = journal.lower() + + # Get figure dimensions in inches + width_inches, height_inches = fig.get_size_inches() + width_mm = width_inches * 25.4 + height_mm = height_inches * 25.4 + + # Journal specifications (widths in mm) + specs = { + 'nature': {'single': 89, 'double': 183, 'max_height': 247}, + 'science': {'single': 55, 'double': 175, 'max_height': 233}, + 'cell': {'single': 85, 'double': 178, 'max_height': 230}, + 'plos': {'single': 83, 'double': 173, 'max_height': 233}, + 'acs': {'single': 82.5, 'double': 178, 'max_height': 247}, + } + + if journal not in specs: + journal_spec = specs['nature'] + print(f"Warning: Journal '{journal}' not found, using Nature specifications") + else: + journal_spec = specs[journal] + + # Determine column type + column_type = None + width_ok = False + + tolerance = 5 # mm tolerance + if abs(width_mm - journal_spec['single']) < tolerance: + column_type = 'single' + width_ok = True + elif abs(width_mm - journal_spec['double']) < tolerance: + column_type = 'double' + width_ok = True + + height_ok = height_mm <= journal_spec['max_height'] + + result = { + 'width_inches': width_inches, + 'height_inches': height_inches, + 'width_mm': width_mm, + 'height_mm': height_mm, + 'journal': journal, + 'column_type': column_type, + 'width_ok': width_ok, + 'height_ok': height_ok, + 'compliant': width_ok and height_ok, + 'recommendations': { + 'single_column_mm': journal_spec['single'], + 'double_column_mm': journal_spec['double'], + 'max_height_mm': journal_spec['max_height'], + } + } + + # Print report + print(f"\n{'='*60}") + print(f"Figure Size Check for {journal.upper()}") + print(f"{'='*60}") + print(f"Current size: {width_mm:.1f} × {height_mm:.1f} mm") + print(f" ({width_inches:.2f} × {height_inches:.2f} inches)") + print(f"\n{journal.upper()} specifications:") + print(f" Single column: {journal_spec['single']} mm") + print(f" Double column: {journal_spec['double']} mm") + print(f" Max height: {journal_spec['max_height']} mm") + print(f"\nCompliance:") + print(f" Width: {'✓ OK' if width_ok else '✗ Non-standard'} ({column_type or 'custom'})") + print(f" Height: {'✓ OK' if height_ok else '✗ Too tall'}") + print(f" Overall: {'✓ COMPLIANT' if result['compliant'] else '✗ NEEDS ADJUSTMENT'}") + print(f"{'='*60}\n") + + return result + + +def verify_font_embedding(pdf_path: Union[str, Path]) -> bool: + """ + Check if fonts are embedded in a PDF file. + + Note: This requires PyPDF2 or a similar library to be installed. + + Parameters + ---------- + pdf_path : str or Path + Path to PDF file + + Returns + ------- + bool + True if fonts are embedded, False otherwise + """ + try: + from PyPDF2 import PdfReader + except ImportError: + print("Warning: PyPDF2 not installed. Cannot verify font embedding.") + print("Install with: pip install PyPDF2") + return None + + pdf_path = Path(pdf_path) + + try: + reader = PdfReader(pdf_path) + # This is a simplified check; full verification is complex + print(f"PDF has {len(reader.pages)} page(s)") + print("Note: Full font embedding verification requires detailed PDF inspection.") + return True + except Exception as e: + print(f"Error reading PDF: {e}") + return False + + +if __name__ == "__main__": + # Example usage + import numpy as np + + # Create example figure + fig, ax = plt.subplots(figsize=(3.5, 2.5)) + x = np.linspace(0, 10, 100) + ax.plot(x, np.sin(x), label='sin(x)') + ax.plot(x, np.cos(x), label='cos(x)') + ax.set_xlabel('x') + ax.set_ylabel('y') + ax.legend() + ax.spines['top'].set_visible(False) + ax.spines['right'].set_visible(False) + + # Check size + check_figure_size(fig, journal='nature') + + # Save in multiple formats + print("\nSaving figure...") + save_publication_figure(fig, 'example_figure', formats=['pdf', 'png'], dpi=300) + + # Save with journal-specific requirements + print("\nSaving for Nature...") + save_for_journal(fig, 'example_figure_nature', journal='nature', figure_type='line_art') + + plt.close(fig) diff --git a/scripts/style_presets.py b/scripts/style_presets.py new file mode 100644 index 0000000..f6b1546 --- /dev/null +++ b/scripts/style_presets.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +""" +Matplotlib Style Presets for Publication-Ready Scientific Figures + +This module provides pre-configured matplotlib styles optimized for +different journals and use cases. +""" + +import matplotlib.pyplot as plt +import matplotlib as mpl +from typing import Optional, Dict, Any + + +# Okabe-Ito colorblind-friendly palette +OKABE_ITO_COLORS = [ + '#E69F00', # Orange + '#56B4E9', # Sky Blue + '#009E73', # Bluish Green + '#F0E442', # Yellow + '#0072B2', # Blue + '#D55E00', # Vermillion + '#CC79A7', # Reddish Purple + '#000000' # Black +] + +# Paul Tol palettes +TOL_BRIGHT = ['#4477AA', '#EE6677', '#228833', '#CCBB44', '#66CCEE', '#AA3377', '#BBBBBB'] +TOL_MUTED = ['#332288', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77', '#CC6677', '#882255', '#AA4499'] +TOL_HIGH_CONTRAST = ['#004488', '#DDAA33', '#BB5566'] + +# Wong palette +WONG_COLORS = ['#000000', '#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2', '#D55E00', '#CC79A7'] + + +def get_base_style() -> Dict[str, Any]: + """ + Get base publication-quality style settings. + + Returns + ------- + dict + Dictionary of matplotlib rcParams + """ + return { + # Figure + 'figure.dpi': 100, # Display DPI (changed on save) + 'figure.facecolor': 'white', + 'figure.autolayout': False, + 'figure.constrained_layout.use': True, + + # Font + 'font.size': 8, + 'font.family': 'sans-serif', + 'font.sans-serif': ['Arial', 'Helvetica', 'DejaVu Sans'], + + # Axes + 'axes.linewidth': 0.5, + 'axes.labelsize': 9, + 'axes.titlesize': 9, + 'axes.labelweight': 'normal', + 'axes.spines.top': False, + 'axes.spines.right': False, + 'axes.spines.left': True, + 'axes.spines.bottom': True, + 'axes.edgecolor': 'black', + 'axes.labelcolor': 'black', + 'axes.axisbelow': True, + 'axes.prop_cycle': mpl.cycler(color=OKABE_ITO_COLORS), + + # Grid + 'axes.grid': False, + + # Ticks + 'xtick.major.size': 3, + 'xtick.minor.size': 2, + 'xtick.major.width': 0.5, + 'xtick.minor.width': 0.5, + 'xtick.labelsize': 7, + 'xtick.direction': 'out', + 'ytick.major.size': 3, + 'ytick.minor.size': 2, + 'ytick.major.width': 0.5, + 'ytick.minor.width': 0.5, + 'ytick.labelsize': 7, + 'ytick.direction': 'out', + + # Lines + 'lines.linewidth': 1.5, + 'lines.markersize': 4, + 'lines.markeredgewidth': 0.5, + + # Legend + 'legend.fontsize': 7, + 'legend.frameon': False, + 'legend.loc': 'best', + + # Savefig + 'savefig.dpi': 300, + 'savefig.format': 'pdf', + 'savefig.bbox': 'tight', + 'savefig.pad_inches': 0.05, + 'savefig.transparent': False, + 'savefig.facecolor': 'white', + + # Image + 'image.cmap': 'viridis', + 'image.aspect': 'auto', + } + + +def apply_publication_style(style_name: str = 'default') -> None: + """ + Apply a pre-configured publication style. + + Parameters + ---------- + style_name : str, default 'default' + Name of the style to apply. Options: + - 'default': General publication style + - 'nature': Nature journal style + - 'science': Science journal style + - 'cell': Cell Press style + - 'minimal': Minimal clean style + - 'presentation': Larger fonts for presentations + + Examples + -------- + >>> apply_publication_style('nature') + >>> fig, ax = plt.subplots() + >>> ax.plot([1, 2, 3], [1, 4, 9]) + """ + base_style = get_base_style() + + # Style-specific modifications + if style_name == 'nature': + base_style.update({ + 'font.size': 7, + 'axes.labelsize': 8, + 'axes.titlesize': 8, + 'xtick.labelsize': 6, + 'ytick.labelsize': 6, + 'legend.fontsize': 6, + 'savefig.dpi': 600, + }) + + elif style_name == 'science': + base_style.update({ + 'font.size': 7, + 'axes.labelsize': 8, + 'xtick.labelsize': 6, + 'ytick.labelsize': 6, + 'legend.fontsize': 6, + 'savefig.dpi': 600, + }) + + elif style_name == 'cell': + base_style.update({ + 'font.size': 8, + 'axes.labelsize': 9, + 'xtick.labelsize': 7, + 'ytick.labelsize': 7, + 'legend.fontsize': 7, + 'savefig.dpi': 600, + }) + + elif style_name == 'minimal': + base_style.update({ + 'axes.linewidth': 0.8, + 'xtick.major.width': 0.8, + 'ytick.major.width': 0.8, + 'lines.linewidth': 2, + }) + + elif style_name == 'presentation': + base_style.update({ + 'font.size': 14, + 'axes.labelsize': 16, + 'axes.titlesize': 18, + 'xtick.labelsize': 12, + 'ytick.labelsize': 12, + 'legend.fontsize': 12, + 'axes.linewidth': 1.5, + 'lines.linewidth': 2.5, + 'lines.markersize': 8, + }) + + elif style_name != 'default': + print(f"Warning: Style '{style_name}' not recognized. Using 'default'.") + + # Apply the style + plt.rcParams.update(base_style) + print(f"✓ Applied '{style_name}' publication style") + + +def set_color_palette(palette_name: str = 'okabe_ito') -> None: + """ + Set a colorblind-friendly color palette. + + Parameters + ---------- + palette_name : str, default 'okabe_ito' + Name of the palette. Options: + - 'okabe_ito': Okabe-Ito palette (8 colors) + - 'wong': Wong palette (8 colors) + - 'tol_bright': Paul Tol bright palette (7 colors) + - 'tol_muted': Paul Tol muted palette (9 colors) + - 'tol_high_contrast': Paul Tol high contrast (3 colors) + + Examples + -------- + >>> set_color_palette('tol_muted') + >>> fig, ax = plt.subplots() + >>> for i in range(5): + ... ax.plot([1, 2, 3], [i, i+1, i+2]) + """ + palettes = { + 'okabe_ito': OKABE_ITO_COLORS, + 'wong': WONG_COLORS, + 'tol_bright': TOL_BRIGHT, + 'tol_muted': TOL_MUTED, + 'tol_high_contrast': TOL_HIGH_CONTRAST, + } + + if palette_name not in palettes: + available = ', '.join(palettes.keys()) + print(f"Warning: Palette '{palette_name}' not found. Available: {available}") + palette_name = 'okabe_ito' + + colors = palettes[palette_name] + plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors) + print(f"✓ Applied '{palette_name}' color palette ({len(colors)} colors)") + + +def configure_for_journal(journal: str, figure_width: str = 'single') -> None: + """ + Configure matplotlib for a specific journal. + + Parameters + ---------- + journal : str + Journal name: 'nature', 'science', 'cell', 'plos', 'acs', 'ieee' + figure_width : str, default 'single' + Figure width: 'single' or 'double' column + + Examples + -------- + >>> configure_for_journal('nature', figure_width='single') + >>> fig, ax = plt.subplots() # Will have correct size for Nature + """ + journal = journal.lower() + + # Journal specifications + journal_configs = { + 'nature': { + 'single_width': 89, # mm + 'double_width': 183, + 'style': 'nature', + }, + 'science': { + 'single_width': 55, + 'double_width': 175, + 'style': 'science', + }, + 'cell': { + 'single_width': 85, + 'double_width': 178, + 'style': 'cell', + }, + 'plos': { + 'single_width': 83, + 'double_width': 173, + 'style': 'default', + }, + 'acs': { + 'single_width': 82.5, + 'double_width': 178, + 'style': 'default', + }, + 'ieee': { + 'single_width': 89, + 'double_width': 182, + 'style': 'default', + }, + } + + if journal not in journal_configs: + available = ', '.join(journal_configs.keys()) + raise ValueError(f"Journal '{journal}' not recognized. Available: {available}") + + config = journal_configs[journal] + + # Apply style + apply_publication_style(config['style']) + + # Set default figure size + width_mm = config['single_width'] if figure_width == 'single' else config['double_width'] + width_inches = width_mm / 25.4 + plt.rcParams['figure.figsize'] = (width_inches, width_inches * 0.75) # 4:3 aspect ratio + + print(f"✓ Configured for {journal.upper()} ({figure_width} column: {width_mm} mm)") + + +def create_style_template(output_file: str = 'publication.mplstyle') -> None: + """ + Create a matplotlib style file that can be used with plt.style.use(). + + Parameters + ---------- + output_file : str, default 'publication.mplstyle' + Output filename for the style file + + Examples + -------- + >>> create_style_template('my_style.mplstyle') + >>> plt.style.use('my_style.mplstyle') + """ + style = get_base_style() + + with open(output_file, 'w') as f: + f.write("# Publication-quality matplotlib style\n") + f.write("# Usage: plt.style.use('publication.mplstyle')\n\n") + + for key, value in style.items(): + if isinstance(value, mpl.cycler): + # Handle cycler specially + colors = [c['color'] for c in value] + f.write(f"axes.prop_cycle : cycler('color', {colors})\n") + else: + f.write(f"{key} : {value}\n") + + print(f"✓ Created style template: {output_file}") + print(f" Use with: plt.style.use('{output_file}')") + + +def show_color_palettes() -> None: + """ + Display available color palettes for visual inspection. + """ + palettes = { + 'Okabe-Ito': OKABE_ITO_COLORS, + 'Wong': WONG_COLORS, + 'Tol Bright': TOL_BRIGHT, + 'Tol Muted': TOL_MUTED, + 'Tol High Contrast': TOL_HIGH_CONTRAST, + } + + fig, axes = plt.subplots(len(palettes), 1, figsize=(8, len(palettes) * 0.5)) + + for ax, (name, colors) in zip(axes, palettes.items()): + ax.set_xlim(0, len(colors)) + ax.set_ylim(0, 1) + ax.set_yticks([]) + ax.set_xticks([]) + ax.set_ylabel(name, fontsize=10) + + for i, color in enumerate(colors): + ax.add_patch(plt.Rectangle((i, 0), 1, 1, facecolor=color, edgecolor='black', linewidth=0.5)) + # Add hex code + ax.text(i + 0.5, 0.5, color, ha='center', va='center', + fontsize=7, color='white' if i >= len(colors) - 1 else 'black') + + fig.suptitle('Colorblind-Friendly Palettes', fontsize=12, fontweight='bold') + plt.tight_layout() + plt.show() + + +def reset_to_default() -> None: + """ + Reset matplotlib to default settings. + """ + mpl.rcdefaults() + print("✓ Reset to matplotlib defaults") + + +if __name__ == "__main__": + print("Matplotlib Style Presets for Scientific Figures") + print("=" * 50) + + # Show available styles + print("\nAvailable publication styles:") + print(" - default") + print(" - nature") + print(" - science") + print(" - cell") + print(" - minimal") + print(" - presentation") + + print("\nAvailable color palettes:") + print(" - okabe_ito (recommended)") + print(" - wong") + print(" - tol_bright") + print(" - tol_muted") + print(" - tol_high_contrast") + + print("\nExample usage:") + print(" from style_presets import apply_publication_style, set_color_palette") + print(" apply_publication_style('nature')") + print(" set_color_palette('okabe_ito')") + + # Create example figure + print("\nGenerating example figure with 'default' style...") + apply_publication_style('default') + + fig, ax = plt.subplots(figsize=(3.5, 2.5)) + for i in range(5): + ax.plot([1, 2, 3, 4], [i, i+1, i+0.5, i+2], marker='o', label=f'Series {i+1}') + ax.set_xlabel('Time (hours)') + ax.set_ylabel('Response (AU)') + ax.legend() + fig.suptitle('Example with Publication Style') + plt.tight_layout() + plt.show() + + # Show color palettes + print("\nDisplaying color palettes...") + show_color_palettes()