嵌入tkinter的Matplotlib通过RectangleSelector触发回调时FuncAnimation重复创建
问题根因
- Figure创建方式错误:使用
plt.subplots()生成的图表会同时被matplotlib的pyplot全局管理器托管,嵌入Tkinter后形成两条独立的事件响应链路,RectangleSelector的触发事件会被两条链路各处理一次,导致回调执行两次;而Tk原生按钮的事件仅走Tk事件循环,不会触发pyplot全局链路,因此运行正常。 - 动画曲线创建逻辑不当:使用
plt.plot()创建动画曲线时会隐式触发全局图表的重绘逻辑,进一步加剧了重复执行的问题。
修复方案
- 替换
plt.subplots()为matplotlib原生无全局托管的Figure创建方式,切断pyplot全局事件链路 - 将动画曲线的创建从
plt.plot()改为绑定到指定axes的self.ax.plot(),避免触发全局重绘
修复后完整代码
import os import threading import tkinter as tk from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import ( FigureCanvasTkAgg) from matplotlib.widgets import RectangleSelector import numpy as np import matplotlib from matplotlib import animation class LineAnimation: def __init__(self, fig, ax): print(' enter LineAnimation ctor') # Parameters self.ax = ax self.fig = fig self.xdata, self.ydata = [], [] # 替换plt.plot为绑定当前axes的plot self.ln, = self.ax.plot([], [], 'ro') # Print figures list figures = [manager.canvas.figure for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()] print('figures BEFORE animation: ', figures) self.animation = animation.FuncAnimation(fig=self.fig, func=self.update, init_func=self.init, frames=np.linspace(0, 2 * np.pi, 128), interval=25, blit=True, repeat=False, cache_frame_data=False) self.fig.canvas.draw() # Print figures list figures = [manager.canvas.figure for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()] print('figures AFTER animation: ', figures, '\n') def init(self): # Prints for debugging print('\nenter init animate') print('Thread id: ', threading.get_ident()) print('Process id: ', os.getpid(), '\n') # Init self.ax.set_xlim(0, 2*np.pi) self.ax.set_ylim(-1, 1) return self.ln, def update(self, frame): self.xdata.append(frame) self.ydata.append(np.sin(frame)) self.ln.set_data(self.xdata, self.ydata) return self.ln, class Example: def __init__(self): # init window self.root = tk.Tk(className=' Species segmentation') # 替换plt.subplots为原生Figure创建方式 self.fig = Figure() self.ax = self.fig.add_subplot() # init sine audio file self.fs = 44100 self.dur = 2 self.freq = 440 self.x = np.sin(2*np.pi*np.arange(self.fs*self.dur)*self.freq/self.fs) # Embedd in tk self.canvas = FigureCanvasTkAgg(self.fig, master=self.root) # A tk.DrawingArea. self.canvas.draw() self.canvas.get_tk_widget().grid() # Plot something self.N = 100000 self.xp = np.linspace(0, 10, self.N) self.ax.plot(self.xp, np.sin(2*np.pi*self.xp)) self.ax.set_title( "Plot for demonstration purpuse") # init Rectangle Selector self.RS = RectangleSelector(self.ax, self.line_select_callback, drawtype='box', useblit=True, button=[1, 3], # avoid using middle button minspanx=5, minspany=5, spancoords='pixels', interactive=True, rectprops={'facecolor': 'yellow', 'edgecolor': 'black', 'alpha': 0.15, 'fill': True}) self.canvas.draw() tk.mainloop() def line_select_callback(self, eclick, erelease): print('enter line_select_callback') self.anim = LineAnimation( self.fig, self.ax) self.fig.canvas.draw() Example()
内容的提问来源于stack exchange,提问作者Ronen
相关产品推荐
相关产品推荐

