在使用 matplotlib 绘制包含中文的图表时,经常会遇到中文显示为方块或乱码的问题。本文记录了完整的排查过程和解决方案。
问题原因
matplotlib 默认字体 DejaVu Sans 不包含中文字形。即使系统已安装 fonts-noto-cjk,也可能无法正常显示中文。
根本原因在于:fonts-noto-cjk 安装的字体是 .ttc(TrueType Collection)格式,这种格式将多个字体打包在一个文件中(SC、TC、JP、KR 四个版本)。但 matplotlib 在解析 .ttc 文件时只能读取第一个子字体(JP),导致 SC(简体中文)无法被识别。
验证方式:
import matplotlib.font_manager as fm
cjk_fonts = [f.name for f in fm.fontManager.ttflist if 'noto' in f.name.lower()]
print(set(cjk_fonts))
# 输出:{'Noto Sans CJK JP', 'Noto Serif CJK JP'} ← 只有 JP,没有 SC
解决方案:用 fonttools 提取 SC 字体
最彻底的解决方式是从 .ttc 文件中提取出独立的 SC 字体文件。
Step 1:安装 fonttools
pip install fonttools
Step 2:查看 ttc 中的子字体索引
from fontTools import ttLib
ttc = ttLib.TTCollection('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
for i, font in enumerate(ttc.fonts):
print(i, font['name'].getDebugName(4))
找到 Noto Sans CJK SC 对应的索引编号。
Step 3:提取并保存 SC 字体
from fontTools import ttLib
ttc = ttLib.TTCollection('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
ttc.fonts[1].save('/usr/share/fonts/NotoSansSC.otf') # index 按上一步结果修改
保存到 /usr/share/fonts/ 后,matplotlib 会在下次启动时自动扫描到该字体,无需手动 addfont()。
Step 4:清除缓存并验证
import matplotlib
import matplotlib.font_manager as fm
import shutil
# 清除旧缓存
shutil.rmtree(matplotlib.get_cachedir(), ignore_errors=True)
# 重新加载字体列表
fm._load_fontmanager(try_read_cache=False)
# 确认 SC 字体已出现
fonts = sorted(set(f.name for f in fm.fontManager.ttflist if 'noto' in f.name.lower()))
print(fonts) # 应包含 Noto Sans CJK SC
Step 5:在代码中设置字体
在每个 notebook 或脚本开头加入:
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False # 防止负号显示为方块
备选方案一:直接使用 JP 字体
如果不想折腾,JP 字体同样包含所有常用汉字(日语汉字与中文汉字共用相同的 Unicode 码位),在图表中显示中文完全没问题,只是字形风格略有差异。
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK JP', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
备选方案二:从 Google Fonts 下载独立字体
前往 Google Fonts – Noto Sans SC 下载,选择 static/NotoSansSC-Regular.ttf(注意不要用 VariableFont 版本,matplotlib 对可变字体支持不佳)。
将文件放入 /usr/share/fonts/ 后清除缓存,字体名为 Noto Sans SC(无 CJK 后缀):
plt.rcParams['font.sans-serif'] = ['Noto Sans SC', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
小结
| 方案 | 优点 | 缺点 |
|---|---|---|
| fonttools 提取 SC | 利用已有字体,无需下载 | 需要安装 fonttools |
| 直接用 JP 字体 | 零配置,开箱即用 | 字形为日式风格 |
| Google Fonts 下载 | 字体最标准 | 需要手动下载上传 |
对于日常数据可视化,三种方案都可以正常使用。