5.1 基础子图创建
5.1.1 subplot()函数
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec
import matplotlib.patches as patches
class SubplotBasicsDemo:
"""子图基础演示类"""
def __init__(self):
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
# 生成示例数据
np.random.seed(42)
self.x = np.linspace(0, 10, 100)
self.y1 = np.sin(self.x)
self.y2 = np.cos(self.x)
self.y3 = np.sin(self.x) * np.cos(self.x)
self.y4 = np.exp(-self.x/5)
def basic_subplot_demo(self):
"""基本子图演示"""
# 1. 2x2子图布局
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 第一个子图
axes[0, 0].plot(self.x, self.y1, 'b-', linewidth=2)
axes[0, 0].set_title('sin(x)')
axes[0, 0].set_xlabel('x')
axes[0, 0].set_ylabel('y')
axes[0, 0].grid(True, alpha=0.3)
# 第二个子图
axes[0, 1].plot(self.x, self.y2, 'r-', linewidth=2)
axes[0, 1].set_title('cos(x)')
axes[0, 1].set_xlabel('x')
axes[0, 1].set_ylabel('y')
axes[0, 1].grid(True, alpha=0.3)
# 第三个子图
axes[1, 0].plot(self.x, self.y3, 'g-', linewidth=2)
axes[1, 0].set_title('sin(x) * cos(x)')
axes[1, 0].set_xlabel('x')
axes[1, 0].set_ylabel('y')
axes[1, 0].grid(True, alpha=0.3)
# 第四个子图
axes[1, 1].plot(self.x, self.y4, 'm-', linewidth=2)
axes[1, 1].set_title('exp(-x/5)')
axes[1, 1].set_xlabel('x')
axes[1, 1].set_ylabel('y')
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def subplot_indexing_demo(self):
"""子图索引演示"""
fig = plt.figure(figsize=(15, 10))
# 方法1: plt.subplot()
plt.subplot(2, 3, 1)
plt.plot(self.x, self.y1, 'b-')
plt.title('subplot(2,3,1)')
plt.grid(True, alpha=0.3)
plt.subplot(2, 3, 2)
plt.plot(self.x, self.y2, 'r-')
plt.title('subplot(2,3,2)')
plt.grid(True, alpha=0.3)
plt.subplot(2, 3, 3)
plt.plot(self.x, self.y3, 'g-')
plt.title('subplot(2,3,3)')
plt.grid(True, alpha=0.3)
# 方法2: add_subplot()
ax4 = fig.add_subplot(2, 3, 4)
ax4.plot(self.x, self.y4, 'm-')
ax4.set_title('add_subplot(2,3,4)')
ax4.grid(True, alpha=0.3)
ax5 = fig.add_subplot(2, 3, 5)
ax5.scatter(self.x[::10], self.y1[::10], c='blue', s=50)
ax5.set_title('add_subplot(2,3,5)')
ax5.grid(True, alpha=0.3)
ax6 = fig.add_subplot(2, 3, 6)
ax6.bar(range(5), [1, 3, 2, 4, 2], color=['red', 'green', 'blue', 'orange', 'purple'])
ax6.set_title('add_subplot(2,3,6)')
ax6.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def shared_axes_demo(self):
"""共享坐标轴演示"""
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 生成不同的数据
x1 = np.linspace(0, 10, 100)
x2 = np.linspace(0, 20, 100)
y1 = np.sin(x1)
y2 = np.cos(x2)
y3 = x1**2 / 100
y4 = np.exp(-x2/10)
# 不共享坐标轴
axes[0, 0].plot(x1, y1, 'b-')
axes[0, 0].set_title('独立坐标轴')
axes[0, 0].grid(True, alpha=0.3)
axes[0, 1].plot(x2, y2, 'r-')
axes[0, 1].set_title('独立坐标轴')
axes[0, 1].grid(True, alpha=0.3)
# 共享X轴的子图
fig2, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
ax1.plot(x1, y1, 'b-', label='sin(x)')
ax1.set_title('共享X轴 - 上图')
ax1.set_ylabel('y1')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.plot(x1, y3, 'g-', label='x²/100')
ax2.set_title('共享X轴 - 下图')
ax2.set_xlabel('x')
ax2.set_ylabel('y2')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 共享Y轴的子图
fig3, (ax3, ax4) = plt.subplots(1, 2, figsize=(12, 5), sharey=True)
ax3.plot(x1, y1, 'b-', label='sin(x)')
ax3.set_title('共享Y轴 - 左图')
ax3.set_xlabel('x1')
ax3.set_ylabel('y')
ax3.legend()
ax3.grid(True, alpha=0.3)
ax4.plot(x1, y2[:100], 'r-', label='cos(x)')
ax4.set_title('共享Y轴 - 右图')
ax4.set_xlabel('x2')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def subplot_spacing_demo(self):
"""子图间距演示"""
# 1. 默认间距
fig1, axes1 = plt.subplots(2, 2, figsize=(10, 8))
for i, ax in enumerate(axes1.flat):
ax.plot(self.x, [self.y1, self.y2, self.y3, self.y4][i])
ax.set_title(f'子图 {i+1} - 默认间距')
ax.grid(True, alpha=0.3)
plt.show()
# 2. 调整间距
fig2, axes2 = plt.subplots(2, 2, figsize=(10, 8))
for i, ax in enumerate(axes2.flat):
ax.plot(self.x, [self.y1, self.y2, self.y3, self.y4][i])
ax.set_title(f'子图 {i+1} - 调整间距')
ax.grid(True, alpha=0.3)
# 调整子图间距
plt.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9,
wspace=0.4, hspace=0.4)
plt.show()
# 3. tight_layout()
fig3, axes3 = plt.subplots(2, 2, figsize=(10, 8))
for i, ax in enumerate(axes3.flat):
ax.plot(self.x, [self.y1, self.y2, self.y3, self.y4][i])
ax.set_title(f'子图 {i+1} - tight_layout')
ax.set_xlabel('x轴标签')
ax.set_ylabel('y轴标签')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 使用示例
subplot_demo = SubplotBasicsDemo()
subplot_demo.basic_subplot_demo()
subplot_demo.subplot_indexing_demo()
subplot_demo.shared_axes_demo()
subplot_demo.subplot_spacing_demo()
5.2 GridSpec高级布局
5.2.1 不规则子图布局
class GridSpecDemo:
"""GridSpec演示类"""
def __init__(self):
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
# 生成示例数据
np.random.seed(42)
self.x = np.linspace(0, 10, 100)
self.y1 = np.sin(self.x) + np.random.normal(0, 0.1, 100)
self.y2 = np.cos(self.x) + np.random.normal(0, 0.1, 100)
# 分类数据
self.categories = ['A', 'B', 'C', 'D', 'E']
self.values = [23, 45, 56, 78, 32]
# 2D数据
self.data_2d = np.random.randn(20, 20)
def basic_gridspec_demo(self):
"""基本GridSpec演示"""
# 创建GridSpec
fig = plt.figure(figsize=(15, 10))
gs = GridSpec(3, 3, figure=fig)
# 占据不同大小的子图
ax1 = fig.add_subplot(gs[0, :]) # 第一行,所有列
ax1.plot(self.x, self.y1, 'b-', linewidth=2)
ax1.set_title('跨越整个第一行的子图')
ax1.grid(True, alpha=0.3)
ax2 = fig.add_subplot(gs[1, 0]) # 第二行,第一列
ax2.plot(self.x, self.y2, 'r-', linewidth=2)
ax2.set_title('第二行第一列')
ax2.grid(True, alpha=0.3)
ax3 = fig.add_subplot(gs[1, 1:]) # 第二行,第二列到最后
ax3.bar(self.categories, self.values, color='green', alpha=0.7)
ax3.set_title('第二行第二列到最后')
ax3.grid(True, alpha=0.3)
ax4 = fig.add_subplot(gs[2, :2]) # 第三行,前两列
ax4.scatter(self.x[::10], self.y1[::10], c='purple', s=50)
ax4.set_title('第三行前两列')
ax4.grid(True, alpha=0.3)
ax5 = fig.add_subplot(gs[2, 2]) # 第三行,第三列
ax5.pie(self.values, labels=self.categories, autopct='%1.1f%%')
ax5.set_title('第三行第三列')
plt.tight_layout()
plt.show()
def complex_gridspec_demo(self):
"""复杂GridSpec布局演示"""
fig = plt.figure(figsize=(16, 12))
# 创建复杂的网格布局
gs = GridSpec(4, 4, figure=fig,
height_ratios=[1, 2, 1, 1], # 行高比例
width_ratios=[1, 2, 1, 1]) # 列宽比例
# 主图 - 占据中心大部分区域
ax_main = fig.add_subplot(gs[1:3, 1:3])
im = ax_main.imshow(self.data_2d, cmap='viridis', aspect='auto')
ax_main.set_title('主图 - 热力图', fontsize=14, fontweight='bold')
plt.colorbar(im, ax=ax_main)
# 顶部图 - X轴边际分布
ax_top = fig.add_subplot(gs[0, 1:3], sharex=ax_main)
ax_top.plot(range(20), np.mean(self.data_2d, axis=0), 'b-', linewidth=2)
ax_top.set_title('X轴边际分布')
ax_top.grid(True, alpha=0.3)
plt.setp(ax_top.get_xticklabels(), visible=False)
# 右侧图 - Y轴边际分布
ax_right = fig.add_subplot(gs[1:3, 3], sharey=ax_main)
ax_right.plot(np.mean(self.data_2d, axis=1), range(20), 'r-', linewidth=2)
ax_right.set_title('Y轴边际分布', rotation=270, labelpad=20)
ax_right.grid(True, alpha=0.3)
plt.setp(ax_right.get_yticklabels(), visible=False)
# 左上角 - 统计信息
ax_stats = fig.add_subplot(gs[0, 0])
stats_text = f"""统计信息:
均值: {np.mean(self.data_2d):.2f}
标准差: {np.std(self.data_2d):.2f}
最大值: {np.max(self.data_2d):.2f}
最小值: {np.min(self.data_2d):.2f}"""
ax_stats.text(0.1, 0.5, stats_text, transform=ax_stats.transAxes,
fontsize=10, verticalalignment='center')
ax_stats.set_title('统计信息')
ax_stats.axis('off')
# 右上角 - 控制面板
ax_control = fig.add_subplot(gs[0, 3])
ax_control.text(0.1, 0.7, '控制面板', transform=ax_control.transAxes,
fontsize=12, fontweight='bold')
ax_control.text(0.1, 0.5, '• 颜色映射: viridis', transform=ax_control.transAxes,
fontsize=10)
ax_control.text(0.1, 0.3, '• 数据大小: 20x20', transform=ax_control.transAxes,
fontsize=10)
ax_control.text(0.1, 0.1, '• 类型: 随机数据', transform=ax_control.transAxes,
fontsize=10)
ax_control.set_title('控制面板')
ax_control.axis('off')
# 底部左侧 - 时间序列
ax_bottom_left = fig.add_subplot(gs[3, :2])
time_series = np.cumsum(np.random.randn(100))
ax_bottom_left.plot(time_series, 'g-', linewidth=2)
ax_bottom_left.set_title('时间序列数据')
ax_bottom_left.set_xlabel('时间')
ax_bottom_left.set_ylabel('数值')
ax_bottom_left.grid(True, alpha=0.3)
# 底部右侧 - 柱状图
ax_bottom_right = fig.add_subplot(gs[3, 2:])
ax_bottom_right.bar(self.categories, self.values,
color=['red', 'green', 'blue', 'orange', 'purple'], alpha=0.7)
ax_bottom_right.set_title('分类数据')
ax_bottom_right.set_xlabel('类别')
ax_bottom_right.set_ylabel('数值')
ax_bottom_right.grid(True, alpha=0.3)
# 左下角 - 散点图
ax_left_bottom = fig.add_subplot(gs[1:3, 0])
scatter_x = np.random.randn(50)
scatter_y = np.random.randn(50)
colors = np.random.rand(50)
ax_left_bottom.scatter(scatter_x, scatter_y, c=colors, cmap='plasma', s=50, alpha=0.7)
ax_left_bottom.set_title('散点图', rotation=90, labelpad=20)
ax_left_bottom.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def nested_gridspec_demo(self):
"""嵌套GridSpec演示"""
fig = plt.figure(figsize=(16, 12))
# 主网格
main_gs = GridSpec(2, 2, figure=fig, hspace=0.3, wspace=0.3)
# 左上角 - 嵌套网格
nested_gs1 = main_gs[0, 0].subgridspec(2, 2, hspace=0.4, wspace=0.4)
for i in range(2):
for j in range(2):
ax = fig.add_subplot(nested_gs1[i, j])
data = np.random.randn(20) + i*2 + j
ax.hist(data, bins=10, alpha=0.7, color=f'C{i*2+j}')
ax.set_title(f'嵌套子图 {i*2+j+1}')
ax.grid(True, alpha=0.3)
# 右上角 - 单个大图
ax_top_right = fig.add_subplot(main_gs[0, 1])
x = np.linspace(0, 10, 100)
for i in range(5):
y = np.sin(x + i) * np.exp(-x/10)
ax_top_right.plot(x, y, label=f'曲线 {i+1}', linewidth=2)
ax_top_right.set_title('多条曲线图')
ax_top_right.legend()
ax_top_right.grid(True, alpha=0.3)
# 左下角 - 另一个嵌套网格
nested_gs2 = main_gs[1, 0].subgridspec(3, 1, hspace=0.5)
# 三个小的时间序列图
for i in range(3):
ax = fig.add_subplot(nested_gs2[i, 0])
time_data = np.cumsum(np.random.randn(50)) + i*5
ax.plot(time_data, color=f'C{i}', linewidth=2)
ax.set_title(f'时间序列 {i+1}')
ax.grid(True, alpha=0.3)
if i == 2:
ax.set_xlabel('时间')
# 右下角 - 复杂的嵌套布局
nested_gs3 = main_gs[1, 1].subgridspec(2, 3, hspace=0.4, wspace=0.4)
# 大的热力图
ax_heatmap = fig.add_subplot(nested_gs3[:, :2])
heatmap_data = np.random.randn(15, 15)
im = ax_heatmap.imshow(heatmap_data, cmap='coolwarm')
ax_heatmap.set_title('热力图')
plt.colorbar(im, ax=ax_heatmap)
# 右侧的两个小图
ax_pie = fig.add_subplot(nested_gs3[0, 2])
ax_pie.pie([30, 25, 20, 15, 10], labels=['A', 'B', 'C', 'D', 'E'],
autopct='%1.0f%%', textprops={'fontsize': 8})
ax_pie.set_title('饼图', fontsize=10)
ax_bar = fig.add_subplot(nested_gs3[1, 2])
ax_bar.bar(['X', 'Y', 'Z'], [10, 15, 8], color=['red', 'green', 'blue'], alpha=0.7)
ax_bar.set_title('柱状图', fontsize=10)
ax_bar.grid(True, alpha=0.3)
plt.show()
def responsive_layout_demo(self):
"""响应式布局演示"""
# 创建自适应布局
fig = plt.figure(figsize=(14, 10))
# 使用GridSpec创建响应式布局
gs = GridSpec(3, 4, figure=fig,
height_ratios=[1, 2, 1],
width_ratios=[1, 2, 2, 1],
hspace=0.3, wspace=0.3)
# 标题区域
ax_title = fig.add_subplot(gs[0, :])
ax_title.text(0.5, 0.5, '响应式仪表板布局',
transform=ax_title.transAxes, fontsize=20,
fontweight='bold', ha='center', va='center')
ax_title.axis('off')
# 左侧控制面板
ax_control = fig.add_subplot(gs[1, 0])
control_text = """控制面板
• 数据源: 实时
• 更新频率: 1秒
• 状态: 正常
• 连接数: 1,234
• 错误率: 0.1%"""
ax_control.text(0.1, 0.9, control_text, transform=ax_control.transAxes,
fontsize=10, verticalalignment='top')
ax_control.set_title('系统状态')
ax_control.axis('off')
# 中央主图区域
ax_main1 = fig.add_subplot(gs[1, 1])
# 实时数据模拟
time_points = np.arange(100)
signal1 = np.sin(time_points * 0.1) + np.random.normal(0, 0.1, 100)
ax_main1.plot(time_points, signal1, 'b-', linewidth=2, alpha=0.8)
ax_main1.fill_between(time_points, signal1, alpha=0.3)
ax_main1.set_title('实时信号监控')
ax_main1.set_xlabel('时间')
ax_main1.set_ylabel('信号强度')
ax_main1.grid(True, alpha=0.3)
ax_main2 = fig.add_subplot(gs[1, 2])
# 性能指标
metrics = ['CPU', 'Memory', 'Disk', 'Network']
values = [65, 78, 45, 82]
colors = ['red' if v > 80 else 'orange' if v > 60 else 'green' for v in values]
bars = ax_main2.barh(metrics, values, color=colors, alpha=0.7)
ax_main2.set_title('系统性能指标 (%)')
ax_main2.set_xlabel('使用率')
# 添加数值标签
for bar, value in zip(bars, values):
ax_main2.text(bar.get_width() + 1, bar.get_y() + bar.get_height()/2,
f'{value}%', va='center')
ax_main2.set_xlim(0, 100)
ax_main2.grid(True, alpha=0.3, axis='x')
# 右侧信息面板
ax_info = fig.add_subplot(gs[1, 3])
# 创建状态指示器
status_data = {
'服务器1': 'green',
'服务器2': 'green',
'服务器3': 'orange',
'服务器4': 'red',
'数据库': 'green'
}
y_pos = 0.9
for service, status in status_data.items():
# 状态圆点
circle = patches.Circle((0.1, y_pos), 0.03, color=status,
transform=ax_info.transAxes)
ax_info.add_patch(circle)
# 服务名称
ax_info.text(0.2, y_pos, service, transform=ax_info.transAxes,
fontsize=10, va='center')
y_pos -= 0.15
ax_info.set_title('服务状态')
ax_info.set_xlim(0, 1)
ax_info.set_ylim(0, 1)
ax_info.axis('off')
# 底部统计区域
ax_stats1 = fig.add_subplot(gs[2, :2])
# 用户活动统计
hours = np.arange(24)
activity = np.random.poisson(50, 24) + 20 * np.sin(hours * np.pi / 12)
ax_stats1.bar(hours, activity, alpha=0.7, color='skyblue')
ax_stats1.set_title('24小时用户活动统计')
ax_stats1.set_xlabel('小时')
ax_stats1.set_ylabel('活跃用户数')
ax_stats1.grid(True, alpha=0.3)
ax_stats2 = fig.add_subplot(gs[2, 2:])
# 地理分布
regions = ['北美', '欧洲', '亚洲', '南美', '非洲', '大洋洲']
users = [1200, 800, 2100, 300, 150, 80]
colors_geo = plt.cm.viridis(np.linspace(0, 1, len(regions)))
wedges, texts, autotexts = ax_stats2.pie(users, labels=regions,
autopct='%1.1f%%', colors=colors_geo,
startangle=90)
ax_stats2.set_title('用户地理分布')
plt.show()
# 使用示例
gridspec_demo = GridSpecDemo()
gridspec_demo.basic_gridspec_demo()
gridspec_demo.complex_gridspec_demo()
gridspec_demo.nested_gridspec_demo()
gridspec_demo.responsive_layout_demo()
5.3 图形对象管理
5.3.1 Figure和Axes对象
class FigureAxesDemo:
"""Figure和Axes对象管理演示类"""
def __init__(self):
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
def figure_management_demo(self):
"""Figure对象管理演示"""
# 1. 创建多个Figure
fig1 = plt.figure(figsize=(10, 6))
fig1.suptitle('Figure 1 - 主要数据分析', fontsize=16, fontweight='bold')
# 在第一个Figure中添加子图
ax1 = fig1.add_subplot(121)
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
ax1.plot(x, y1, 'b-', linewidth=2)
ax1.set_title('正弦函数')
ax1.grid(True, alpha=0.3)
ax2 = fig1.add_subplot(122)
y2 = np.cos(x)
ax2.plot(x, y2, 'r-', linewidth=2)
ax2.set_title('余弦函数')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
# 2. 创建第二个Figure
fig2 = plt.figure(figsize=(8, 8))
fig2.suptitle('Figure 2 - 统计图表', fontsize=16, fontweight='bold')
# 添加不同类型的图表
ax3 = fig2.add_subplot(221)
categories = ['A', 'B', 'C', 'D']
values = [23, 45, 56, 78]
ax3.bar(categories, values, color='green', alpha=0.7)
ax3.set_title('柱状图')
ax4 = fig2.add_subplot(222)
ax4.pie(values, labels=categories, autopct='%1.1f%%')
ax4.set_title('饼图')
ax5 = fig2.add_subplot(223)
data = np.random.randn(100)
ax5.hist(data, bins=20, alpha=0.7, color='orange')
ax5.set_title('直方图')
ax6 = fig2.add_subplot(224)
x_scatter = np.random.randn(50)
y_scatter = np.random.randn(50)
ax6.scatter(x_scatter, y_scatter, alpha=0.7, c='purple')
ax6.set_title('散点图')
plt.tight_layout()
# 显示所有Figure
plt.show()
def axes_properties_demo(self):
"""Axes属性演示"""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 生成示例数据
x = np.linspace(0, 10, 100)
y = np.sin(x)
# 1. 基本Axes属性
ax1 = axes[0, 0]
ax1.plot(x, y, 'b-', linewidth=2)
ax1.set_title('基本Axes属性设置', fontsize=14, fontweight='bold')
ax1.set_xlabel('X轴标签', fontsize=12)
ax1.set_ylabel('Y轴标签', fontsize=12)
ax1.set_xlim(0, 10)
ax1.set_ylim(-1.5, 1.5)
ax1.grid(True, alpha=0.3)
# 添加文本注释
ax1.text(5, 0.5, '这是一个注释', fontsize=10,
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5))
# 2. 自定义刻度
ax2 = axes[0, 1]
ax2.plot(x, y, 'r-', linewidth=2)
ax2.set_title('自定义刻度', fontsize=14, fontweight='bold')
# 自定义X轴刻度
ax2.set_xticks([0, 2, 4, 6, 8, 10])
ax2.set_xticklabels(['零', '二', '四', '六', '八', '十'])
# 自定义Y轴刻度
ax2.set_yticks([-1, -0.5, 0, 0.5, 1])
ax2.set_yticklabels(['-1.0', '-0.5', '0.0', '+0.5', '+1.0'])
ax2.grid(True, alpha=0.3)
# 3. 双Y轴
ax3 = axes[1, 0]
# 左Y轴
line1 = ax3.plot(x, y, 'b-', linewidth=2, label='sin(x)')
ax3.set_xlabel('X轴')
ax3.set_ylabel('sin(x)', color='blue')
ax3.tick_params(axis='y', labelcolor='blue')
# 右Y轴
ax3_twin = ax3.twinx()
y2 = np.exp(x/10)
line2 = ax3_twin.plot(x, y2, 'r-', linewidth=2, label='exp(x/10)')
ax3_twin.set_ylabel('exp(x/10)', color='red')
ax3_twin.tick_params(axis='y', labelcolor='red')
ax3.set_title('双Y轴图表', fontsize=14, fontweight='bold')
# 合并图例
lines = line1 + line2
labels = [l.get_label() for l in lines]
ax3.legend(lines, labels, loc='upper left')
ax3.grid(True, alpha=0.3)
# 4. 自定义坐标轴样式
ax4 = axes[1, 1]
ax4.plot(x, y, 'g-', linewidth=3)
ax4.set_title('自定义坐标轴样式', fontsize=14, fontweight='bold')
# 设置坐标轴样式
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)
ax4.spines['left'].set_linewidth(2)
ax4.spines['bottom'].set_linewidth(2)
ax4.spines['left'].set_color('darkblue')
ax4.spines['bottom'].set_color('darkblue')
# 设置刻度样式
ax4.tick_params(axis='both', which='major', labelsize=10,
colors='darkblue', width=2, length=6)
# 添加箭头
ax4.annotate('', xy=(10, 0), xytext=(9.5, 0),
arrowprops=dict(arrowstyle='->', color='darkblue', lw=2))
ax4.annotate('', xy=(0, 1.2), xytext=(0, 1.1),
arrowprops=dict(arrowstyle='->', color='darkblue', lw=2))
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def axes_positioning_demo(self):
"""Axes定位演示"""
fig = plt.figure(figsize=(14, 10))
# 1. 使用add_axes精确定位
# [left, bottom, width, height] 相对于figure的比例
ax1 = fig.add_axes([0.1, 0.7, 0.35, 0.25])
x = np.linspace(0, 10, 100)
y = np.sin(x)
ax1.plot(x, y, 'b-', linewidth=2)
ax1.set_title('精确定位的Axes')
ax1.grid(True, alpha=0.3)
# 2. 创建插图(inset)
ax2 = fig.add_axes([0.55, 0.7, 0.35, 0.25])
ax2.plot(x, np.cos(x), 'r-', linewidth=2)
ax2.set_title('主图')
ax2.grid(True, alpha=0.3)
# 在主图中添加插图
ax_inset = fig.add_axes([0.65, 0.8, 0.15, 0.1])
ax_inset.plot(x[:20], np.cos(x[:20]), 'r-', linewidth=1)
ax_inset.set_title('插图', fontsize=8)
ax_inset.tick_params(labelsize=6)
# 3. 重叠的Axes
ax3 = fig.add_axes([0.1, 0.4, 0.4, 0.25])
ax3.bar(['A', 'B', 'C', 'D'], [1, 3, 2, 4], alpha=0.7, color='green')
ax3.set_title('背景图表')
# 重叠的小图
ax3_overlay = fig.add_axes([0.25, 0.5, 0.15, 0.1])
ax3_overlay.pie([30, 70], labels=['是', '否'], autopct='%1.0f%%')
ax3_overlay.set_title('重叠饼图', fontsize=8)
# 4. 不规则形状的Axes
from matplotlib.patches import Circle
ax4 = fig.add_axes([0.6, 0.4, 0.3, 0.25])
# 创建圆形裁剪区域
circle = Circle((0.5, 0.5), 0.4, transform=ax4.transAxes,
facecolor='none', edgecolor='black', linewidth=2)
ax4.add_patch(circle)
# 绘制数据
theta = np.linspace(0, 2*np.pi, 100)
r = 1 + 0.3*np.sin(5*theta)
ax4.plot(theta, r, 'purple', linewidth=2)
ax4.set_title('极坐标风格图')
# 设置裁剪
for line in ax4.lines:
line.set_clip_path(circle)
# 5. 动态调整的Axes
ax5 = fig.add_axes([0.1, 0.05, 0.8, 0.25])
# 创建多个数据系列
for i in range(5):
y_data = np.random.randn(50).cumsum() + i*2
ax5.plot(y_data, label=f'系列 {i+1}', linewidth=2, alpha=0.8)
ax5.set_title('多系列时间序列', fontsize=14, fontweight='bold')
ax5.set_xlabel('时间点')
ax5.set_ylabel('数值')
ax5.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax5.grid(True, alpha=0.3)
# 添加整体标题
fig.suptitle('Axes定位与布局演示', fontsize=16, fontweight='bold', y=0.98)
plt.show()
def interactive_axes_demo(self):
"""交互式Axes演示"""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 生成示例数据
x = np.linspace(0, 10, 100)
# 1. 可缩放的图表
ax1 = axes[0, 0]
y1 = np.sin(x) * np.exp(-x/10)
ax1.plot(x, y1, 'b-', linewidth=2)
ax1.set_title('可缩放图表\n(鼠标滚轮缩放)')
ax1.grid(True, alpha=0.3)
# 2. 带有注释的图表
ax2 = axes[0, 1]
y2 = np.cos(x)
line, = ax2.plot(x, y2, 'r-', linewidth=2)
ax2.set_title('带注释的图表')
ax2.grid(True, alpha=0.3)
# 添加交互式注释
max_idx = np.argmax(y2)
ax2.annotate(f'最大值: ({x[max_idx]:.2f}, {y2[max_idx]:.2f})',
xy=(x[max_idx], y2[max_idx]), xytext=(x[max_idx]+1, y2[max_idx]+0.3),
arrowprops=dict(arrowstyle='->', color='red'),
bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.7))
# 3. 多轴图表
ax3 = axes[1, 0]
y3 = np.sin(x)
y4 = x**2 / 50
line1 = ax3.plot(x, y3, 'g-', linewidth=2, label='sin(x)')
ax3.set_ylabel('sin(x)', color='green')
ax3.tick_params(axis='y', labelcolor='green')
ax3_twin = ax3.twinx()
line2 = ax3_twin.plot(x, y4, 'orange', linewidth=2, label='x²/50')
ax3_twin.set_ylabel('x²/50', color='orange')
ax3_twin.tick_params(axis='y', labelcolor='orange')
ax3.set_title('双轴图表')
ax3.set_xlabel('x')
# 合并图例
lines1, labels1 = ax3.get_legend_handles_labels()
lines2, labels2 = ax3_twin.get_legend_handles_labels()
ax3.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
ax3.grid(True, alpha=0.3)
# 4. 动态更新的图表
ax4 = axes[1, 1]
# 模拟实时数据
time_points = np.arange(50)
data_series = []
colors = ['blue', 'red', 'green', 'orange', 'purple']
for i in range(5):
data = np.random.randn(50).cumsum() + i*3
line = ax4.plot(time_points, data, color=colors[i],
linewidth=2, label=f'传感器 {i+1}', alpha=0.8)[0]
data_series.append((line, data))
ax4.set_title('多传感器数据监控')
ax4.set_xlabel('时间')
ax4.set_ylabel('读数')
ax4.legend()
ax4.grid(True, alpha=0.3)
# 添加当前值显示
for i, (line, data) in enumerate(data_series):
current_value = data[-1]
ax4.text(0.02, 0.98 - i*0.05, f'传感器{i+1}: {current_value:.2f}',
transform=ax4.transAxes, fontsize=9,
bbox=dict(boxstyle='round,pad=0.2', facecolor=colors[i], alpha=0.3))
plt.tight_layout()
plt.show()
# 使用示例
figure_demo = FigureAxesDemo()
figure_demo.figure_management_demo()
figure_demo.axes_properties_demo()
figure_demo.axes_positioning_demo()
figure_demo.interactive_axes_demo()
5.4 本章总结
5.4.1 学习要点回顾
基础子图创建
plt.subplots()
: 创建规则网格子图plt.subplot()
: 单个子图创建fig.add_subplot()
: 面向对象方式创建子图- 共享坐标轴:
sharex
,sharey
参数
GridSpec高级布局
- 不规则子图布局
- 行列比例控制:
height_ratios
,width_ratios
- 嵌套网格:
subgridspec()
- 间距控制:
hspace
,wspace
图形对象管理
- Figure对象: 整个图形窗口
- Axes对象: 单个绘图区域
- 精确定位:
fig.add_axes()
- 双轴图表:
ax.twinx()
,ax.twiny()
布局优化
plt.tight_layout()
: 自动调整布局plt.subplots_adjust()
: 手动调整间距- 响应式设计原则
- 交互式图表设计
5.4.2 实践练习
# 练习1: 创建仪表板布局
def practice_dashboard_layout():
"""创建一个完整的数据仪表板"""
# TODO: 使用GridSpec创建复杂布局
# 包含: 标题区、控制面板、主图区、统计区
pass
# 练习2: 响应式图表设计
def practice_responsive_design():
"""设计响应式图表布局"""
# TODO: 创建适应不同屏幕尺寸的布局
# 考虑移动端和桌面端的显示效果
pass
# 练习3: 交互式多图表联动
def practice_interactive_charts():
"""创建交互式多图表联动"""
# TODO: 实现图表间的数据联动
# 点击一个图表影响其他图表的显示
pass
5.4.3 常见问题
Q: 如何解决子图重叠问题?
A: 使用plt.tight_layout()
或手动调整plt.subplots_adjust()
参数。
Q: GridSpec和subplots有什么区别? A: subplots创建规则网格,GridSpec可以创建不规则布局和跨越多个网格的子图。
Q: 如何在子图中添加插图?
A: 使用fig.add_axes()
或mpl_toolkits.axes_grid1.inset_locator
模块。
Q: 双轴图表如何统一图例? A: 获取两个轴的图例句柄和标签,然后在一个轴上统一显示。
5.4.4 下章预告
下一章我们将学习数据处理与可视化,包括数据清洗、统计分析、时间序列处理等,让你能够处理真实的数据并创建有意义的可视化。