diff --git a/examples/animation/animate_decay.py b/examples/animation/animate_decay.py index d292814a3c73..38d9675582fe 100644 --- a/examples/animation/animate_decay.py +++ b/examples/animation/animate_decay.py @@ -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() diff --git a/examples/animation/animated_histogram.py b/examples/animation/animated_histogram.py index fc4e7f2dad69..adec98b24d82 100644 --- a/examples/animation/animated_histogram.py +++ b/examples/animation/animated_histogram.py @@ -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() diff --git a/examples/animation/bayes_update.py b/examples/animation/bayes_update.py index 59462d3420f2..a7759aea2317 100644 --- a/examples/animation/bayes_update.py +++ b/examples/animation/bayes_update.py @@ -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() diff --git a/examples/animation/double_pendulum_sgskip.py b/examples/animation/double_pendulum_sgskip.py index 55657f336e85..e9101c6c74f4 100644 --- a/examples/animation/double_pendulum_sgskip.py +++ b/examples/animation/double_pendulum_sgskip.py @@ -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() diff --git a/examples/animation/rain.py b/examples/animation/rain.py index 6fe60ff0520d..f254b9233fe0 100644 --- a/examples/animation/rain.py +++ b/examples/animation/rain.py @@ -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() diff --git a/examples/animation/random_walk.py b/examples/animation/random_walk.py index 07e4707517f2..5f2dee92e985 100644 --- a/examples/animation/random_walk.py +++ b/examples/animation/random_walk.py @@ -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() diff --git a/examples/animation/strip_chart.py b/examples/animation/strip_chart.py index 2661e1927b97..91482e978779 100644 --- a/examples/animation/strip_chart.py +++ b/examples/animation/strip_chart.py @@ -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() diff --git a/examples/animation/unchained.py b/examples/animation/unchained.py index fbcce8337ee9..2d028dbb2dcf 100644 --- a/examples/animation/unchained.py +++ b/examples/animation/unchained.py @@ -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() diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 5744c257fcb7..85f562a82bd8 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -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() diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index a519fadc4197..f8feead124ce 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -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` @@ -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): @@ -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 @@ -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 ---------- @@ -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 @@ -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 @@ -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 @@ -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` @@ -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 @@ -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""" @@ -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. @@ -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 @@ -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 @@ -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. @@ -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`