1. 从一次糟糕的图表汇报说起

那次汇报我至今记忆犹新。我花了整整一周时间,用Python的matplotlib库精心制作了一份数据分析报告,图表数据扎实,逻辑清晰。然而,当我把PPT投到大屏幕上时,台下的一位资深前辈皱起了眉头:“小张,你这图……标题和坐标轴的字是不是太小了?还有,注释文字的方向怎么是歪的?坐在后排根本看不清。” 那一刻,我意识到,在数据可视化的世界里, “正确”的数据只是基础,而“得体”的呈现才是让数据开口说话的关键 。这其中,对字体、字号、粗细、方向乃至旋转角度的精细控制,是区分“能用”的图表和“专业”的图表的核心技能。很多朋友在入门matplotlib时,往往把精力集中在如何画线、如何设置颜色上,却忽略了文字排版这块“最后的拼图”。结果就是,图表在Jupyter Notebook里看着还行,一旦导出为PDF嵌入报告,或者放到演示文稿里,立刻就显得粗糙和不专业。本文将结合我无数次踩坑和调整的经验,为你梳理一份关于matplotlib文字属性控制的“全集”指南,涵盖字体、大小、粗细、方向、斜体与旋转,让你能像设计师一样掌控图表中的每一个字符。

2. 字体(Family):不仅仅是选择“宋体”或“Arial”

字体是图表风格的基石。在matplotlib中,字体的选择远不止是传入一个字符串那么简单,它涉及到字体栈的配置、系统字体的查找以及跨平台兼容性这些底层逻辑。

2.1 理解matplotlib的字体管理机制

Matplotlib维护着一个字体缓存和查找路径列表。当你指定一个字体名称(如 ‘SimHei’ )时,它会在以下位置按顺序查找:

  1. 用户通过 rcParams 或代码显式指定的路径。
  2. 系统标准字体目录(如Windows的 C:\Windows\Fonts ,Linux的 /usr/share/fonts ,macOS的 /Library/Fonts ~/Library/Fonts )。
  3. Matplotlib自带的字体库(通常位于 matplotlib/mpl-data/fonts/ttf 下,如DejaVu Sans)。

你可以通过以下代码查看当前可用的字体列表和matplotlib的字体查找路径:

import matplotlib.font_manager as fm
# 查看所有可用字体名
font_names = [f.name for f in fm.fontManager.ttflist]
print(f“可用的字体数量:{len(font_names)}“)
# 简单打印前20个
for name in sorted(font_names)[:20]:
    print(name)

# 查看字体查找路径
print(fm.fontManager.ttflist[0].fname)  # 查看某个字体的实际文件路径

注意 :直接打印的字体列表可能非常长,且包含许多相似名称。更常见的做法是通过 FontProperties 对象来测试特定字体是否可用。

2.2 如何正确设置中文字体(解决乱码问题)

这是中文用户最常遇到的坑。Matplotlib默认的字体库不包含完整的中文字体,直接设置中文标签会导致显示为方框(□□□)。

解决方案一:临时设置(推荐用于单张图或脚本) 在绘图代码前,通过 rcParams 动态修改配置。这是最灵活的方式。

import matplotlib.pyplot as plt
import matplotlib

# 指定一个系统中存在的中文字体文件
# Windows 常见中文字体:SimHei(黑体), SimSun(宋体), Microsoft YaHei(微软雅黑)
# macOS/Linux 常见中文字体:PingFang SC(苹方), Noto Sans CJK SC(思源黑体)
plt.rcParams[‘font.sans-serif’] = [‘SimHei’]  # 用来正常显示中文标签
plt.rcParams[‘axes.unicode_minus’] = False    # 用来正常显示负号

# 然后进行绘图
plt.plot([1, 2, 3], [4, 5, 6])
plt.title(‘这是一张中文标题的图表’)
plt.xlabel(‘横坐标’)
plt.ylabel(‘纵坐标’)
plt.show()

解决方案二:全局设置(修改配置文件) 一劳永逸的方法,但需要找到matplotlib的配置文件 matplotlibrc 。可以通过 matplotlib.matplotlib_fname() 查看其路径。找到后,编辑该文件,找到以下两行,取消注释并修改:

#font.sans-serif: DejaVu Sans, Bitstream Vera Sans, ...  # 这是一长串字体名
font.sans-serif: SimHei, DejaVu Sans, Bitstream Vera Sans, ...  # 将SimHei添加到最前面
#axes.unicode_minus: True
axes.unicode_minus: False  # 将True改为False

解决方案三:使用绝对路径指定字体文件 当你的字体文件不在系统标准目录,或者需要确保在任意环境下字体一致时(例如打包交付),可以使用此方法。

import matplotlib.font_manager as fm
import matplotlib.pyplot as plt

# 指定字体文件的绝对路径
font_path = ‘/path/to/your/custom_font.ttf‘  # 例如 ‘C:\\Windows\\Fonts\\msyh.ttc‘
font_prop = fm.FontProperties(fname=font_path)

# 在具体的文本设置中使用这个FontProperties对象
plt.plot([1, 2, 3], [4, 5, 6])
plt.title(‘自定义字体标题’, fontproperties=font_prop, fontsize=14)
plt.xlabel(‘横轴’, fontproperties=font_prop)
plt.show()

实操心得 :在团队协作或生产环境中,我强烈推荐 方案三 。将所需的字体文件(如思源黑体)放在项目目录的 assets/fonts 文件夹下,然后在代码中通过相对路径加载。这样可以完全避免因不同机器字体缺失导致的显示不一致问题,实现“一次配置,处处运行”。

2.3 西文字体的选择与搭配

对于纯英文或中英文混排的图表,选择合适的西文字体同样重要。Matplotlib默认的 ‘DejaVu Sans’ 是一款优秀的无衬线字体,清晰易读。但你也可以选择其他风格:

  • 无衬线体 (Sans-serif) :如 ‘Arial’ , ‘Helvetica’ , ‘Calibri’ 。风格现代、简洁,适用于大多数科技、商业图表。是标题和标签的 安全选择
  • 衬线体 (Serif) :如 ‘Times New Roman’ , ‘Georgia’ 。更具传统、正式感,常见于学术出版(某些期刊要求)。在图表中大量使用可能降低屏幕可读性。
  • 等宽字体 (Monospace) :如 ‘Courier New’ , ‘Consolas’ 。通常用于坐标轴刻度标签需要严格对齐,或需要展示代码、数据等场景。

设置方法与中文字体类似,通过 rcParams[‘font.sans-serif’] 列表来定义字体栈。列表顺序代表优先级。

# 设置一个字体栈,优先使用Arial,如果没有则用Helvetica,再没有则用默认的DejaVu Sans
plt.rcParams[‘font.sans-serif’] = [‘Arial’, ‘Helvetica’, ‘DejaVu Sans’]

3. 字体大小(Size):建立清晰的视觉层次

字体大小直接决定了图表的可读性和信息层级。盲目使用默认大小(通常是10pt)是新手常犯的错误。

3.1 理解matplotlib中的尺寸单位

Matplotlib中字体大小的默认单位是“点”(points, pt)。这是一个绝对单位,1 pt = 1/72 英寸。这意味着在导出为PDF或调整图形大小时,字体物理尺寸是固定的。你也可以使用其他相对单位,但在设置字体大小时,直接使用数字(如12, 14)即表示pt。

3.2 全局设置与局部设置

全局设置 :通过 rcParams 影响图表中的所有文本元素。

plt.rcParams[‘font.size’] = 12           # 默认文本大小
plt.rcParams[‘axes.titlesize’] = 16      # 子图标题大小
plt.rcParams[‘axes.labelsize’] = 14      # 坐标轴标签大小
plt.rcParams[‘xtick.labelsize’] = 12     # x轴刻度标签大小
plt.rcParams[‘ytick.labelsize’] = 12     # y轴刻度标签大小
plt.rcParams[‘legend.fontsize’] = 11     # 图例字体大小

局部设置 :在调用具体的绘图函数时,通过参数覆盖全局设置。这是更精细的控制方式。

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(x, y, label=‘数据线’)
ax.set_title(‘主标题’, fontsize=18, pad=15)  # pad用于调整标题与图的间距
ax.set_xlabel(‘X轴标签’, fontsize=14)
ax.set_ylabel(‘Y轴标签’, fontsize=14)
ax.tick_params(axis=‘both’, which=‘major’, labelsize=11)  # 设置主刻度标签大小
ax.legend(fontsize=12, title=‘图例’, title_fontsize=13)

3.3 根据输出媒介调整字体大小的经验法则

这是一个非常实用的技巧。在笔记本里看着刚好的字,导出后可能就太小了。

  1. 屏幕显示 (Jupyter Notebook, 网页) :基础字体大小( font.size )可以设置在10-12pt。标题可以放到16-20pt。
  2. 打印或PDF报告 :由于打印分辨率高,需要更大的字体。建议基础大小从12pt起,标题18-24pt。一个简单的检查方法是:将图表导出为PDF后,在PDF阅读器中以“实际大小”(100%)视图查看,确保在手臂长度的距离内清晰可读。
  3. 海报或展板 :需要远距离阅读,字体必须足够大。坐标轴标签可能需要24-36pt,标题可能需要48pt以上。这时可能需要同步调整图形尺寸( figsize ),避免文字挤在一起。

一个实用的调试技巧 :在调整过程中,我习惯先以最终输出媒介(如A4纸)的尺寸设置 figsize (例如 figsize=(11.7, 8.3) 英寸,接近A4),然后在这个画布上调整字体,这样看到的效果最接近最终结果。

4. 字体粗细(Weight)与样式(Style):强调与区分

粗细和样式是用于创建视觉对比和强调的轻量级工具。

4.1 字体粗细(Weight)

fontweight 参数接受数字(100-900,间隔100)或字符串。常见的字符串值有:

  • ‘ultralight’ / ‘light’ (200-300):很细,慎用,可能打印不清晰。
  • ‘normal’ (400):标准。
  • ‘medium’ (500):中等。
  • ‘semibold’ (600):半粗。
  • ‘bold’ (700):粗体。 最常用的强调选项
  • ‘heavy’ / ‘black’ (800-900):特粗。
ax.set_title(‘这是主标题,需要加粗强调’, fontweight=‘bold’, fontsize=16)
ax.set_xlabel(‘X轴标签,使用正常粗细’, fontweight=‘normal’)
# 或者使用数字
ax.text(0.5, 0.5, ‘重要的注释’, fontweight=750, transform=ax.transAxes)

4.2 字体样式(Style)

style 参数主要用于控制斜体。

  • ‘normal’ :正常(默认)。
  • ‘italic’ :斜体。常用于强调、引用或表示变量。
  • ‘oblique’ :倾斜体。一种模拟的斜体,当字体本身没有斜体变体时使用。视觉效果与 italic 类似但可能略差。
# 在学术图表中,常用斜体表示变量或特定术语
ax.set_ylabel(‘Energy (J)’, style=‘italic’)  # 单位J有时用斜体
ax.text(2, 50, ‘$f(x) = \sin(x)$’, style=‘italic’)  # 结合LaTeX,斜体是自动的

注意事项 :不是所有字体都包含完整的粗体(Bold)或斜体(Italic)变体。如果你设置了一个不存在的粗细/样式组合,matplotlib可能会尝试用算法模拟(如合成粗体),效果通常不如真正的字体变体好。最稳妥的方式是选择像 ‘Arial’ ‘Times New Roman’ ‘思源黑体’ 这样拥有完整字族(Regular, Bold, Italic, Bold Italic)的字体。

5. 文字方向、旋转角度与对齐:应对复杂布局

当坐标轴标签很长、刻度密集或者需要在特定位置添加注释时,调整文字的方向和角度就变得至关重要。

5.1 旋转角度(Rotation)

这是最常用的功能,通过 rotation 参数控制,单位是度(°)。

  • 水平 rotation=0 (默认)
  • 垂直 rotation=90
  • 任意角度 rotation=45 (45度倾斜)

应用场景一:长标签的x轴刻度 当x轴刻度标签是长字符串(如长日期、长名称)时,水平放置会导致重叠。

import matplotlib.pyplot as plt
import numpy as np

categories = [‘非常长的类别名称A’, ‘另一个超长的类别B’, ‘中等长度C’, ‘短D’, ‘长标签E’]
values = [25, 32, 18, 27, 21]

fig, ax = plt.subplots(figsize=(10, 6))
ax.bar(categories, values)
ax.set_xticks(range(len(categories)))
ax.set_xticklabels(categories, rotation=45, ha=‘right’) # 旋转45度,右对齐
ax.tick_params(axis=‘x’, which=‘major’, labelsize=10)
plt.tight_layout() # 自动调整布局,为旋转的标签留出空间
plt.show()

关键点 :旋转标签时,务必同时调整 水平对齐方式( ha rotation=45 配合 ha=‘right’ 通常比默认的 ha=‘center’ 看起来更整齐,标签的末端更靠近刻度线。

应用场景二:y轴标签 有时为了节省横向空间或追求设计感,会将y轴标签旋转。

ax.set_ylabel(‘销售额 (百万美元)’, rotation=0, ha=‘right’, va=‘center’)
# rotation=0 但通过 ha=‘right’ 让标签靠右,与y轴平行

5.2 文字方向(Direction)与书写模式

这是一个更底层的属性,通过 rotation_mode 参数与 rotation 配合使用。

  • rotation_mode=‘default’ :先旋转文字,然后对齐锚点。这是默认行为。
  • rotation_mode=‘anchor’ :先对齐文字,然后整体旋转。这个模式在旋转非零角度时,能更好地保持文字与锚点(如数据点)的相对位置,避免文字“飘走”。

set_direction 函数(或 direction 参数)用于设置文本的书写方向,对于中文等竖排文字有用,但日常使用较少。更常见的竖排效果可以通过将 rotation 设为90度,并配合 va=‘bottom’ ha=‘center’ 来实现。

5.3 对齐方式(Alignment)

对齐方式决定了文本边界框相对于其锚点(即指定的x, y坐标)的位置。

  • 水平对齐 ( ha / horizontalalignment ) : ‘left’ , ‘center’ , ‘right’
  • 垂直对齐 ( va / verticalalignment ) : ‘bottom’ , ‘baseline’ , ‘center’ , ‘top’

对齐方式在与 rotation 结合时尤其重要。例如,为一个数据点添加注释:

ax.plot(x_data, y_data, ‘o’)
# 在点 (x_data[5], y_data[5]) 上方添加注释
ax.annotate(‘峰值点’,
            xy=(x_data[5], y_data[5]),  # 箭头指向的点
            xytext=(0, 15),             # 文本位置相对于xy的偏移(像素)
            textcoords=‘offset points’,
            ha=‘center’,                # 文本水平居中
            va=‘bottom’,                # 文本底部与偏移点对齐
            arrowprops=dict(arrowstyle=‘->’, connectionstyle=‘arc3’))

6. 综合实战:定制一张出版级图表

让我们将以上所有知识点融合,从头创建一张满足学术出版要求(如APA格式)的图表。假设我们需要绘制两组数据的对比折线图。

import matplotlib.pyplot as plt
import numpy as np
import matplotlib

# 1. 全局字体与大小设置(符合出版要求)
plt.rcParams.update({
    ‘font.family’: ‘serif’,        # 使用衬线字体,更正式
    ‘font.serif’: [‘Times New Roman‘], # 指定Times New Roman
    ‘font.size’: 11,               # 正文基础大小
    ‘axes.titlesize’: 14,          # 图表标题
    ‘axes.labelsize’: 12,          # 坐标轴标签
    ‘xtick.labelsize’: 10,         # x轴刻度
    ‘ytick.labelsize’: 10,         # y轴刻度
    ‘legend.fontsize’: 10,         # 图例
    ‘figure.titlesize’: 16         # 图形总标题(如果使用)
})
plt.rcParams[‘axes.unicode_minus’] = False  # 处理负号

# 2. 生成示例数据
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# 3. 创建图形和坐标轴,指定适合打印的尺寸(单位:英寸)
fig, ax = plt.subplots(figsize=(6, 4), dpi=300) # 高DPI确保打印清晰
fig.suptitle(‘Sine and Cosine Functions Comparison’, fontweight=‘bold’)

# 4. 绘制数据
line1, = ax.plot(x, y1, color=‘#1f77b4’, linewidth=2, label=‘$y = \sin(x)$‘) # 使用LaTeX
line2, = ax.plot(x, y2, color=‘#d62728’, linewidth=2, linestyle=‘--’, label=‘$y = \cos(x)$‘)

# 5. 设置坐标轴标签(使用斜体表示变量)
ax.set_xlabel(‘Independent variable $x$‘, style=‘italic’)
ax.set_ylabel(‘Dependent variable $y$‘, style=‘italic’)

# 6. 设置刻度与网格
ax.set_xlim(0, 10)
ax.set_ylim(-1.2, 1.2)
ax.set_xticks(np.arange(0, 11, 2))
ax.set_yticks(np.arange(-1, 1.1, 0.5))
ax.grid(True, which=‘major’, linestyle=‘:’, linewidth=0.5, alpha=0.7) # 添加浅色网格

# 7. 添加图例,并精细控制其位置和边框
ax.legend(loc=‘upper right’, frameon=True, fancybox=False, edgecolor=‘black’, framealpha=0.8)
# loc=‘best’ 可能不稳定,直接指定‘upper right’更可控

# 8. 在关键位置添加自定义文本注释
ax.text(2.5, 0.8, ‘Local Maximum\nof Sine’, ha=‘center’, va=‘bottom’,
        fontsize=9, style=‘italic’, bbox=dict(boxstyle=‘round,pad=0.3’, facecolor=‘wheat’, alpha=0.7))
# 使用bbox给文本添加一个浅色圆角背景框,使其更突出

# 9. 调整x轴刻度标签(假设标签很长)
# 这里演示旋转,虽然我们的x是数字不需要
# ax.set_xticklabels([f‘Point {i}‘ for i in range(11)], rotation=30, ha=‘right’)

# 10. 紧凑布局并保存
plt.tight_layout()
# 保存为多种格式,适用于不同用途
fig.savefig(‘professional_plot.pdf’, format=‘pdf’, bbox_inches=‘tight’) # 用于打印或投稿
fig.savefig(‘professional_plot.png’, dpi=300, bbox_inches=‘tight’)     # 用于网页或PPT
plt.show()

这张图几乎用到了我们讨论的所有属性:字体族(Times New Roman)、大小层级(标题14pt,标签12pt)、粗细(主标题加粗)、样式(坐标轴标签斜体)、对齐(图例、注释文本),并且考虑了高DPI输出。 bbox_inches=‘tight’ 在保存时能自动裁剪掉图形周围多余的空白区域。

7. 常见问题排查与进阶技巧

即使掌握了所有参数,实践中还是会遇到一些棘手的问题。

7.1 字体不生效或回退到默认字体

症状 :设置了中文字体,但中文仍显示为方框,或者指定了 ‘Arial’ 却显示为其他字体。 排查步骤

  1. 确认字体名称正确 :使用 matplotlib.font_manager.findfont(FontProperties(family=[‘YourFontName’])) 检查matplotlib是否能找到该字体。如果返回一个 .ttf .otf 文件路径,说明名称正确。
  2. 清除字体缓存 :Matplotlib会缓存字体列表以加速加载。如果新安装了字体,需要删除缓存文件。缓存文件通常位于 ~/.matplotlib (Linux/macOS)或 C:\Users\<用户名>\.matplotlib (Windows)下的 fontlist-*.json 文件。删除后重启Python内核或重新运行程序。
  3. 检查字体栈顺序 :如果你通过 rcParams[‘font.sans-serif’] 设置了一个列表,确保目标字体在列表靠前的位置。Matplotlib会使用列表中找到的第一个可用字体。
  4. 使用绝对路径 :如前所述,这是最可靠的方法,尤其对于中文字体。

7.2 保存后字体与屏幕显示不一致

症状 :在Jupyter里显示正常,保存为PDF或PNG后字体变了或中文没了。 原因与解决

  • PDF后端问题 :Matplotlib保存PDF时,默认会嵌入字体。但如果你的字体许可证不允许嵌入,或者字体文件找不到,就会发生替换。
    • 解决方案:确保使用允许嵌入的字体(如开源字体思源黑体、Noto Sans)。在保存PDF时,可以检查生成的PDF文件属性中的字体列表。
    • 使用 plt.rcParams[‘pdf.fonttype’] = 42 可以将字体保存为TrueType(更兼容),但文件可能变大。
  • Agg后端(用于PNG/SVG等) :通常没问题。如果出现问题,尝试在导入matplotlib后立即设置后端和字体。
    import matplotlib
    matplotlib.use(‘Agg’)  # 在导入pyplot之前设置
    import matplotlib.pyplot as plt
    plt.rcParams[‘font.sans-serif’] = [‘SimHei’]
    

7.3 刻度标签重叠与自动调整

即使旋转了标签,在类别极多或图形很窄时,重叠仍可能发生。

  • 使用 plt.tight_layout() fig.tight_layout() :这是首选方案,它能自动调整子图参数,为标签留出空间。
  • 手动调整图形边距 :使用 plt.subplots_adjust() fig.subplots_adjust() 手动调整 left , bottom , right , top 等参数。
    fig, ax = plt.subplots(figsize=(8, 4))
    # ... 绘图代码 ...
    plt.subplots_adjust(bottom=0.25)  # 为底部的x轴标签增加25%的图形高度作为边距
    
  • 减少刻度密度 :如果刻度太密,可以考虑减少显示的刻度数量。
    ax.xaxis.set_major_locator(plt.MaxNLocator(10))  # 最多显示10个主刻度
    

7.4 进阶:使用LaTeX渲染数学公式与特殊字体

Matplotlib集成了LaTeX引擎,可以渲染复杂的数学公式,这本身也涉及字体(数学字体)。

# 在rcParams中启用LaTeX渲染(需要系统安装LaTeX,如MiKTeX或TeX Live)
plt.rcParams.update({
    “text.usetex”: True,  # 使用LaTeX渲染所有文本
    “font.family”: “serif”,
    “font.serif”: [“Computer Modern Roman”], # LaTeX常用字体
})

# 或者,局部使用LaTeX
ax.set_title(r‘$\frac{\partial u}{\partial t} = \alpha \nabla^2 u$‘) # 渲染偏微分方程
ax.set_xlabel(r‘$\theta$ (radians)‘)

注意 :启用 text.usetex=True 会显著减慢图形渲染速度,因为它需要调用外部LaTeX编译器。对于简单的公式,使用Matplotlib内置的MathText(用 $...$ 包裹)通常是更快的选择,且不需要外部依赖。内置MathText的字体由 rcParams[‘mathtext.fontset’] 控制(如 ‘stix’ , ‘dejavusans’ , ‘cm’ )。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐