From c582fa18bf56621565a7af3f14d5d4d76bfe91b1 Mon Sep 17 00:00:00 2001 From: smheidrich Date: Fri, 4 Jan 2019 01:46:18 +0100 Subject: [PATCH] axes_grid1: add host/parasite axes pick methods Fixes #5581 --- lib/mpl_toolkits/axes_grid1/parasite_axes.py | 19 +++++++ lib/mpl_toolkits/tests/test_axes_grid1.py | 59 ++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/lib/mpl_toolkits/axes_grid1/parasite_axes.py b/lib/mpl_toolkits/axes_grid1/parasite_axes.py index 2143076573a9..ccdd4b8a4034 100644 --- a/lib/mpl_toolkits/axes_grid1/parasite_axes.py +++ b/lib/mpl_toolkits/axes_grid1/parasite_axes.py @@ -36,6 +36,18 @@ def cla(self): self.xaxis.set_zorder(2.5) self.yaxis.set_zorder(2.5) + def pick(self, mouseevent): + # This most likely goes to Artist.pick (depending on axes_class given + # to the factory), which only handles pick events registered on the + # axes associated with each child: + super().pick(mouseevent) + # But parasite axes are additionally given pick events from their host + # axes (cf. HostAxesBase.pick), which we handle here: + for a in self.get_children(): + if (hasattr(mouseevent.inaxes, "parasites") + and self in mouseevent.inaxes.parasites): + a.pick(mouseevent) + @functools.lru_cache(None) def parasite_axes_class_factory(axes_class=None): @@ -232,6 +244,13 @@ def cla(self): ax.cla() super().cla() + def pick(self, mouseevent): + super().pick(mouseevent) + # Also pass pick events on to parasite axes and, in turn, their + # children (cf. ParasiteAxesBase.pick) + for a in self.parasites: + a.pick(mouseevent) + def twinx(self, axes_class=None): """ create a twin of Axes for generating a plot with a sharex diff --git a/lib/mpl_toolkits/tests/test_axes_grid1.py b/lib/mpl_toolkits/tests/test_axes_grid1.py index 8030028ab0f2..fef2ebc210b1 100644 --- a/lib/mpl_toolkits/tests/test_axes_grid1.py +++ b/lib/mpl_toolkits/tests/test_axes_grid1.py @@ -17,6 +17,7 @@ AnchoredSizeBar, AnchoredDirectionArrows) +from matplotlib.backend_bases import MouseEvent from matplotlib.colors import LogNorm from matplotlib.transforms import Bbox, TransformedBbox from itertools import product @@ -422,3 +423,61 @@ def test_gettightbbox(): bbox = fig.get_tightbbox(fig.canvas.get_renderer()) np.testing.assert_array_almost_equal(bbox.extents, [-17.7, -13.9, 7.2, 5.4]) + + +@pytest.mark.parametrize("click_on", ["big", "small"]) +@pytest.mark.parametrize("big_on_axes,small_on_axes", [ + ("gca", "gca"), + ("host", "host"), + ("host", "parasite"), + ("parasite", "host"), + ("parasite", "parasite") +]) +def test_picking_callbacks_overlap(big_on_axes, small_on_axes, click_on): + """Test pick events on normal, host or parasite axes.""" + # Two rectangles are drawn and "clicked on", a small one and a big one + # enclosing the small one. The axis on which they are drawn as well as the + # rectangle that is clicked on are varied. + # In each case we expect that both rectangles are picked if we click on the + # small one and only the big one is picked if we click on the big one. + # Also tests picking on normal axes ("gca") as a control. + big = plt.Rectangle((0.25, 0.25), 0.5, 0.5, picker=5) + small = plt.Rectangle((0.4, 0.4), 0.2, 0.2, facecolor="r", picker=5) + # Machinery for "receiving" events + received_events = [] + def on_pick(event): + received_events.append(event) + plt.gcf().canvas.mpl_connect('pick_event', on_pick) + # Shortcut + rectangles_on_axes = (big_on_axes, small_on_axes) + # Axes setup + axes = {"gca": None, "host": None, "parasite": None} + if "gca" in rectangles_on_axes: + axes["gca"] = plt.gca() + if "host" in rectangles_on_axes or "parasite" in rectangles_on_axes: + axes["host"] = host_subplot(111) + axes["parasite"] = axes["host"].twin() + # Add rectangles to axes + axes[big_on_axes].add_patch(big) + axes[small_on_axes].add_patch(small) + # Simulate picking with click mouse event + if click_on == "big": + click_axes = axes[big_on_axes] + axes_coords = (0.3, 0.3) + else: + click_axes = axes[small_on_axes] + axes_coords = (0.5, 0.5) + # In reality mouse events never happen on parasite axes, only host axes + if click_axes is axes["parasite"]: + click_axes = axes["host"] + (x, y) = click_axes.transAxes.transform(axes_coords) + m = MouseEvent("button_press_event", click_axes.figure.canvas, x, y, + button=1) + click_axes.pick(m) + # Checks + expected_n_events = 2 if click_on == "small" else 1 + assert len(received_events) == expected_n_events + event_rects = [event.artist for event in received_events] + assert big in event_rects + if click_on == "small": + assert small in event_rects