From 7a8a8e73eae8d79e4089e4f12120903e55c37276 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 13 Oct 2017 01:57:54 -0700 Subject: [PATCH] Assign event to later Axes if zorders are tied. If axes zorders are tied, axes appearing later in figure.axes get drawn above those appearing earlier, so events should be preferably assigned to them. By default, max() returns the *first* maximum in case of ties, so the previous implementation was not satisfactory. --- lib/matplotlib/backend_bases.py | 31 ++++++++++++++----------------- lib/matplotlib/backend_tools.py | 2 +- lib/matplotlib/cbook/__init__.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d7c6e61e6c95..4190c8506203 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1527,22 +1527,19 @@ def __init__(self, name, canvas, x, y, guiEvent=None): else: axes_list = [self.canvas.mouse_grabber] - if axes_list: # Use highest zorder. - self.inaxes = max(axes_list, key=lambda x: x.zorder) - else: # None found. - self.inaxes = None - self._update_enter_leave() - return - - try: - trans = self.inaxes.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) - except ValueError: - self.xdata = None - self.ydata = None + if axes_list: + self.inaxes = cbook._topmost_artist(axes_list) + try: + trans = self.inaxes.transData.inverted() + xdata, ydata = trans.transform_point((x, y)) + except ValueError: + self.xdata = None + self.ydata = None + else: + self.xdata = xdata + self.ydata = ydata else: - self.xdata = xdata - self.ydata = ydata + self.inaxes = None self._update_enter_leave() @@ -1805,7 +1802,7 @@ def onRemove(self, ev): canvas.mpl_connect('mouse_press_event',canvas.onRemove) """ # Find the top artist under the cursor - under = sorted(self.figure.hitlist(ev), key=lambda x: x.zorder) + under = cbook._topmost_artist(self.figure.hitlist(ev)) h = None if under: h = under[-1] @@ -2893,7 +2890,7 @@ def mouse_move(self, event): if a.contains(event) and a.get_visible()] if artists: - a = max(artists, key=lambda x: x.zorder) + a = cbook._topmost_artist(artists) if a is not event.inaxes.patch: data = a.get_cursor_data(event) if data is not None: diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 553f3b62c5b0..415fe8442b47 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -340,7 +340,7 @@ def send_message(self, event): if a.contains(event) and a.get_visible()] if artists: - a = max(artists, key=lambda x: x.zorder) + a = cbook._topmost_artist(artists) if a is not event.inaxes.patch: data = a.get_cursor_data(event) if data is not None: diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index bf33e5fe65cc..0fc3052f783f 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -21,6 +21,7 @@ from itertools import repeat import locale import numbers +import operator import os import re import sys @@ -2752,3 +2753,16 @@ def _get_key_params(self): (params, str_func)) return str_func, params + + +def _topmost_artist( + artists, + _cached_max=functools.partial(max, key=operator.attrgetter("zorder"))): + """Get the topmost artist of a list. + + In case of a tie, return the *last* of the tied artists, as it will be + drawn on top of the others. `max` returns the first maximum in case of ties + (on Py2 this is undocumented but true), so we need to iterate over the list + in reverse order. + """ + return _cached_max(reversed(artists))