Skip to content

Prevent GC of animations and widgets still in use. #16221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/animation/animate_decay.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ def run(data):

return line,

ani = animation.FuncAnimation(fig, run, data_gen, blit=False, interval=10,
repeat=False, init_func=init)
animation.FuncAnimation(fig, run, data_gen, blit=False, interval=10,
repeat=False, init_func=init)
plt.show()
2 changes: 1 addition & 1 deletion examples/animation/animated_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,5 @@ def animate(i):
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())

ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
plt.show()
4 changes: 2 additions & 2 deletions examples/animation/bayes_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ def __call__(self, i):

fig, ax = plt.subplots()
ud = UpdateDist(ax, prob=0.7)
anim = FuncAnimation(fig, ud, frames=np.arange(100), init_func=ud.init,
interval=100, blit=True)
FuncAnimation(fig, ud, frames=np.arange(100), init_func=ud.init,
interval=100, blit=True)
plt.show()
4 changes: 2 additions & 2 deletions examples/animation/double_pendulum_sgskip.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ def animate(i):
return line, time_text


ani = animation.FuncAnimation(fig, animate, range(1, len(y)),
interval=dt*1000, blit=True, init_func=init)
animation.FuncAnimation(fig, animate, range(1, len(y)),
interval=dt*1000, blit=True, init_func=init)
plt.show()
2 changes: 1 addition & 1 deletion examples/animation/rain.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,5 @@ def update(frame_number):


# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=10)
FuncAnimation(fig, update, interval=10)
plt.show()
2 changes: 1 addition & 1 deletion examples/animation/random_walk.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def update_lines(num, data_lines, lines):
ax.set_title('3D Test')

# Creating the Animation object
line_ani = animation.FuncAnimation(
animation.FuncAnimation(
fig, update_lines, 25, fargs=(data, lines), interval=50)

plt.show()
3 changes: 1 addition & 2 deletions examples/animation/strip_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ def emitter(p=0.03):
scope = Scope(ax)

# pass a generator in "emitter" to produce data for the update func
ani = animation.FuncAnimation(fig, scope.update, emitter, interval=10,
blit=True)
animation.FuncAnimation(fig, scope.update, emitter, interval=10, blit=True)

plt.show()
2 changes: 1 addition & 1 deletion examples/animation/unchained.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ def update(*args):
return lines

# Construct the animation, using the update function as the animation director.
anim = animation.FuncAnimation(fig, update, interval=10)
animation.FuncAnimation(fig, update, interval=10)
plt.show()
9 changes: 6 additions & 3 deletions lib/matplotlib/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,14 +929,17 @@ def __init__(self, fig, event_source=None, blit=False):
self.frame_seq = self.new_frame_seq()
self.event_source = event_source

# Wrapper lambdas prevent GC.

# Instead of starting the event source now, we connect to the figure's
# draw_event, so that we only start once the figure has been drawn.
self._first_draw_id = fig.canvas.mpl_connect('draw_event', self._start)
self._first_draw_id = fig.canvas.mpl_connect(
'draw_event', lambda event: self._start(event))

# Connect to the figure's close_event so that we don't continue to
# fire events and try to draw to a deleted figure.
self._close_id = self._fig.canvas.mpl_connect('close_event',
self._stop)
self._close_id = self._fig.canvas.mpl_connect(
'close_event', lambda event: self._stop(event))
if self._blit:
self._setup_blit()

Expand Down
50 changes: 11 additions & 39 deletions lib/matplotlib/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,6 @@ class AxesWidget(Widget):
"""
Widget connected to a single `~matplotlib.axes.Axes`.

To guarantee that the widget remains responsive and not garbage-collected,
a reference to the object should be maintained by the user.

This is necessary because the callback registry
maintains only weak-refs to the functions, which are member
functions of the widget. If there are no references to the widget
object it may be garbage collected which will disconnect the callbacks.

Attributes
----------
ax : `~matplotlib.axes.Axes`
Expand All @@ -121,9 +113,11 @@ def connect_event(self, event, callback):
Connect callback with an event.

This should be used in lieu of `figure.canvas.mpl_connect` since this
function stores callback ids for later clean up.
function stores callback ids for later clean up, and sets up a wrapper
callback to prevent the original callback and thus the widget from
being garbage collected.
"""
cid = self.canvas.mpl_connect(event, callback)
cid = self.canvas.mpl_connect(event, lambda event: callback(event))
self.cids.append(cid)

def disconnect_events(self):
Expand All @@ -136,7 +130,6 @@ class Button(AxesWidget):
"""
A GUI neutral button.

For the button to remain responsive you must keep a reference to it.
Call `.on_clicked` to connect to the button.

Attributes
Expand Down Expand Up @@ -247,9 +240,8 @@ class Slider(AxesWidget):
"""
A slider representing a floating point range.

Create a slider from *valmin* to *valmax* in axes *ax*. For the slider to
remain responsive you must maintain a reference to it. Call
:meth:`on_changed` to connect to the slider event.
Create a slider from *valmin* to *valmax* in axes *ax*.
Call :meth:`on_changed` to connect to the slider event.

Attributes
----------
Expand Down Expand Up @@ -505,9 +497,6 @@ class CheckButtons(AxesWidget):
r"""
A GUI neutral set of check buttons.

For the check buttons to remain responsive you must keep a
reference to this object.

Connect to the CheckButtons with the `.on_clicked` method.

Attributes
Expand Down Expand Up @@ -660,8 +649,6 @@ class TextBox(AxesWidget):
"""
A GUI neutral text input box.

For the text box to remain responsive you must keep a reference to it.

Call `.on_text_change` to be updated whenever the text changes.

Call `.on_submit` to be updated whenever the user hits enter or
Expand Down Expand Up @@ -968,9 +955,6 @@ class RadioButtons(AxesWidget):
"""
A GUI neutral radio button.

For the buttons to remain responsive you must keep a reference to this
object.

Connect to the RadioButtons with the `.on_clicked` method.

Attributes
Expand Down Expand Up @@ -1236,8 +1220,6 @@ class Cursor(AxesWidget):
"""
A crosshair cursor that spans the axes and moves with mouse cursor.

For the cursor to remain responsive you must keep a reference to it.

Parameters
----------
ax : `matplotlib.axes.Axes`
Expand Down Expand Up @@ -1331,8 +1313,6 @@ class MultiCursor(Widget):
Provide a vertical (default) and/or horizontal line cursor shared between
multiple axes.

For the cursor to remain responsive you must keep a reference to it.

Example usage::

from matplotlib.widgets import MultiCursor
Expand Down Expand Up @@ -1386,9 +1366,11 @@ def __init__(self, canvas, axes, useblit=True, horizOn=False, vertOn=True,

def connect(self):
"""connect events"""
self._cidmotion = self.canvas.mpl_connect('motion_notify_event',
self.onmove)
self._ciddraw = self.canvas.mpl_connect('draw_event', self.clear)
# Wrapper lambdas prevent GC.
self._cidmotion = self.canvas.mpl_connect(
'motion_notify_event', lambda event: self.onmove(event))
self._ciddraw = self.canvas.mpl_connect(
'draw_event', lambda event: self.clear(event))

def disconnect(self):
"""disconnect events"""
Expand Down Expand Up @@ -1656,8 +1638,6 @@ class SpanSelector(_SelectorWidget):
Visually select a min/max range on a single axis and call a function with
those values.

To guarantee that the selector remains responsive, keep a reference to it.

In order to turn off the SpanSelector, set ``span_selector.active`` to
False. To turn it back on, set it to True.

Expand Down Expand Up @@ -1932,8 +1912,6 @@ class RectangleSelector(_SelectorWidget):
"""
Select a rectangular region of an axes.

For the cursor to remain responsive you must keep a reference to it.

Example usage::

import numpy as np
Expand Down Expand Up @@ -2357,8 +2335,6 @@ class EllipseSelector(RectangleSelector):
"""
Select an elliptical region of an axes.

For the cursor to remain responsive you must keep a reference to it.

Example usage::

import numpy as np
Expand Down Expand Up @@ -2427,8 +2403,6 @@ class LassoSelector(_SelectorWidget):
"""
Selection curve of an arbitrary shape.

For the selector to remain responsive you must keep a reference to it.

The selected path can be used in conjunction with `~.Path.contains_point`
to select data points from an image.

Expand Down Expand Up @@ -2509,8 +2483,6 @@ class PolygonSelector(_SelectorWidget):
drag anywhere in the axes to move all vertices. Press the *esc* key to
start a new polygon.

For the selector to remain responsive you must keep a reference to it.

Parameters
----------
ax : `~matplotlib.axes.Axes`
Expand Down