From 4c11aad1c904988e3561e3b51bafd9d5818476c3 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 1 Mar 2021 20:23:57 +0100 Subject: [PATCH] Fix double picks. pick_events were previously incorrectly emitted twice due to the combination of two recent(ish) chnages: Figures now always start with a FigureCanvasBase attached -- eventually switching to a concrete subclass of FigureCanvasBase for display or saving --, and callbacks are now actually stored at the Figure level rather than the Canvas level. Hence, the button_pick_id callback (in charge of emitting picks) would previously be both registered both through the FigureCanvasBase and the concrete subclass. The fix is to also move that callback to the Figure level, so that each Figure only has one such callback. --- lib/matplotlib/backend_bases.py | 8 +++----- lib/matplotlib/figure.py | 4 ++++ lib/matplotlib/tests/test_backend_bases.py | 13 +++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 3798a936ef75..32e069ed69cf 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1726,15 +1726,13 @@ def __init__(self, figure=None): self._button = None # the button pressed self._key = None # the key pressed self._lastx, self._lasty = None, None - self.button_pick_id = self.mpl_connect('button_press_event', self.pick) - self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick) self.mouse_grabber = None # the axes currently grabbing mouse self.toolbar = None # NavigationToolbar2 will set me self._is_idle_drawing = False - @property - def callbacks(self): - return self.figure._canvas_callbacks + callbacks = property(lambda self: self.figure._canvas_callbacks) + button_pick_id = property(lambda self: self.figure._button_pick_id) + scroll_pick_id = property(lambda self: self.figure._scroll_pick_id) @classmethod @functools.lru_cache() diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 44c436937487..e0f45fde140d 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2106,6 +2106,10 @@ def __init__(self, # a proxy property), but that actually need to be on the figure for # pickling. self._canvas_callbacks = cbook.CallbackRegistry() + self._button_pick_id = self._canvas_callbacks.connect( + 'button_press_event', lambda event: self.canvas.pick(event)) + self._scroll_pick_id = self._canvas_callbacks.connect( + 'scroll_event', lambda event: self.canvas.pick(event)) if figsize is None: figsize = mpl.rcParams['figure.figsize'] diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index 8288cd1f2190..9f08bc223a7c 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -120,6 +120,19 @@ def test_location_event_position(x, y): assert re.match("x=foo +y=foo", ax.format_coord(x, y)) +def test_pick(): + fig = plt.figure() + fig.text(.5, .5, "hello", ha="center", va="center", picker=True) + fig.canvas.draw() + picks = [] + fig.canvas.mpl_connect("pick_event", lambda event: picks.append(event)) + start_event = MouseEvent( + "button_press_event", fig.canvas, *fig.transFigure.transform((.5, .5)), + MouseButton.LEFT) + fig.canvas.callbacks.process(start_event.name, start_event) + assert len(picks) == 1 + + def test_interactive_zoom(): fig, ax = plt.subplots() ax.set(xscale="logit")