目录24
文章/Matplotlib/第 30 章
轴框、网格、尺度与标题
Matplotlib · 第 30 / 43 章
spines:轴框线
spines 是包围绘图区的那四条线(不是刻度、不是轴标签)。它们各自是 Spine 对象,可以单独隐藏、改色、改位置、截断。
Axes.spines["left" | "right" | "top" | "bottom"]
Spine.set_visible(False)
Spine.set_position(("outward", n) | ("axes", frac) | ("data", val) | "center" | "zero")
Spine.set_bounds(low, high) # 只画这一段
Spine.set_color(c) / set_linewidth(w) / set_linestyle(ls)
ax.spines[:] # 切片:一次处理四条(3.11 正式支持,3.10 实测可用)| 参数 | 一句话 | 默认值的影响 |
|---|---|---|
set_visible(False) |
去掉这一条 | 默认四条全画 |
set_position(('outward', n)) |
往外挪 n 点 | 默认 ('outward', 0) |
set_position('zero') |
挪到数据 0 处 | 适合「过原点坐标轴」 |
set_bounds(low, high) |
只画这一段 | 默认整条 |
set_color / set_linewidth |
颜色 / 粗细 | 默认黑、0.8pt |
最小示例
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6), np.arange(6) ** 1.5, 'o-')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
print(sorted(ax.spines.keys()))
# 输出: ['bottom', 'left', 'right', 'top']
plt.show()
set_position 的四种位置
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3])
ax.spines['left'].set_position(('outward', 10)) # 离框 10pt
print(ax.spines['left'].get_position())
# 输出: ('outward', 10)
ax.spines['bottom'].set_position('zero') # 挪到 y=0
print(ax.spines['bottom'].get_position())
# 输出: zero四种取值:
| 写法 | 含义 |
|---|---|
('outward', n) |
沿法线往外挪 n 点 |
('axes', frac) |
放在框内 0~1 的比例位置 |
('data', val) |
放在数据坐标 val 处 |
'center' / 'zero' |
快捷写法,等于 ('data', 中点) / ('data', 0) |
默认值是 ('outward', 0)。('data', 0) 就是「过原点的坐标轴」,做「四象限」图必用。
set_bounds:截断
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6), np.arange(6) ** 1.5)
ax.spines['left'].set_bounds(2, 10)
print(ax.spines['left'].get_bounds())
# 输出: (2, 10)
plt.show()set_bounds(low, high) 只画 low~high 这一段——这就是「断轴(broken axis)」的实现方式:两条 spine 各截一半,中间留白。
注意事项
spines是只读字典,不能增删键,只能set_*。set_position的('outward', n)单位是点,不是数据单位。set_bounds之后对应的刻度仍按原范围显示,可能出现「刻度在但框线没有」。ax.spines[:]在 3.10 实测可用,但官方文档把它标注为 3.11 正式支持,跨版本写for s in ax.spines.values()更保险。- 隐藏 spine 不影响刻度:
spines['bottom'].set_visible(False)后下方刻度还在(这正是 hide-params 图里最后一格的样子)。
网格线 grid()
网格线默认跟随主刻度,且默认就在数据下层。要次网格、要某个方向、要不同样式,都得显式指定 which 和 axis。
Axes.grid(visible=None, which="major", axis="both", **kwargs)| 参数 | 取值 | 说明 |
|---|---|---|
visible |
True / False / None |
None 是切换 |
which |
'major' / 'minor' / 'both' |
默认只管主刻度 |
axis |
'x' / 'y' / 'both' |
默认两个方向 |
color / ls / lw / alpha |
同 Line2D |
样式 |
最小示例
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 4 * np.pi, 200)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x))
ax.minorticks_on()
ax.grid(which='major', color='0.55', ls='-', lw=.8, alpha=.7)
ax.grid(which='minor', color='0.85', ls=':', lw=.6)
plt.show()主网格实线深灰、次网格点线浅灰——这是「网格存在感足够但不抢戏」的标准做法。
只给一个方向
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6), np.arange(6) ** 1.5, 'o-')
ax.grid(axis='y', color='0.6', ls='--', alpha=.6)
print(len(ax.get_xgridlines()), len(ax.get_ygridlines()))
# 输出: 8 8
print(ax.get_xgridlines()[0].get_visible(), ax.get_ygridlines()[0].get_visible())
# 输出: False True注意 len() 两边都是 8——网格线对象一直都在,axis= 只是把另一方向的 visible 设成 False,并不是不创建。所以想判断「哪个方向有网格」不能数个数,要看 get_visible()。
只画横向网格是折线图的常见配置:y 方向辅助读数,x 方向不画以免和折线打架。
网格压在数据下面
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.bar(['A', 'B', 'C'], [3, 5, 4])
ax.grid(True, axis='y')
print(ax.get_axisbelow())
# 输出: line ← 默认值,字符串 'line'
ax.set_axisbelow(True) # 柱子在网格之上
print(ax.get_axisbelow())
# 输出: True ← 显式设过就变成布尔
plt.show()返回值有三种形态,别被 True / 'line' 混着搞晕:默认是字符串 'line'(网格在折线下面、在 patch 上面),set_axisbelow(True) 之后读出来就是布尔 True,set_axisbelow('stamp') 读出来是 'stamp'。True 和 'line' 表达的是同一个层级,只是设置方式不同。
不设 set_axisbelow(True) 时网格会画在柱子上面,视觉上像柱子被划了几道。
注意事项
grid()默认只管which='major',次网格要显式which='minor'且先minorticks_on()。grid()不传visible是切换当前状态,传True/False才是设定。set_axisbelow(True)只压到线下面;柱状图的 patch 仍会盖住网格,要彻底在下面用'stamp'。- 网格线的条数等于对应方向的刻度条数,
set_xticks([])后网格自动消失。
对数轴与自定义 scale
set_xscale 换的是刻度系统(Locator + Formatter 一起换),不是「把数据取对数再画」—— 数据本身不变,只是轴被压缩了。
Axes.set_xscale(value, **kwargs)
Axes.set_yscale(value, **kwargs)
Axes.set_xscale("log", base=10, subs=(1.0,), nonpositive="clip")
Axes.loglog() / semilogx() / semilogy() # 一次性设 x/yvalue |
含义 | 常用参数 |
|---|---|---|
'linear' |
线性(默认) | — |
'log' |
对数 | base=10、subs、nonpositive |
'symlog' |
0 附近线性、远处对数 | linthresh、linscale |
'logit' |
logit 变换 | 数据必须在 (0, 1) |
'function' |
自定义正反函数 | functions=(fwd, inv) |
'functionlog' |
自定义 + 对数刻度 | 同上 |
最小示例
import matplotlib.pyplot as plt
import numpy as np
x = np.logspace(0, 4, 200)
fig, ax = plt.subplots()
ax.plot(x, x ** 2)
ax.set_xscale('log')
ax.set_yscale('log')
print(np.round(ax.get_xticks(), 2)[:4])
# 输出: [ 0.01 0.1 1. 10. ]
plt.show()
base:换底
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.logspace(0, 4, 200), np.logspace(0, 4, 200))
ax.set_xscale('log', base=2)
fig.canvas.draw()
print(np.round(ax.get_xticks(), 2)[:5])
# 输出: [ 0.12 0.5 2. 8. 32. ]
plt.show()base=2 时刻度落在 2 的幂上,标签是 2^1、2^3 这种(由 LogFormatterSciNotation 决定)。做「翻倍关系」的图(内存、文件大小)常用。
nonpositive:0 和负值怎么办
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([0, 1, 10, 100], [1, 2, 3, 4])
ax.set_xscale('log')
try:
fig.canvas.draw()
except Exception as e:
print(type(e).__name__, str(e)[:80])含 0 或负值时 log 会把它们掩码掉(图上是空白),不抛异常。要「夹到最小正值」就传 nonpositive='clip';要「当无效值丢掉的发声提示」用默认 'mask'。
symlog:正负都有
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
xs = np.linspace(-100, 100, 400)
ax.plot(xs, xs)
ax.set_xscale('symlog', linthresh=10)
fig.canvas.draw()
print(len(ax.get_xticks()), np.round(ax.get_xticks(), 1))
# 输出: 5 [-100. -10. 0. 10. 100.]
plt.show()linthresh 是线性区的半宽(|x| < linthresh 走线性)。设 linthresh=10 表示「-10~10 之间保持正常间距」。
自定义 scale:'function'
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
xs = np.linspace(0, 10, 200)
ax.plot(xs, xs ** 2)
ax.set_yscale('function', functions=(np.sqrt, np.square))
fig.canvas.draw()
print(type(ax.yaxis.get_scale()).__name__)
# 输出: str
plt.show()functions=(正向, 反向) 必须互为反函数,否则刻度和数据会错位。这是做「平方根轴」「倒数轴」的通用手段。
注意事项
set_yscale('log')会把 ≤0 的值掩码成空白,不报错;图上白块先怀疑它。- 对数轴的次刻度默认打开(
subs),minorticks_off()关不掉它。 base只影响刻度位置,不改变数据映射。'logit'要求数据严格在(0, 1)内,否则UserWarning: Data has no values between 0 and 1。'function'必须给一对互逆函数,np.log/np.exp这种配错会静默出错。loglog()/semilogx()/semilogy()只是语法糖,等价于两次set_*scale('log')。
标题与轴标签
标题和轴标签都是 Text 对象,挂在固定位置,但可以改对齐方式、加 padding、给 fontdict。多子图时还要区分「子图标题」和「整幅标题」。
Axes.set_title(label, fontdict=None, loc="center", pad=6, **kwargs)
Axes.set_xlabel(xlabel, fontdict=None, labelpad=None, *, loc=None, **kwargs)
Axes.set_ylabel(ylabel, fontdict=None, labelpad=None, *, loc=None, **kwargs)
Figure.suptitle(t, x=0.5, y=0.98, ha="center", **kwargs) # 整幅大标题
Figure.supxlabel(t) / Figure.supylabel(t)| 参数 | 一句话 | 默认值的影响 |
|---|---|---|
loc |
'left' / 'center' / 'right' |
'center',长标题不贴边 |
pad / labelpad |
与轴的距离(点) | 6 / 由 mpl 算 |
fontsize / fontweight / color |
文字样式 | 跟 rcParams |
suptitle |
整幅标题 | 多子图时不要用 set_title 当总标题 |
最小示例
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(5))
ax.set_title('销售额趋势', fontsize=11, pad=12)
ax.set_xlabel('月份', labelpad=8)
ax.set_ylabel('万元')
plt.show()loc:三种对齐
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3])
ax.set_title('左对齐标题', loc='left')
print(ax.title.get_position())
# 输出: (0.5, 1.0) ← ax.title 永远是居中那个
print(ax._left_title.get_position())
# 输出: (0.0, 1.0) ← 左标题是另一个 Text
print(ax.get_title(loc='left'))
# 输出: 左对齐标题
ax.set_title('居中标题', loc='center')
print(ax.get_title(), '|', ax.get_title(loc='left'))
# 输出: 居中标题 | 左对齐标题 ← 两者互不覆盖这是 loc 最容易踩的坑:loc='left' 并不会把 ax.title 挪到左边,而是写进 ax._left_title 这个独立的 Text;loc='right' 写进 ax._right_title。所以写 ax.title.get_position() 永远读到 (0.5, 1.0)——要用 ax.get_title(loc='left') 读文字、ax._left_title.get_position() 读位置。
loc='left' 让标题贴左边框——收益是排版更整齐(尤其多个子图并排时),代价是和 y 轴标签可能撞上,得配合 pad。
set_xlabel(loc='right') 让 x 轴标签贴右端,配合「单位放右边」的习惯。
多行标题
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3])
ax.set_title('主标题\n副标题(小字说明)', fontsize=10, linespacing=1.6)
plt.show()\n 换行后行距由 linespacing 控制(默认 1.2)。多行标题会把整个绘图区往下挤,pad 要相应调小。
suptitle:整幅标题
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(1, 2, figsize=(9, 3))
for i, ax in enumerate(axes):
ax.plot(np.arange(5), np.arange(5) ** (i + 1))
ax.set_title(f'子图 {i + 1}', fontsize=9)
fig.suptitle('2026 年分季度营收', fontsize=12, y=1.02)
plt.tight_layout()
plt.show()suptitle 挂在 Figure 上,坐标是整幅画布的比例(y=1.02 就是画布上方一点点)。tight_layout() 默认不为 suptitle 留空间,所以要自己给 y。
注意事项
set_title返回Text对象,想事后改文字用返回值或ax.title.set_text()。loc='left'的标题会贴到框左边缘,长标题可能压住 y 轴标签,需要加pad。suptitle的坐标是画布比例,set_title的是轴比例——两者单位不同。tight_layout()不认suptitle,要么手给y,要么改用constrained_layout。labelpad的默认值由字体大小和刻度长度共同决定,设成 0 常常会和刻度文字重叠。- 中文标题要设
rcParams['font.sans-serif'],否则出现方块(见 rcParams 一节)。
综合示例:三个子图的完整排版
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
fig, axes = plt.subplots(1, 3, figsize=(13, 3.6))
x = np.arange(12)
rng = np.random.default_rng(0)
# ① 折线:极简坐标轴(去掉上右框线 + 只留横向网格)
ax = axes[0]
ax.plot(x, rng.normal(size=12).cumsum(), 'o-', color='#4C72B0', ms=4)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.grid(axis='y', color='0.85', lw=.8)
ax.set_axisbelow(True)
ax.set_title('月度累计', loc='left', fontsize=10)
ax.set_xlabel('月', labelpad=6, fontsize=9)
ax.yaxis.set_major_locator(mticker.MaxNLocator(4))
# ② 百分比柱:PercentFormatter + 顶边刻度
ax = axes[1]
ax.bar(['Q1', 'Q2', 'Q3', 'Q4'], [18, 25, 31, 26], color='#DD8452')
ax.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=100, decimals=0))
ax.yaxis.set_major_locator(mticker.MultipleLocator(10))
ax.tick_params(axis='x', direction='in', length=0, pad=6)
ax.tick_params(axis='y', direction='in', colors='0.35', labelsize=8)
ax.set_ylim(0, 40)
ax.set_title('分季度占比', loc='left', fontsize=10)
# ③ 对数轴:base=2 + 次网格
ax = axes[2]
ax.plot(np.logspace(0, 4, 60), np.logspace(0, 4, 60) ** 1.2, color='#2ca02c')
ax.set_xscale('log', base=2)
ax.set_yscale('log')
ax.grid(which='major', color='0.6', lw=.8)
ax.grid(which='minor', color='0.88', ls=':', lw=.6)
ax.set_title('翻倍关系', loc='left', fontsize=10)
fig.suptitle('三个子图的轴设置对照', fontsize=11, y=1.03)
plt.tight_layout()
plt.show()三格分别演示三组最常用的轴配置:① 极简风(去框线 + 单向网格 + MaxNLocator),② 百分比(PercentFormatter + 刻度朝内),③ 对数(base=2 + 主次网格)。loc='left' 统一加在 set_title 上,让三个标题左边缘对齐。