Skip to content

Commit 22b6e0d

Browse files
committed
Fix pickling of AxesWidgets.
There's no need to hold onto the (non-picklable) canvas as an attribute.
1 parent d347c32 commit 22b6e0d

File tree

3 files changed

+25
-30
lines changed

3 files changed

+25
-30
lines changed

lib/matplotlib/tests/test_pickle.py

+8
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,11 @@ def test_cycler():
307307
ax = pickle.loads(pickle.dumps(ax))
308308
l, = ax.plot([3, 4])
309309
assert l.get_color() == "m"
310+
311+
312+
# Run under an interactive backend to test that we don't try to pickle the
313+
# (interactive and non-picklable) canvas.
314+
@pytest.mark.backend('tkagg')
315+
def test_axeswidget_interactive():
316+
ax = plt.figure().add_subplot()
317+
pickle.dumps(mpl.widgets.Button(ax, "button"))

lib/matplotlib/widgets.py

+8-27
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,6 @@ def ignore(self, event):
9090
"""
9191
return not self.active
9292

93-
def _changed_canvas(self):
94-
"""
95-
Someone has switched the canvas on us!
96-
97-
This happens if `savefig` needs to save to a format the previous
98-
backend did not support (e.g. saving a figure using an Agg based
99-
backend saved to a vector format).
100-
101-
Returns
102-
-------
103-
bool
104-
True if the canvas has been changed.
105-
106-
"""
107-
return self.canvas is not self.ax.figure.canvas
108-
10993

11094
class AxesWidget(Widget):
11195
"""
@@ -131,9 +115,10 @@ class AxesWidget(Widget):
131115

132116
def __init__(self, ax):
133117
self.ax = ax
134-
self.canvas = ax.figure.canvas
135118
self._cids = []
136119

120+
canvas = property(lambda self: self.ax.figure.canvas)
121+
137122
def connect_event(self, event, callback):
138123
"""
139124
Connect a callback function with an event.
@@ -1100,7 +1085,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
11001085

11011086
def _clear(self, event):
11021087
"""Internal event handler to clear the buttons."""
1103-
if self.ignore(event) or self._changed_canvas():
1088+
if self.ignore(event) or self.canvas.is_saving():
11041089
return
11051090
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
11061091
self.ax.draw_artist(self._checks)
@@ -1677,7 +1662,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16771662

16781663
def _clear(self, event):
16791664
"""Internal event handler to clear the buttons."""
1680-
if self.ignore(event) or self._changed_canvas():
1665+
if self.ignore(event) or self.canvas.is_saving():
16811666
return
16821667
self._background = self.canvas.copy_from_bbox(self.ax.bbox)
16831668
self.ax.draw_artist(self._buttons)
@@ -1933,7 +1918,7 @@ def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False,
19331918

19341919
def clear(self, event):
19351920
"""Internal event handler to clear the cursor."""
1936-
if self.ignore(event) or self._changed_canvas():
1921+
if self.ignore(event) or self.canvas.is_saving():
19371922
return
19381923
if self.useblit:
19391924
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
@@ -2573,9 +2558,7 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False,
25732558
self.drag_from_anywhere = drag_from_anywhere
25742559
self.ignore_event_outside = ignore_event_outside
25752560

2576-
# Reset canvas so that `new_axes` connects events.
2577-
self.canvas = None
2578-
self.new_axes(ax, _props=props)
2561+
self.new_axes(ax, _props=props, _init=True)
25792562

25802563
# Setup handles
25812564
self._handle_props = {
@@ -2588,14 +2571,12 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False,
25882571

25892572
self._active_handle = None
25902573

2591-
def new_axes(self, ax, *, _props=None):
2574+
def new_axes(self, ax, *, _props=None, _init=False):
25922575
"""Set SpanSelector to operate on a new Axes."""
25932576
self.ax = ax
2594-
if self.canvas is not ax.figure.canvas:
2577+
if _init or self.canvas is not ax.figure.canvas:
25952578
if self.canvas is not None:
25962579
self.disconnect_events()
2597-
2598-
self.canvas = ax.figure.canvas
25992580
self.connect_default_events()
26002581

26012582
# Reset

lib/matplotlib/widgets.pyi

+9-3
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ class Widget:
3333

3434
class AxesWidget(Widget):
3535
ax: Axes
36-
canvas: FigureCanvasBase | None
3736
def __init__(self, ax: Axes) -> None: ...
37+
@property
38+
def canvas(self) -> FigureCanvasBase | None: ...
3839
def connect_event(self, event: Event, callback: Callable) -> None: ...
3940
def disconnect_events(self) -> None: ...
4041

@@ -310,7 +311,6 @@ class SpanSelector(_SelectorWidget):
310311
grab_range: float
311312
drag_from_anywhere: bool
312313
ignore_event_outside: bool
313-
canvas: FigureCanvasBase | None
314314
def __init__(
315315
self,
316316
ax: Axes,
@@ -330,7 +330,13 @@ class SpanSelector(_SelectorWidget):
330330
ignore_event_outside: bool = ...,
331331
snap_values: ArrayLike | None = ...,
332332
) -> None: ...
333-
def new_axes(self, ax: Axes, *, _props: dict[str, Any] | None = ...) -> None: ...
333+
def new_axes(
334+
self,
335+
ax: Axes,
336+
*,
337+
_props: dict[str, Any] | None = ...,
338+
_init: bool = ...,
339+
) -> None: ...
334340
def connect_default_events(self) -> None: ...
335341
@property
336342
def direction(self) -> Literal["horizontal", "vertical"]: ...

0 commit comments

Comments
 (0)