使用 Python 和 Matplotlib 绘制总能差柱状图

March 30, 2026
Published in 编程技术

Abstract

在长期的材料计算与分子模拟中,对比不同结构间的能量变化往往是评估物理体系平衡与稳定性的关键环节。本文将展示如何通过一段精简的 Python 脚本自动读取 CSV 数据文件,并结合 Matplotlib 绘制包含类 LaTeX 字体排版的高质量总能差(Total Energy Difference)柱状图。

Keywords: Python, Matplotlib, 数据可视化, 物理计算

脚本功能概述

本例提供的脚本 plot_ediff.py 具有极简而高效的特性,通过少量的代码便可完成高质量可视化。其主要工作流程如下:

  1. 读取数据:从相对路径 dat/etotal_compare.csv 文件中依序读取各个结构的文件名称及其对应的能量差( $\Delta E$ ,单位为 eV)。
  2. 样式配置:对 Matplotlib 的绘图样式进行专业微调,引入适合学术报告的类 LaTeX 罗马字体和数学字体组合。
  3. 快速绘图:指定 Agg 渲染后端(无需图形界面即依赖即可渲染),生成柱状图后自动落盘保存至指定输出目录。

核心代码分解与解析

以下我们将分步讲解代码关键维度的实现细节。

依赖引入与无头模式配置

导入必要的标准库与绘图框架。特别是在连接云端 Linux 计算节点执行该脚本时,通过设定 matplotlib.use('Agg') 可以从根本上避免因为缺失图形渲染界面(如无 X11 转发)而引发的执行阻断或报错。

import csv
import os
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

图表全局字体格式设置

为了确保输出的图表在正式的学术交流论文或展示中拥有一致且美观的排版,代码使用 plt.rcParams.update 指令修改了全局默认配置体系。其中,指定的背景数学字体集为 cm (Computer Modern),这将使得纵轴、横轴及标题里的物理符号能够呈现出经典的 LaTeX 般细腻画质:

# 设置使用类 LaTeX 罗马/数学字体
plt.rcParams.update({
    "font.family": "serif",
    "mathtext.fontset": "cm",
    "axes.formatter.use_mathtext": True
})

数据自动化读取与装载

代码预先校验并确保目标输出目录 viz 存在。随后借助 Python 内置的 csv.DictReader 类进行数据解析,这使开发者能依据直观的 CSV 结构化表头(即 Filenameetotal_eV diff)来精准抽取对应列,从而免除了针对裸数据行坐标的繁琐索引管控:

# 确保输出目录存在
os.makedirs("viz", exist_ok=True)

filenames = []
ediff_ev = []

with open("dat/etotal_compare.csv", "r") as f:
    reader = csv.DictReader(f)
    for row in reader:
        filenames.append(row["Filename"])
        ediff_ev.append(float(row["etotal_eV diff"]))

可视化结构绘制排版

获取了清晰的数据点后,即可进入图表搭建。此环节针对观感优化做出了数项调整:移除了背景中冗余的网格线、微调并旋转坐标轴下方向的密集文字节点以防止标签相互遮挡覆盖,且熟练使用内置的 LaTeX 样式数学公式排版物理量和轴标题:

fig, ax = plt.subplots(figsize=(12, 6))

# 画柱状图
x_pos = range(len(filenames))
ax.bar(x_pos, ediff_ev, color='#4c72b0', edgecolor='black', zorder=3)

# 无网格线
ax.grid(False)

# 设置轴标签
ax.set_xticks(x_pos)
ax.set_xticklabels(filenames, rotation=45, ha='right', fontsize=10)

ax.set_ylabel(r'$\Delta E_{\mathrm{total}}$ (eV)', fontsize=14)
ax.set_xlabel('Structures', fontsize=14)
ax.set_title(r'Total Energy Difference ($\Delta E = E_{\mathrm{end}} - E_{\mathrm{initial}}$)', fontsize=16)

plt.tight_layout()

图片渲染输出

最后一步,将经过 tight_layout() 模型自适应优化的最终渲染图像,以高达 300 DPI 分辨率落地储存保留在 viz/etotal_ev_diff.png 内部,脚本还会在终端输出人性化的成功打印提示:

# 保存图片
output_path = "viz/etotal_ev_diff.png"
plt.savefig(output_path, dpi=300)
plt.close()

print(f"Plot successfully saved to {output_path}")

完整实现源码

将前述所有的思路拆组后,即可组装出无需二次修改、开箱即用的稳健脚本大全:

import csv
import os
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

# 设置使用类 LaTeX 罗马/数学字体
plt.rcParams.update({
    "font.family": "serif",
    "mathtext.fontset": "cm",
    "axes.formatter.use_mathtext": True
})

# 确保输出目录存在
os.makedirs("viz", exist_ok=True)

filenames = []
ediff_ev = []

with open("dat/etotal_compare.csv", "r") as f:
    reader = csv.DictReader(f)
    for row in reader:
        filenames.append(row["Filename"])
        ediff_ev.append(float(row["etotal_eV diff"]))

fig, ax = plt.subplots(figsize=(12, 6))

# 画柱状图
x_pos = range(len(filenames))
ax.bar(x_pos, ediff_ev, color='#4c72b0', edgecolor='black', zorder=3)

# 无网格线
ax.grid(False)

# 设置轴标签
ax.set_xticks(x_pos)
ax.set_xticklabels(filenames, rotation=45, ha='right', fontsize=10)

ax.set_ylabel(r'$\Delta E_{\mathrm{total}}$ (eV)', fontsize=14)
ax.set_xlabel('Structures', fontsize=14)
ax.set_title(r'Total Energy Difference ($\Delta E = E_{\mathrm{end}} - E_{\mathrm{initial}}$)', fontsize=16)

plt.tight_layout()

# 保存图片
output_path = "viz/etotal_ev_diff.png"
plt.savefig(output_path, dpi=300)
plt.close()

print(f"Plot successfully saved to {output_path}")

参考资料 (References)

  • Hunter, John D. "Matplotlib: A 2D Graphics Environment." Computing in Science & Engineering, vol. 9, no. 3, 2007, pp. 90-95. link