| 参数 | 值 | 说明 |
|---|---|---|
axes.linewidth |
0.75 pt | 边框 |
lines.linewidth |
1.2 pt | 数据曲线 |
xtick/ytick.direction |
in | 内向刻度 |
xtick.top / ytick.right |
True | 四边都有刻度 |
xtick/ytick.minor.visible |
True | 自动开启次刻度 |
axes.grid |
False | 不带网格(Bode 例外) |
legend.frameon |
False | 图例无边框 |
pdf.fonttype |
42 | TrueType 嵌入,避免 PDF 乱码 |
mathtext.fontset |
stix | 数学符号字体 |
savefig.dpi |
600 | 印刷分辨率 |
| profile | figsize (in) | font.size | label | legend | linewidth |
|---|---|---|---|---|---|
single / single_cn |
(3.5, 2.6) | 8 | 9 | 7 | 1.2 |
double / double_cn |
(7.16, 3.0) | 8 | 9 | 7 | 1.2 |
slide / slide_cn |
(8.0, 5.0) | 14 | 16 | 13 | 1.8 |
poster / poster_cn |
(10.0, 7.0) | 18 | 20 | 16 | 2.2 |
_cn profile 仅在字体族首位插入中文字体,其余参数与对应英文 profile 一致。
import numpy as np
import academic_plot as ap
fs = 1667
t = np.arange(0, 0.5, 1/fs)
vib = np.loadtxt("vibration.csv")
fig, axes = ap.new_figure("double", n_rows=1, n_cols=2)
ap.plot_time_series(t * 1000, vib,
xlabel="Time (ms)",
ylabel=r"Acc. (m/s$^2$)",
ax=axes[0])
ap.plot_spectrum(vib, fs, xlim=(0, 300),
ylabel=r"Amp. (m/s$^2$)",
ax=axes[1])
ap.add_subplot_label(axes[0], "(a)")
ap.add_subplot_label(axes[1], "(b)")
fig.tight_layout()
ap.save_figure(fig, "fig2_vibration.png")loads = [0.5, 1.0, 2.0, 5.0]
fig, ax = ap.plot_multi_curves(
t, [response[load] for load in loads],
labels=[f"{lo} kg" for lo in loads],
xlabel="Time (s)",
ylabel="Force (N)",
markers=["o", "s", "^", "D"],
markevery=20,
legend_loc="best",
legend_ncol=2,
)
ap.save_figure(fig, "fig3_force.png")fig, (ax1, ax2) = ap.plot_dual_axis(
t_min, voltage, temperature,
xlabel="Time (min)",
ylabel1="Voltage (V)",
ylabel2=r"Temperature ($^\circ$C)",
label1="Battery",
label2="Cell temp.",
legend_loc="center right",
)
ax1.axvspan(20, 30, alpha=0.15, color="gray")
ax1.text(25, 4.1, "Charge phase", ha="center", fontsize=7)
ap.save_figure(fig, "fig4_battery.png")fig, ax = ap.plot_errorbar(
x_load, y_voltage, yerr=y_std,
xlabel="Applied Force (N)",
ylabel="Output Voltage (mV)",
label="Measured",
)
x_fit = np.linspace(x_load.min(), x_load.max(), 200)
y_fit = slope * x_fit + intercept
ax.plot(x_fit, y_fit, "--", color=ap.COLORS[2],
label=f"Linear fit ($R^2$={r2:.4f})")
ax.legend()
ap.save_figure(fig, "fig5_calibration.png")fig, (ax_m, ax_p) = ap.plot_bode(
freq, mag_db, phase_deg,
label="Measured",
)
ax_m.semilogx(freq, mag_db_theory, "--", color=ap.COLORS[2], label="1st-order")
ax_m.legend()
ap.save_figure(fig, "fig6_bode.png")from scipy.signal import spectrogram
f, t, Sxx = spectrogram(vib, fs=fs, nperseg=256)
fig, ax = ap.plot_heatmap(
10 * np.log10(Sxx + 1e-12),
xlabel="Time (s)",
ylabel="Frequency (Hz)",
cbar_label="PSD (dB)",
extent=(t.min(), t.max(), f.min(), f.max()),
cmap="viridis",
)
ax.set_ylim(0, 300)
ap.save_figure(fig, "fig7_stft.png")ap.apply_style("double_cn")
fig, axes = ap.new_figure("double_cn", n_rows=2, n_cols=2)
axes[0, 0].plot(t * 1000, vib, color=ap.COLORS[1])
axes[0, 0].set_xlabel("时间 (ms)")
axes[0, 0].set_ylabel(r"加速度 (m/s$^2$)")
ap.add_subplot_label(axes[0, 0], "(a)")
# ... 其他子图
fig.suptitle("某型号传感器综合测试", fontproperties=ap.CJK_FONT, fontsize=10)
fig.tight_layout()
ap.save_figure(fig, "fig_cn_综合.png")fig, axes = ap.new_figure("double", n_rows=2, n_cols=1, sharex=True)
fig.subplots_adjust(hspace=0.08)from mpl_toolkits.axes_grid1.inset_locator import inset_axes
axins = inset_axes(ax, width="35%", height="35%", loc="upper right",
borderpad=1)
axins.plot(t_zoom, y_zoom, color=ap.COLORS[1])
axins.tick_params(labelsize=6)ax1_min, ax1_max = ax1.get_ylim()
ax2_min, ax2_max = ax2.get_ylim()
ratio = ax1_min / (ax1_min - ax1_max) if ax1_min < 0 else 0
ax2.set_ylim(ax2_max * ratio / (ratio - 1), ax2_max)peak_idx = np.argmax(spec)
ax.annotate(f"{freq[peak_idx]:.1f} Hz",
xy=(freq[peak_idx], spec[peak_idx]),
xytext=(20, -20), textcoords="offset points",
fontsize=7,
arrowprops=dict(arrowstyle="->", lw=0.5, color="gray"))gray_colors = ["#000000", "#444444", "#888888", "#bbbbbb"]
fig, ax = ap.plot_multi_curves(
x, ys, labels,
linestyles=["-", "--", "-.", ":"],
)
for line, c in zip(ax.lines, gray_colors):
line.set_color(c)
ap.save_figure(fig, "fig_gray.png")\begin{figure}[tb]
\centering
\includegraphics[width=\columnwidth]{fig2_vibration.pdf}
\caption{Vibration signal in time and frequency domains.}
\label{fig:vibration}
\end{figure}注意:
- 用 PDF 不用 PNG(矢量缩放无损)
\columnwidth自动匹配单栏 / 双栏,画图时按"single"/"double"profile 出 1:1 尺寸,不要额外缩放- 字号已按 8 pt 设计,与 IEEE 期刊正文 10 pt 协调
国内核心期刊(仪器仪表学报、机械工程学报、电子学报、振动工程学报等)的常见要求:
| 要求 | academic-plot 对应 |
|---|---|
| 字号 7-9 号 | _cn profile 默认 8 pt |
| 中英文同时存在 | 自动 fallback(中文字体优先,英文 Helvetica) |
| 单位用英文(s, Hz, V, dB 等) | 直接写 "频率 (Hz)" 即可 |
| 数学符号用 mathtext | r"$10^{-3}$"、r"$\mu$m" 等 |
| 子图编号 (a) (b) (c) | add_subplot_label(ax, "(a)") |
| 图注用宋体 | 默认黑体;如需宋体在 apply_style 后手动覆盖 font.sans-serif |
| 黑白印刷需识别 | 用 linestyles 区分曲线,颜色作辅助 |
如某期刊明确要求宋体:
import matplotlib as mpl
ap.apply_style("single_cn")
mpl.rcParams["font.sans-serif"] = ["Songti SC", "STSong"] + mpl.rcParams["font.sans-serif"]需要:matplotlib >= 3.5、numpy、scipy(仅 spectrogram 等用到)。
查看当前生效:
import matplotlib as mpl
print(mpl.rcParams["axes.prop_cycle"])
print(mpl.rcParams["figure.figsize"])
print(mpl.rcParams["font.sans-serif"])中文不显示 / 显示方块:
import academic_plot as ap
print(ap.has_cjk_font())
print(ap.list_cjk_fonts())
# Linux: sudo apt install fonts-noto-cjk