基于Matplotlib与PySimpleGUI实现带共享轴的动态子图
动态子图实现方案(Python3 + Matplotlib 3.6.2 + PySimpleGUI)
需求说明
- 背景:通过PySimpleGUI的信号复选框控制Matplotlib子图的动态增减,勾选时添加对应子图并均分画布空间,取消勾选则移除子图,剩余子图自动填充整个画布。
- 具体要求:
- 子图最多支持10行1列布局;
- 仅显示1张子图时,该子图占据整个画布;
- 新增子图后,所有子图均分画布空间,且共享底部X轴;
- 可随时删除指定子图,删除后剩余子图自动调整布局占满画布。
现有代码问题
原代码基本实现了子图添加,但存在轴未共享、子图尺寸不一致的问题,原代码如下:
import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec number_of_plots = 2 fig = plt.figure() ax = fig.add_subplot(1, 1, 1) gs = gridspec.GridSpec(number_of_plots + 1, 1) plot_space = gs[0:number_of_plots].get_position(fig) print(plot_space, [plot_space.x0, plot_space.y0, plot_space.width, plot_space.height]) ax.set_position(plot_space) ax.set_subplotspec(gs[0:number_of_plots]) # only necessary if using tight_layout() fig.tight_layout() # not strictly part of the question ax = fig.add_subplot(gs[2], sharex=ax) plt.show()
修正后的完整实现
以下代码整合PySimpleGUI复选框交互,解决了轴共享和子图尺寸问题,满足所有需求:
import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import PySimpleGUI as sg import matplotlib.gridspec as gridspec # 初始化配置 MAX_PLOTS = 10 # 模拟10个信号的名称 signal_names = [f"信号{i+1}" for i in range(MAX_PLOTS)] # 记录当前选中的信号索引 selected_signals = [] # 存储子图对象 axes = [] # 设置PySimpleGUI布局 sg.theme('DefaultNoMoreNagging') # 左侧复选框列 checkbox_col = [[sg.Checkbox(name, key=f'-CHECKBOX-{i}', enable_events=True)] for i, name in enumerate(signal_names)] # 右侧Matplotlib画布区域 layout = [ [sg.Column(checkbox_col), sg.Canvas(key='-CANVAS-')] ] window = sg.Window('动态子图演示', layout, finalize=True, element_justification='center') # 初始化Matplotlib图 fig = plt.figure(figsize=(8, 6)) canvas = FigureCanvasTkAgg(fig, window['-CANVAS-'].TKCanvas) canvas.draw() canvas.get_tk_widget().pack(side='top', fill='both', expand=1) def update_subplots(): # 清除所有现有子图 for ax in axes: ax.remove() axes.clear() num_plots = len(selected_signals) if num_plots == 0: # 无选中信号时清空画布 fig.clear() canvas.draw() return # 创建GridSpec,所有子图均分空间 gs = gridspec.GridSpec(num_plots, 1, hspace=0.1) # 共享X轴的基准子图 base_ax = None for i in range(num_plots): if i == 0: # 第一个子图作为基准,后续子图共享其X轴 ax = fig.add_subplot(gs[i]) base_ax = ax else: ax = fig.add_subplot(gs[i], sharex=base_ax) # 隐藏中间子图的X轴刻度,只保留底部子图的 plt.setp(ax.get_xticklabels(), visible=False) # 模拟绘制数据(可替换为真实信号数据) ax.plot([1,2,3,4,5], [i*2 + j for j in [1,3,2,4,3]]) ax.set_title(signal_names[selected_signals[i]]) axes.append(ax) # 调整布局,避免标签重叠 fig.tight_layout() canvas.draw() # 事件循环 while True: event, values = window.read() if event == sg.WIN_CLOSED: break # 处理复选框事件 if '-CHECKBOX-' in event: idx = int(event.split('-')[-1]) if values[event]: if len(selected_signals) < MAX_PLOTS: selected_signals.append(idx) else: if idx in selected_signals: selected_signals.remove(idx) # 更新子图布局 update_subplots() window.close() plt.close(fig)
关键实现说明
- 动态布局管理:使用
gridspec.GridSpec根据当前选中的子图数量创建对应行数的布局,保证所有子图均分画布空间; - X轴共享:以第一个子图为基准,后续子图通过
sharex=base_ax实现X轴共享,并隐藏中间子图的X轴刻度,仅保留底部子图的刻度; - 交互逻辑:通过PySimpleGUI的复选框事件监听,实时更新选中信号列表,触发子图重绘;
- 子图清理:每次更新前移除所有现有子图,避免布局混乱,确保新布局完全适配当前选中状态。
内容的提问来源于stack exchange,提问作者Sreenath R
相关产品推荐
相关产品推荐

