如何在手动关闭Matplotlib绘制的图形时退出事件循环?
问题
在项目中需要用Matplotlib绘制图形,让用户通过交互定义信号起止的cutoff值并保留在内存中。当前脚本可正常选择cutoff值,按下按键能退出循环,但手动关闭图形时,虽触发了close_event函数,脚本仍陷入死循环,需通过Ctrl+C终止。环境为Python 3.9.16、Matplotlib 3.7.0,Windows 10系统。原代码如下:
import matplotlib.pyplot as plt import numpy as np close_flag = 0 # Creating a random dataset x = np.arange(0, np.pi * 2 * 5, 0.1) y = np.sin(x) # Shamelessly stolen from Matplotlib documentation def tellme(s): plt.title(s, fontsize=16) plt.draw() def close_event(event): global close_flag close_flag = 1 # Creating the figure fig, axs = plt.subplots() fig.canvas.mpl_connect('close_event', close_event) plt.plot(x, y) plt.grid() while close_flag == 0: pts = [] # Empty list to keep my cutoff values in memory _vlines = [] # Updating the plot title tellme('Select the first cutoff value with the mouse') # First cutoff value pt = plt.ginput(1, timeout=-1)[0][0] # We only need the value along the x axis pts.append(pt) # Ploting the first cutoff value as a vertical line tmp = plt.vlines(pt, ymin=y.min(), ymax=y.max(), colors='k', linestyles='-.') _vlines.append(tmp) # Updating the plot title a second time tellme('Select the second cutoff dates with the mouse') # Second cutoff value pt = plt.ginput(1, timeout=-1)[0][0] pts.append(pt) # Ploting the second cutoff value as a vertical line tmp = plt.vlines(pt, ymin=y.min(), ymax=y.max(), colors='k', linestyles='-.') _vlines.append(tmp) # Updating the title a third time to inform the user both cutoff values are set tellme('Happy? Key click for yes, mouse click for no') # First control to let the user escape the loop if plt.waitforbuttonpress(): break # First control returned False, we remove the vertical lines and let the user select new values for v in _vlines: v.remove() # Second control used to check if the Figure was closed, if so we should exit the loop if close_flag == 1: break # Rest of the script where I am using the selected values
解决方案
问题核心在于plt.ginput(timeout=-1)会无限阻塞等待鼠标输入,即使窗口关闭也不会终止,导致循环无法检测到close_flag的变化。修改思路是给ginput设置短超时,让它定期返回,同时在每次输入前检查窗口状态,具体修改如下:
- 将
ginput和waitforbuttonpress的timeout设为100毫秒,避免无限阻塞; - 在获取输入后先判断是否有有效点击,若没有且窗口已关闭则退出循环;
- 将
pts移到循环外,确保最终能保留用户选择的有效值; - 在
close_event中主动关闭图形,确保窗口状态正确更新。
修改后的代码:
import matplotlib.pyplot as plt import numpy as np close_flag = 0 pts = [] # 移到循环外,保存最终选择的cutoff值 # 创建随机数据集 x = np.arange(0, np.pi * 2 * 5, 0.1) y = np.sin(x) def tellme(s): plt.title(s, fontsize=16) plt.draw() def close_event(event): global close_flag close_flag = 1 plt.close(event.canvas.figure) # 主动关闭图形,避免残留 # 创建图形 fig, axs = plt.subplots() fig.canvas.mpl_connect('close_event', close_event) plt.plot(x, y) plt.grid() while close_flag == 0: _vlines = [] tellme('Select the first cutoff value with the mouse') # 获取第一个cutoff值,带超时 inputs = plt.ginput(1, timeout=100) if not inputs: if close_flag: break continue pt = inputs[0][0] pts.append(pt) # 绘制第一条竖线 tmp = plt.vlines(pt, ymin=y.min(), ymax=y.max(), colors='k', linestyles='-.') _vlines.append(tmp) tellme('Select the second cutoff value with the mouse') # 获取第二个cutoff值,带超时 inputs = plt.ginput(1, timeout=100) if not inputs: if close_flag: break # 若未点击且窗口未关,移除已画的竖线并重新开始 pts.pop() tmp.remove() continue pt = inputs[0][0] pts.append(pt) # 绘制第二条竖线 tmp = plt.vlines(pt, ymin=y.min(), ymax=y.max(), colors='k', linestyles='-.') _vlines.append(tmp) tellme('Happy? Key click for yes, mouse click for no') # 等待按键/鼠标操作 if plt.waitforbuttonpress(timeout=100): break # 用户不满意,移除竖线并清空当前选择的两个值 for v in _vlines: v.remove() pts = pts[:-2] if close_flag == 1: break # 后续使用pts中的值 print("Selected cutoff values:", pts)
修改说明
- 短超时设置让程序定期检查
close_flag,不会一直阻塞在输入等待上; - 增加输入有效性判断,避免因超时或窗口关闭导致的索引错误;
- 调整
pts的作用域,确保最终能保留用户确认的cutoff值; - 主动关闭图形的操作让窗口状态及时同步,循环能快速响应关闭事件。
内容的提问来源于stack exchange,提问作者gsalles
相关产品推荐
相关产品推荐

