文章/Matplotlib/第 28 章
范围、方向与显隐
Matplotlib · 第 28 / 43 章
范围:set_xlim / margins / autoscale
坐标轴的范围(view limits)和数据范围(data limits)是两回事。plot() 只更新数据范围,视图范围由 autoscale 根据数据范围加边距算出来;一旦你手动设过范围,autoscale 就关掉了。
Axes.set_xlim(left=None, right=None, *, emit=True, auto=False, xmin=None, xmax=None)
Axes.set_ylim(bottom=None, top=None, *, emit=True, auto=False, ymin=None, ymax=None)
Axes.set_xbound(lower=None, upper=None) # 只给 autoscale 划边界,不关 autoscale
Axes.margins(*margins, x=None, y=None, tight=True) # 数据两侧留白比例
Axes.autoscale(enable=True, axis="both", tight=None) # 重新打开 autoscale
Axes.autoscale_view() # 立刻按当前数据重算一次
Axes.set_box_aspect(aspect=None) # 框的宽高比(不是数据比例)| 参数 | 一句话 | 默认值的影响 |
|---|---|---|
left / right 只给一个 |
只改一端 | 另一端保持当前值 |
set_xlim |
直接定视图 | 同时关掉 x 轴 autoscale |
set_xbound |
给 autoscale 划上下界 | autoscale 仍是开的 |
margins |
数据两侧留白比例 | 0.05,即两端各留 5% |
set_box_aspect |
框高/框宽 | None 由布局决定 |
最小示例
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6))
print(np.round(ax.get_xlim(), 2))
# 输出: [-0.25 5.25]
plt.show()
数据是 0~5,边界变成 -0.25 ~ 5.25——两端各多出 0.25,正是默认 margins(0.05) 按数据跨度 5 的 5% 算出来的。
上面为什么写 np.round(ax.get_xlim(), 2) 而不是直接 print(ax.get_xlim())?因为 get_xlim() 返回的是 numpy 标量,numpy 2.0 起它的 repr 变了:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6))
print(ax.get_xlim())
# 输出: (np.float64(-0.25), np.float64(5.25)) ← numpy 2.x
# 输出: (-0.25, 5.25) ← numpy 1.x,同一个值数字本身没错,只是显示形式随 numpy 版本变。本文涉及范围、刻度的打印统一用 np.round,输出才能跨版本一致。
注意别用内置 round() 包一层——round() 不改变 numpy 标量类型:
import numpy as np
print(tuple(round(v, 2) for v in np.array([-0.25, 5.25])))
# 输出: (np.float64(-0.25), np.float64(5.25)) ← 还是 np.float64
print(tuple(np.round(np.array([-0.25, 5.25]), 2)))
# 输出: (np.float64(-0.25), np.float64(5.25)) ← 一样没救要么用 np.round 输出数组,要么 float(v) 显式转成 Python 浮点。
set_xlim 一个参数也行
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6))
ax.set_xlim(left=0)
print(np.round(ax.get_xlim(), 2))
# 输出: [0. 5.25]只给 left 时右端保持不动。反过来 ax.set_xlim(right=10) 只拉右端。这两个关键字是 3.x 才有的写法,老代码用 ax.set_xlim(xmin=0) 等价。
set_xlim 会关掉 autoscale,set_xbound 不会
这是两者唯一的实际差别,也是最容易搞混的地方:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot([0, 1, 2])
ax.set_xlim(0, 10)
print(ax.get_autoscalex_on())
# 输出: False ← set_xlim 之后 autoscale 关了
fig2, ax2 = plt.subplots()
ax2.plot([0, 1, 2])
ax2.set_xbound(0, 10)
print(ax2.get_autoscalex_on())
# 输出: True ← set_xbound 之后 autoscale 还开着所以「后面还要继续加数据、但希望范围别乱跳」用 set_xbound;「就要这个范围,之后不许动」用 set_xlim。
想恢复自动缩放,显式打开:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot([0, 1, 2])
ax.set_xlim(0, 10)
print(ax.get_autoscalex_on())
# 输出: False
ax.autoscale(enable=True, axis='x')
print(ax.get_autoscalex_on())
# 输出: Truemargins:留白比例
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6))
ax.margins(x=.5)
print(np.round(ax.get_xlim(), 2))
# 输出: [-2.5 7.5] ← 数据 0~5,两端各留 2.5(= 5 × 0.5)
ax.margins(x=0)
print(np.round(ax.get_xlim(), 2))
# 输出: [0. 5.] ← 数据顶到边框margins 接受两种写法:位置参数 margins(0.1) 同时管 x/y,关键字 margins(x=0, y=.2) 分开管。设 x=0 是画柱状图/直方图的标准动作——柱子本来就该顶到边框。
注意事项
- 手动
set_xlim/set_ylim会关闭该轴的 autoscale,之后加数据视图不跟着变。 set_xbound只限制 autoscale 的边界,get_autoscalex_on()仍是True。margins(x=0)是柱状图的标准配置;折线图留 5% 默认边距即可。axis('equal')改的是数据单位比例(1 个 x 单位 = 1 个 y 单位),set_box_aspect改的是框的物理宽高比,两者不同。- 用
set_box_aspect后imshow的aspect='equal'会被覆盖,别同时给。
方向与比例:翻转 / 等比例 / 宽高比
三个容易被当成一回事的东西:翻转向(哪个方向变大)、等比例(1 单位 x 等不等于 1 单位 y)、宽高比(这个矩形框本身长什么样)。
Axes.invert_xaxis() # 翻转 X(已是反向则变正)
Axes.invert_yaxis() # 翻转 Y
Axes.set_xlim(3, 0) # 等价写法:直接给反序范围
Axes.set_aspect(aspect, adjustable=None, anchor=None) # 数据比例:'equal' / 'auto' / 数值
Axes.axis("equal") # = set_aspect('equal', adjustable='datalim')
Axes.set_box_aspect(aspect) # 框物理高/宽比最小示例
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(5), np.arange(5) ** 2, 'o-')
ax.invert_yaxis() # y 轴倒过来(图像坐标常用)
print(np.round(ax.get_ylim(), 2))
# 输出: [16.8 -0.8] ← 大值在前
plt.show()翻转 Y 的两种写法
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(5), np.arange(5) ** 2)
ax.set_ylim(16, -0.8) # 显式给反序范围,最明确
print(np.round(ax.get_ylim(), 2))
# 输出: [16. -0.8]
fig2, ax2 = plt.subplots()
ax2.plot(np.arange(5), np.arange(5) ** 2)
ax2.set_ylim(reversed(ax2.get_ylim())) # 按当前范围反向,不用记数字
print(np.round(ax2.get_ylim(), 2))
# 输出: [16.8 -0.8]set_ylim(bottom, top) 里 bottom > top 就是反向——名字叫 bottom/top,但位置已经不重要了,只看数值顺序。
invert_yaxis() 是「翻转当前状态」:连调两次会回到原样。要「一定是反向」用 set_ylim(大, 小)。
axis('equal') vs set_aspect('equal')
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot([0, 1, 1], [0, 0, 1])
ax.set_aspect('equal') # 1 单位 x 和 1 单位 y 一样长
print(ax.get_aspect())
# 输出: 1.0
fig2, ax2 = plt.subplots()
ax2.plot([0, 1, 1], [0, 0, 1])
ax2.axis('equal') # 等价于 set_aspect('equal', adjustable='datalim')
print(ax2.get_aspect())
# 输出: 1.0两者 get_aspect() 都返回 1.0,差别在 adjustable:set_aspect('equal') 默认 adjustable='box'(缩框),axis('equal') 用 'datalim'(改数据范围去凑比例)。画地图、画圆时 'box' 更稳;想保住给定的 xlim/ylim 用 'datalim'。
set_box_aspect:框的物理比例
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(6, 3))
ax.plot(np.arange(5))
ax.set_box_aspect(2) # 框高 = 框宽 × 2
print(ax.get_box_aspect())
# 输出: 2.0
plt.show()set_box_aspect(2) 之后,哪怕 figsize=(6, 3),这个 axes 也会被改成竖长条——它会覆盖 figsize 里给的比例。
注意事项
invert_yaxis()是切换(toggle),连调两次等于没调。set_ylim(16, -0.8)与invert_yaxis()视觉一致,但前者可重复执行、后者不行。axis('equal')会改数据范围(adjustable='datalim'),你之前设的 xlim 可能被覆盖。set_aspect('equal')默认改框(adjustable='box'),xlim 保住但图变窄。set_box_aspect与set_aspect互不干扰,可以同时用。- 3.10 没有
set_xinverted()/get_xinverted()(3.11 才加),判断反向用ax.xaxis_inverted()。
隐藏坐标轴:五个层次
「隐藏坐标轴」有五个粒度,从「全不要」到「只去掉一条框线」,选错就会出现「刻度数字还在」「框线还在」这种半成品效果。
Axes.set_axis_off() # ① 最狠:轴对象整个不画
Axes.xaxis.set_visible(False) # ② 只隐藏 x 轴(含刻度和标签)
Axes.tick_params(left=False, labelleft=False,
bottom=False, labelbottom=False) # ③ 只隐藏刻度线和刻度文字
Axes.set_xticks([]) # ④ 等价:清空刻度位置
Axes.spines["top"].set_visible(False) # ⑤ 只隐藏某条框线
Axes.set_axisbelow(True) # 网格放到数据下面| 做法 | 隐藏了什么 | 保留了什么 |
|---|---|---|
set_axis_off() |
框线、刻度、刻度文字、轴标签 | 数据、标题、图例 |
xaxis.set_visible(False) |
该轴全部 | 另一轴、数据 |
tick_params(...=False) |
刻度线 + 刻度文字 | 框线、轴标签 |
set_xticks([]) |
刻度位置(连带文字) | 框线、轴标签 |
spines["x"].set_visible(False) |
一条框线 | 该轴的刻度 |
最小示例
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.set_axis_off() # 整块轴都不要
plt.show()
① set_axis_off():全隐藏
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.plot(np.arange(6), np.arange(6) ** 1.5)
ax.set_axis_off()
ax.set_title('标题还在')
plt.show()标题、图例、text() 都还在,只有「轴」这一整套没了。要恢复用 ax.set_axis_on()。
② xaxis.set_visible(False):只杀一个方向
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.xaxis.set_visible(False)
plt.show()ax.xaxis / ax.yaxis 是 XAxis / YAxis 对象,set_visible(False) 会把刻度、刻度文字、轴标签一起关掉,但下方那条 spine 不受影响——它是 ax.spines['bottom'],属于另一个对象。
③ tick_params:只关刻度,留框线
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.tick_params(left=False, labelleft=False,
bottom=False, labelbottom=False)
plt.show()四个开关各管一件事:left / bottom 是刻度线,labelleft / labelbottom 是刻度文字。只关 labelleft 就保留了短刻度线但没数字——这是「保持框完整、去掉数字噪音」的常用做法。
④ set_xticks([]):清空刻度位置
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.set_xticks([])
ax.set_yticks([])
print(len(ax.get_xticks()))
# 输出: 0
plt.show()效果和 tick_params(...=False) 接近,但同时删掉了刻度位置信息——后面 ax.text 若依赖 get_xticks() 会拿到空数组。
⑤ spines:只去掉一条框线
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([k for k, s in ax.spines.items() if s.get_visible()])
# 输出: ['left', 'bottom']
plt.show()spines 是一个只读字典,键固定 'left' / 'right' / 'top' / 'bottom'。3.11 起支持切片 ax.spines[:](3.10 也实测可用),可以一次遍历。
set_axisbelow:网格与数据的层级
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(True)
print(ax.get_axisbelow())
# 输出: line
ax.set_axisbelow(True) # 轴和网格压到数据下面
plt.show()get_axisbelow() 默认返回 'line'(在线的下面、patch 的上面)。设 True 等价 'line',设 'stamp' 是「压在所有东西下面」,设 False 会让网格盖住数据。
注意事项
set_axis_off()不影响标题、图例、text()、table()——它们不是「轴」的一部分。xaxis.set_visible(False)不会隐藏下方那条 spine。tick_params(left=False)只关刻度线;要连数字一起去掉必须再给labelleft=False。set_xticks([])会清掉刻度位置,之后get_xticks()返回空数组。spines的键只有四个,拼错(如'Left')会KeyError。set_axisbelow('stamp')才是真正「网格在一切之下」;True只压到线下面,柱状图仍会盖住网格。