Reputation: 141
I am writing a program in Python, using matplotlib to (among other things) run an animation showing a numerical solution to the time-dependent Schrodinger Equation.
Everything is working fine, but once an animation has finished running, I would like the window it was in to close itself. My way of doing this (shown below) works, but exceptions are thrown up which I cant seem to catch. It works fine for what I need it to do, but the error looks very messy.
I have an alternative method which works without throwing an error, but requires the user to manually close the window (unacceptable for my purposes). Can someone please point out what I am doing wrong, or suggest a better option?
A simplified version of the relevant parts of my code follows:
from matplotlib import animation as ani
from matplotlib import pyplot as plt
multiplier = 0
def get_data(): # some dummy data to animate
x = range(-10, 11)
global multiplier
y = [multiplier * i for i in x]
multiplier += 0.005
return x, y
class Schrodinger_Solver(object):
def __init__(self, xlim = (-10, 10), ylim = (-10, 10), num_frames = 200):
self.num_frames = num_frames
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111, xlim = xlim, ylim = ylim)
self.p_line, = self.ax.plot([], [])
self.ani = ani.FuncAnimation(self.fig, self.animate_frame,
init_func = self.init_func,
interval = 1, frames = self.num_frames,
repeat = False, blit = True)
plt.show()
def animate_frame(self, framenum):
data = get_data()
self.p_line.set_data(data[0], data[1])
if framenum == self.num_frames - 1:
plt.close()
# closes the window when the last frame is reached,
# but exception is thrown. Comment out to avoid the error,
# but then the window needs manual closing
return self.p_line,
def init_func(self):
self.p_line.set_data([], [])
return self.p_line,
Schrodinger_Solver()
I am running Python 2.7.2 on windows 7, with matplotlib 1.1.0
Thanks in advance
EDIT: exception and traceback as follows:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1410, in __call__
return self.func(*args)
File "C:\Python27\lib\lib-tk\Tkinter.py", line 495, in callit
func(*args)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 116, in _on_timer
TimerBase._on_timer(self)
File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 1092, in _on_timer
ret = func(*args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 315, in _step
still_going = Animation._step(self, *args)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 177, in _step
self._draw_next_frame(framedata, self._blit)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 197, in _draw_next_frame
self._post_draw(framedata, blit)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 220, in _post_draw
self._blit_draw(self._drawn_artists, self._blit_cache)
File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 240, in _blit_draw
ax.figure.canvas.blit(ax.bbox)
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 244, in blit
tkagg.blit(self._tkphoto, self.renderer._renderer, bbox=bbox, colormode=2)
File "C:\Python27\lib\site-packages\matplotlib\backends\tkagg.py", line 19, in blit
tk.call("PyAggImagePhoto", photoimage, id(aggimage), colormode, id(bbox_array))
TclError: this isn't a Tk application
Traceback (most recent call last):
File "C:\Python27\quicktest.py", line 44, in <module>
Schrodinger_Solver()
File "C:\Python27\quicktest.py", line 26, in __init__
plt.show()
File "C:\Python27\lib\site-packages\matplotlib\pyplot.py", line 139, in show
_show(*args, **kw)
File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 109, in __call__
self.mainloop()
File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 69, in mainloop
Tk.mainloop()
File "C:\Python27\lib\lib-tk\Tkinter.py", line 325, in mainloop
_default_root.tk.mainloop(n)
AttributeError: 'NoneType' object has no attribute 'tk'
I can catch the second exception, the AttributeError, by a small change:
try: plt.show()
except AttributeError: pass
but the first part, the TclError, remains no matter what I try
Upvotes: 14
Views: 6338
Reputation: 765
I am facing the exact same problem, and I managed to solve the issue by creating another Animation
class. Essentially, I made two changes:
_stop
and _step
methods.StopIteration
error in the update function, instead of using plt.close
. The exception will be caught and won't break the script.Here is the code.
from matplotlib import animation as ani
from matplotlib import pyplot as plt
class FuncAnimationDisposable(ani.FuncAnimation):
def __init__(self, fig, func, **kwargs):
super().__init__(fig, func, **kwargs)
def _step(self, *args):
still_going = ani.Animation._step(self, *args)
if not still_going and self.repeat:
super()._init_draw()
self.frame_seq = self.new_frame_seq()
self.event_source.interval = self._repeat_delay
return True
elif (not still_going) and (not self.repeat):
plt.close() # this code stopped the window
return False
else:
self.event_source.interval = self._interval
return still_going
def _stop(self, *args):
# On stop we disconnect all of our events.
if self._blit:
self._fig.canvas.mpl_disconnect(self._resize_id)
self._fig.canvas.mpl_disconnect(self._close_id)
multiplier = 0
def get_data(): # some dummy data to animate
x = range(-10, 11)
global multiplier
y = [multiplier * i for i in x]
multiplier += 0.005
return x, y
class Schrodinger_Solver(object):
def __init__(self, xlim = (-10, 10), ylim = (-10, 10), num_frames = 200):
self.num_frames = num_frames
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111, xlim = xlim, ylim = ylim)
self.p_line, = self.ax.plot([], [])
self.ani = FuncAnimationDisposable(self.fig, self.animate_frame,
init_func = self.init_func,
interval = 1, frames = self.num_frames,
repeat = False, blit = True)
plt.show()
def animate_frame(self, framenum):
data = get_data()
self.p_line.set_data(data[0], data[1])
if framenum == self.num_frames - 1:
raise StopIteration # instead of plt.close()
return self.p_line,
def init_func(self):
self.p_line.set_data([], [])
return self.p_line,
Schrodinger_Solver()
Schrodinger_Solver()
print(multiplier)
(The code snippet was tested with Python 3.7 and matplotlib 3.4.2.)
Upvotes: 1
Reputation: 5187
I had the same problem a few minutes before... The reason was a very low interval
-value in FuncAnimation
. Your code tries to update the graphics evere 1 millisecond - quite fast! (1000 fps might not be needed). I tried interval=200
and the error was gone...
HTH
Upvotes: 4
Reputation: 2481
try with:
if framenum == self.num_frames - 1:
exit()
it works for me...
Upvotes: 0