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 学习要点回顾

  1. 基础子图创建

    • plt.subplots(): 创建规则网格子图
    • plt.subplot(): 单个子图创建
    • fig.add_subplot(): 面向对象方式创建子图
    • 共享坐标轴: sharex, sharey参数
  2. GridSpec高级布局

    • 不规则子图布局
    • 行列比例控制: height_ratios, width_ratios
    • 嵌套网格: subgridspec()
    • 间距控制: hspace, wspace
  3. 图形对象管理

    • Figure对象: 整个图形窗口
    • Axes对象: 单个绘图区域
    • 精确定位: fig.add_axes()
    • 双轴图表: ax.twinx(), ax.twiny()
  4. 布局优化

    • 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 下章预告

下一章我们将学习数据处理与可视化,包括数据清洗、统计分析、时间序列处理等,让你能够处理真实的数据并创建有意义的可视化。