# 子图的布局
# 当带有标题的多个子图并排显示时,多个子图会因区域过于紧凑而出现标题和坐标轴之间相互重叠的问题,而且子图元素的摆放过于紧凑,也影响用户的正常查看。matplotlib 中提供了一些调整子图布局的方法,包括约束布局、紧密布局和自定义布局,通过这些方法可以合理布局多个子图。下面将对子图的布局方法进行详细介绍。
# 约束布局
# 约束布局是指通过一系列限制来确定画布中元素的位置的方式,它预先会确定一个元素绝对定位,之后以该元素的位置为基点对其他元素进行绝对定位,从而灵活地调整元素的位置。
malplotlib 在绘制多子图时默认并未启用约束布局,它提供了两种方式启用约束布局 :第一种方式是使用 subplots() 或 figure() 函数的 constrained_layout 参数 ;第二种方式是修改 figure.constrained_layout.use 配置项。具体内容如下。
( 1 ) 使用 constrained_layout 参数
matplotlib 使用 subplots() 或 figure() 函数创建子图或画布时, 可以将 constrained_layout 参数的值设为 True, 进而调整子图元素的布局,示例代码如下:
plt.subplots(constrained_layout=True)
# ( 2 ) 修改 figure.constrained_layout.use 配置项
matplotlib 可以通过 rcParams 字典或 r() 函数修改 figure.constrained_layout.use 配置项的值为 True, 进而调整子图元素的布局,示例代码如下:
plt.rcParams['figure.constrained__layout.use'] = True
# 另外,matplotlib 还可以修改以下配置项来调整子图之间的距离。
# · figure.constrained_layout.w_pad/h_pad :表示绘图区域的内边距,默认为 0.04167。
· figure.constrained_layout.wspace/hspace :表示子图之间的空隙,默认为 0.02。
# 例如,使用 subplots() 函数绘制 2 行 2 列的带有坐标轴标签的子图,并通过 subplots() 函数的 constrained_layout 参数启动约束布局,解决子图之间标签重叠的问题,具体代码如下。
In [10]:
import matplotlib.pyplot as plt
# 绘制子图并启用约束布局
fig, axs = plt.subplots(2, 2, constrained_layout=True)
ax_one = axs[0, 0]
ax_one.set_title('Title')
ax_two = axs[0, 1]
ax_two.set_title('Title')
ax_thr = axs[1, 0]
ax_thr.set_title('Title')
ax_fou = axs[1, 1]
ax_fou.set_title('Title')
plt.show()
# 约束布局调整前与调整后的效果如图 5-15 所示。
# 此外,Figure 类对象还可以通过以下方法使用和设置约束布局。
# · set_constrained_layout() : 设置是否使用约束布局。若该参数设为 None, 则说明使用配置文件中 rcParams['figure.constrained_layout.use'] 指定的值。
· set_constrained_layout_pads() :设置子图的内边距。
# 需要注意的是,约束布局仅适用于调整刻度标签、轴标签、标题和图例的位置,而不会调整子图其他元素的位置。因此,使用约束布局后的子图之间仍然会出现图表元素被裁剪或重叠的问题。
# 紧密布局
# matplotlib 中紧密布局与约束布局相似,它采用紧凑的形式将子图排列到画布中,仅适用于刻度标签、坐标轴标签和标题位置的调整。
pyplot 中提供了两种实现紧密布局的方式:第一种方式是调用 tight_layout() 函数;第二种方式是修改 figure.autolayoutrcParam 配置项。关于紧密布局的两种实现方式的介绍如下。
( 1 ) 调用 tight_layout() 函数
matplotlib 在 1.1 版本中引入了 tight_layout() 函数,通过该函数调整子图的内边距及子图的间隙,使子图能适应画布的绘图区域。light_layout() 函数的语法格式如下:
tigh_layout(pad=l.08, h_pad=None, w_pad=None, rect=None)
# 该函数的参数含义如下。
# · pad :表示画布边缘与子图边缘之间的空白区域的大小,默认为 1.08。
· h_pad, w_pad :表示相邻子图之间的空白区域的大小。
· rect:表示调整所有子图位置的矩形区域的四元组 (left, bottom,right, top),默认为 (0,0,1,1)。
# 需要注意的是,当 pad 参数设为 0 时,空白区域的文本会出现被裁剪的现象,之所以出现文本部分缺失的情况,可能是因为算法错误或受到算法的限制。因此,官方建议 pad 参数的取值应至少大于 0.3。
( 2 ) 修改 figure.autolayoutrcParam 配置项
pyplot 可以通过 rcParams 字典或 rc() 函数修改 figure.autolayoutrcParam 配置项的值为True, 使子图元素适应画布,示例代码如下:
In [11]:
import matplotlib.pyplot as plt
fig, axs = plt.subplots (2, 2)
ax_one = axs[0, 0]
ax_one.set_title('Title')
ax_two = axs[0, 1]
ax_two.set_title('Title')
ax_thr = axs[1, 0]
ax_thr.set_title('Title')
ax_fou = axs[1, 1]
ax_fou.set_title('Title')
# 调整子图之间的距离
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=2)
plt.show ()
# 紧密布局调整前与调整后的效果如图 5-16 所示。
# 自定义布局
# matplotlib 的 gridspec 模块是专门指定画布中子图位置的模块,该模块中包含一个GridSpec 类,通过显式地创建 GridSpec 类对象来自定义画布中子图的布局结构,使得子图能够更好地适应画布。GridSpec 类的构造方法的语法格式如下 :
GridSpec (nrows, ncols, figure=None, left=None, bottom=None, right=None,
top=None, wspace=None, hspace=None, width_ratios=None, height_ratios=None)
# 该方法常用参数的含义如下。
# · nrows :表示行数。<br.>· ncols :表示列数。
· figure :表示布局的画布。
· left, bottom, right, top :表示子图的范围。
· wspace :表示子图之间预留的宽度量。
· hspace :表示子图之间预留的高度量。
# GridSpec 类对象的使用方式与数组的使用方式相似,采用索引或切片的形式访问每个布局元素。此外,matplotlib 中还为 Figure 对象提供了快速添加布局结构的方法 add_gridspec()。下面分别使用两种方式创建自定义的布局结构。
( 1 ) 使用 GridSpec() 方法创建子图的布局结构
这种方式需要创建子图和 GridSpec 类对象,之后在调用 add_subplot() 方法时传入 GridSpec 类对象即可,具体示例如下。
In [12]:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig2 = plt.figure()
spec2 = gridspec.GridSpec(ncols=2, nrows=2, figure=fig2)
f2_ax1 = fig2.add_subplot(spec2[0, 0])
f2_ax2 = fig2.add_subplot(spec2[0, 1])
f2_ax3 = fig2.add_subplot(spec2[1, 0])
f2_ax4 = fig2.add_subplot(spec2[1, 1])
plt.show ()
# 以上示例创建的子图布局如图 5-17 所示。
# ( 2 ) 使用 add_gridspec() 方法向画布添加布局结构
这种方式需要创建画布和 GridSpec 类对象,之后在调用 add_subplot() 方法添加子图时传入一个 GridSpec 类对象即可,具体示例如下。
In [13]:
fig3 = plt.figure()
gs = fig3.add_gridspec(3, 3)
f3_ax1 = fig3.add_subplot(gs[0, :])
f3_ax1.set_title('gs[0, :]')
f3_ax2 = fig3.add_subplot(gs[1, :-l])
f3_ax2.set_title('gs[1, :-1]')
f3_ax3 = fig3.add_subplot(gs[1:, -1])
f3_ax3.set_title('gs[1:, -1]')
f3_ax4 = fig3.add_subplot(gs[-1, 0])
f3_ax4.set_title('gs[-1, 0]')
f3_ax5 = fig3.add_subplot(gs[-1, -2])
f3_ax5.set_title('gs[-1, -2]')
# 以上示例创建的子图布局如图 5-18 所示。
# 注意:
# 使用 subplot2grid() 函数创建的子图默认已经拥有了自定义的布局,例如:
ax = plt.subplot2grid((2, 2), (0, 0))
# 等价于 :
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 2)
ax = plt.subplot(gs[0, 0])
# 实例5: 2018 年上半年某品牌汽车销售情况
# 随着人们的生活水平日益提高,汽车已经成为人们出行的代步工具,为人们的生活带来了便利。已知某品牌汽车公司分别在北京、上海、广州、深圳、浙江、山东设立了6个分公司,各分公司在 2018 年的销售额十分可观,具体如表 5-5 和表 5-6 所示。
# 根据表 5-5 和表 5-6 的数据,使用 3 个子图进行展示:在第 0 行第 0 列的区域中,绘制反映 2018 上半年汽车销售额的柱形图;在第 1 行第 0 列和第 1 行第 1 列的区域中,绘制反映 2018 上半年各分公司汽车销量的折线图和堆积面积图,具体代码如下。
In [14]:
# 05_cars_sales
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.rcParams["font.sans-serif"] = ["SimHei"]
x_montih = np.array(['1月', '2月', '3月', '4月', '5月', '6月'])
y_sales = np.array([2150, 1050, 1560, 1480, 1530, 1490])
x_citys = np.array(['北京', '上海', '广州', '深圳', '浙江', '山东'])
y_sale_count = np.array([83775, 62860, 59176, 64205, 48671, 39968])
# 创建画布和布局
fig = plt.figure(constrained_layout=True)
gs = fig.add_gridspec(2, 2)
ax_one = fig.add_subplot(gs[0, :])
ax_two = fig.add_subplot(gs[1, 0])
ax_thr = fig.add_subplot(gs[1, 1])
# 第 1 个子图
ax_one.bar(x_month, y_sales, width=0.5, color='#3299CC')
ax_one.set_tltle('2018 年上半年某品牌汽车的销售额')
ax_one.set_ylabel('销售额(亿元)')
# 第 2 个子图
ax_two.plot(x_citys, y_sale_count, 'm--o', ms=8)
ax_two.set_title('分公司某品牌汽车的销量')
ax_two.set_ylabel('销量(辆)')
# 第 3 个子图
ax__thr.stackplot(x_citys, y_sale_count, color='#9999FF')
ax__thr.set_title('分公司某品牌汽车的销量')
ax_thr.set_ylabel('销量(辆)')
plt.show()