From 57116cfc3bdafc3b108c36d7572ae880ad6caeb7 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 20 Jun 2024 14:52:41 +0200 Subject: [PATCH] Allow sharing locators across axises. Locally changing the assigned axis seems to be the most practical solution, if not the most elegant. --- .../next_api_changes/behavior/28429-AL.rst | 9 +++++++++ lib/matplotlib/axis.py | 6 +++--- lib/matplotlib/tests/test_ticker.py | 13 ++++++++++++ lib/matplotlib/ticker.py | 19 +++++++++++++++++- lib/mpl_toolkits/axisartist/axislines.py | 20 +++++++++++-------- 5 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/28429-AL.rst diff --git a/doc/api/next_api_changes/behavior/28429-AL.rst b/doc/api/next_api_changes/behavior/28429-AL.rst new file mode 100644 index 000000000000..1df04be6328c --- /dev/null +++ b/doc/api/next_api_changes/behavior/28429-AL.rst @@ -0,0 +1,9 @@ +Tick locating code now ensure that the locator's axis is correctly set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tick locators maintain a reference to the Axis on which they are used (so that +they can e.g. check the size of the Axis to know how many ticks will fit), but +is problematic if the same locator object is used for multiple Axis. From now +on, before calling a locator to determine tick locations, Matplotlib will +ensure that the locator's axis is correctly (temporarily) set to the correct +Axis. This fix can cause a change in behavior if a locator's axis had been +intentionally set to an artificial value. diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 921de9271be8..8d594c31f31f 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1527,14 +1527,14 @@ def get_ticklines(self, minor=False): def get_majorticklocs(self): """Return this Axis' major tick locations in data coordinates.""" - return self.major.locator() + return self.major.locator._call_with_axis(self) def get_minorticklocs(self): """Return this Axis' minor tick locations in data coordinates.""" # Remove minor ticks duplicating major ticks. - minor_locs = np.asarray(self.minor.locator()) + minor_locs = np.asarray(self.minor.locator._call_with_axis(self)) if self.remove_overlapping_locs: - major_locs = self.major.locator() + major_locs = self.major.locator._call_with_axis(self) transform = self._scale.get_transform() tr_minor_locs = transform.transform(minor_locs) tr_major_locs = transform.transform(major_locs) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index ac68a5d90b14..9b6c881a7a4b 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -1873,3 +1873,16 @@ def test_minorticks_on_multi_fig(): assert ax.get_xgridlines() assert isinstance(ax.xaxis.get_minor_locator(), mpl.ticker.AutoMinorLocator) + + +def test_locator_reuse(): + fig = plt.figure() + ax = fig.add_subplot(xlim=(.6, .8)) + loc = mticker.AutoLocator() + ax.xaxis.set_major_locator(loc) + ax.yaxis.set_major_locator(loc) + fig.draw_without_rendering() + xticklabels = [l.get_text() for l in ax.get_xticklabels()] + yticklabels = [l.get_text() for l in ax.get_yticklabels()] + assert xticklabels == ["0.60", "0.65", "0.70", "0.75", "0.80"] + assert yticklabels == ["0.0", "0.2", "0.4", "0.6", "0.8", "1.0"] diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 2b00937f9e29..b008a8447baf 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1629,11 +1629,28 @@ def set_params(self, **kwargs): str(type(self))) def __call__(self): - """Return the locations of the ticks.""" + """ + Return the locations of the ticks. + + The returned locations depend on the locator's axis. If a locator + is used across multiple axises, make sure to (temporarily) set + ``locator.axis`` to the correct axis before getting the tick locations. + """ # note: some locators return data limits, other return view limits, # hence there is no *one* interface to call self.tick_values. raise NotImplementedError('Derived must override') + def _call_with_axis(self, axis): + """ + Get the tick locations while the locator's axis is temporarily set to *axis*. + """ + current = axis + try: + self.set_axis(axis) + return self() + finally: + self.set_axis(current) + def raise_if_exceeds(self, locs): """ Log at WARNING level if *locs* is longer than `Locator.MAXTICKS`. diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index 1d695c129ae2..03febd5980dd 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -185,11 +185,11 @@ def get_tick_iterators(self, axes): angle_normal, angle_tangent = {0: (90, 0), 1: (0, 90)}[self.nth_coord] major = self.axis.major - major_locs = major.locator() + major_locs = major.locator._call_with_axis(self.axis) major_labels = major.formatter.format_ticks(major_locs) minor = self.axis.minor - minor_locs = minor.locator() + minor_locs = minor.locator._call_with_axis(self.axis) minor_labels = minor.formatter.format_ticks(minor_locs) tick_to_axes = self.get_tick_transform(axes) - axes.transAxes @@ -246,11 +246,11 @@ def get_tick_iterators(self, axes): angle_normal, angle_tangent = {0: (90, 0), 1: (0, 90)}[self.nth_coord] major = self.axis.major - major_locs = major.locator() + major_locs = major.locator._call_with_axis(self.axis) major_labels = major.formatter.format_ticks(major_locs) minor = self.axis.minor - minor_locs = minor.locator() + minor_locs = minor.locator._call_with_axis(self.axis) minor_labels = minor.formatter.format_ticks(minor_locs) data_to_axes = axes.transData - axes.transAxes @@ -351,18 +351,22 @@ def get_gridlines(self, which="major", axis="both"): locs = [] y1, y2 = self.axes.get_ylim() if which in ("both", "major"): - locs.extend(self.axes.xaxis.major.locator()) + locs.extend( + self.axes.xaxis.major.locator._call_with_axis(self.axes.xaxis)) if which in ("both", "minor"): - locs.extend(self.axes.xaxis.minor.locator()) + locs.extend( + self.axes.xaxis.minor.locator._call_with_axis(self.axes.xaxis)) gridlines.extend([[x, x], [y1, y2]] for x in locs) if axis in ("both", "y"): x1, x2 = self.axes.get_xlim() locs = [] if self.axes.yaxis._major_tick_kw["gridOn"]: - locs.extend(self.axes.yaxis.major.locator()) + locs.extend( + self.axes.yaxis.major.locator._call_with_axis(self.axes.yaxis)) if self.axes.yaxis._minor_tick_kw["gridOn"]: - locs.extend(self.axes.yaxis.minor.locator()) + locs.extend( + self.axes.yaxis.minor.locator._call_with_axis(self.axes.yaxis)) gridlines.extend([[x1, x2], [y, y]] for y in locs) return gridlines pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy