Skip to content

Tkagg event loop throws error on window close #19940

Closed
@charwick

Description

@charwick

I have some code that continuously updates an MPL plot, and used fig.canvas.start_event_loop(0.0001) to listen for keyboard input. As of MPL 3.4, this code is broken in two ways:

  1. Commit c53570d converts the timeout to int milliseconds. This rounds down 0.0001 to 0 and fails to display the window at all. I might suggest math.ceil(1000*timeout) instead of int(1000*timeout)?
  2. After changing 0.0001 to 0.001 in my code, Commit 630e806 causes MPL to throw an error invalid command name "140668474408832stop_event_loop" while executing "140668474408832stop_event_loop" ("after" script) when closing the window before pausing the loop. Expected behavior would be for it to simply terminate the loop. It's a harmless error in the minimal code below (you can still launch new runs), but my full code updates using an asyncio event loop, which this behavior fails to exit, so this error prevents launching new runs in that case (asyncio.run() cannot be called from a running event loop). Not sure how to fix this one, but I'll note that using fig.canvas.flush_events() in my code instead of start_event_loop produces the desired behavior.

Not sure if #2 is worth fixing given that flush_events() restores the desired behavior, but @richardsheridan suggested I post an issue in my comment on that commit. Minimal example below. Launch an MPL window and then close it before pausing to trigger the error:

from random import randint
import matplotlib, matplotlib.pyplot as plt
from tkinter import *

class Visual:
	running = False
	
	#Construct a launch button
	def __init__(self):
		self.parent = Tk()
		
		self.runButton = Button(self.parent, text='Run', command=self.launchVisual, padx=10, pady=10)
		self.runButton.pack(fill="x", side=TOP)
					
	def start(self):
		self.runButton['text'] = 'Pause'
		self.runButton['command'] = self.stop
		self.running = True
		
		while self.running:
			self.data.append(randint(0,100))
		
			self.line.set_ydata(self.data)
			self.line.set_xdata(range(len(self.data)))
			self.axes.relim()
			self.axes.autoscale_view(tight=False)
		
			if self.fig.stale: self.fig.canvas.draw_idle()
			
			#This rounds down to 0 and doesn't draw the window at all
			#self.fig.canvas.start_event_loop(0.0001)
			
			#This throws an error and breaks asyncio if you close the window before pausing
			self.fig.canvas.start_event_loop(0.001)
		
			#This works
			#self.fig.canvas.flush_events()
	
	def stop(self, *args):
		self.running = False
		self.runButton['text'] = 'Run'
		self.runButton['command'] = self.start
	
	def terminate(self, evt=False):
		self.running = False
		self.runButton['text'] = 'New Model'
		self.runButton['command'] = self.launchVisual
		
	def launchVisual(self):
		matplotlib.use('TkAgg')
		self.fig, self.axes = plt.subplots()
		self.fig.canvas.mpl_connect('close_event', self.terminate)
		
		#Keyboard input
		def pause(event):
			if event.key==' ' and event.canvas is self.fig.canvas:
				if self.running: self.stop()
				else: self.start()
		self.fig.canvas.mpl_connect('key_press_event', pause)
		
		self.data = []
		self.line, = self.axes.plot(self.data, color='#330099')
		
		self.fig.canvas.draw_idle()
		plt.show(block=False)
		
		self.start()

viz = Visual()
viz.parent.mainloop()

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions