diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 36baff85fa66..23cc1c869c07 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3976,10 +3976,20 @@ def format_ydata(self, y): def format_coord(self, x, y): """Return a format string formatting the *x*, *y* coordinates.""" - return "x={} y={}".format( - "???" if x is None else self.format_xdata(x), - "???" if y is None else self.format_ydata(y), - ) + twins = self._twinned_axes.get_siblings(self) + if len(twins) == 1: + return "(x, y) = ({}, {})".format( + "???" if x is None else self.format_xdata(x), + "???" if y is None else self.format_ydata(y)) + screen_xy = self.transData.transform((x, y)) + xy_strs = [] + # Retrieve twins in the order of self.figure.axes to sort tied zorders (which is + # the common case) by the order in which they are added to the figure. + for ax in sorted(twins, key=attrgetter("zorder")): + data_x, data_y = ax.transData.inverted().transform(screen_xy) + xy_strs.append( + "({}, {})".format(ax.format_xdata(data_x), ax.format_ydata(data_y))) + return "(x, y) = {}".format(" | ".join(xy_strs)) def minorticks_on(self): """ diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index ee043f351dbb..a41bfe56744f 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -849,12 +849,18 @@ class Grouper: def __init__(self, init=()): self._mapping = weakref.WeakKeyDictionary( {x: weakref.WeakSet([x]) for x in init}) + self._ordering = weakref.WeakKeyDictionary() + for x in init: + if x not in self._ordering: + self._ordering[x] = len(self._ordering) + self._next_order = len(self._ordering) # Plain int to simplify pickling. def __getstate__(self): return { **vars(self), # Convert weak refs to strong ones. "_mapping": {k: set(v) for k, v in self._mapping.items()}, + "_ordering": {**self._ordering}, } def __setstate__(self, state): @@ -862,6 +868,7 @@ def __setstate__(self, state): # Convert strong refs to weak ones. self._mapping = weakref.WeakKeyDictionary( {k: weakref.WeakSet(v) for k, v in self._mapping.items()}) + self._ordering = weakref.WeakKeyDictionary(self._ordering) def __contains__(self, item): return item in self._mapping @@ -875,10 +882,19 @@ def join(self, a, *args): Join given arguments into the same set. Accepts one or more arguments. """ mapping = self._mapping - set_a = mapping.setdefault(a, weakref.WeakSet([a])) - + try: + set_a = mapping[a] + except KeyError: + set_a = mapping[a] = weakref.WeakSet([a]) + self._ordering[a] = self._next_order + self._next_order += 1 for arg in args: - set_b = mapping.get(arg, weakref.WeakSet([arg])) + try: + set_b = mapping[arg] + except KeyError: + set_b = mapping[arg] = weakref.WeakSet([arg]) + self._ordering[arg] = self._next_order + self._next_order += 1 if set_b is not set_a: if len(set_b) > len(set_a): set_a, set_b = set_b, set_a @@ -892,9 +908,8 @@ def joined(self, a, b): def remove(self, a): """Remove *a* from the grouper, doing nothing if it is not there.""" - set_a = self._mapping.pop(a, None) - if set_a: - set_a.remove(a) + self._mapping.pop(a, {a}).remove(a) + self._ordering.pop(a, None) def __iter__(self): """ @@ -904,12 +919,12 @@ def __iter__(self): """ unique_groups = {id(group): group for group in self._mapping.values()} for group in unique_groups.values(): - yield [x for x in group] + yield sorted(group, key=self._ordering.__getitem__) def get_siblings(self, a): """Return all of the items joined with *a*, including itself.""" siblings = self._mapping.get(a, [a]) - return [x for x in siblings] + return sorted(siblings, key=self._ordering.get) class GrouperView: diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index 399949c93bef..c264f01acdb2 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -1,5 +1,3 @@ -import re - from matplotlib import path, transforms from matplotlib.backend_bases import ( FigureCanvasBase, KeyEvent, LocationEvent, MouseButton, MouseEvent, @@ -123,11 +121,21 @@ def test_location_event_position(x, y): assert event.y == int(y) assert isinstance(event.y, int) if x is not None and y is not None: - assert re.match( - f"x={ax.format_xdata(x)} +y={ax.format_ydata(y)}", - ax.format_coord(x, y)) + assert (ax.format_coord(x, y) + == f"(x, y) = ({ax.format_xdata(x)}, {ax.format_ydata(y)})") ax.fmt_xdata = ax.fmt_ydata = lambda x: "foo" - assert re.match("x=foo +y=foo", ax.format_coord(x, y)) + assert ax.format_coord(x, y) == "(x, y) = (foo, foo)" + + +def test_location_event_position_twin(): + fig, ax = plt.subplots() + ax.set(xlim=(0, 10), ylim=(0, 20)) + assert ax.format_coord(5., 5.) == "(x, y) = (5.00, 5.00)" + ax.twinx().set(ylim=(0, 40)) + assert ax.format_coord(5., 5.) == "(x, y) = (5.00, 5.00) | (5.00, 10.0)" + ax.twiny().set(xlim=(0, 5)) + assert (ax.format_coord(5., 5.) + == "(x, y) = (5.00, 5.00) | (5.00, 10.0) | (2.50, 5.00)") def test_pick():
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: