diff --git a/doc/users/next_whats_new/imshow_extent_units.rst b/doc/users/next_whats_new/imshow_extent_units.rst new file mode 100644 index 000000000000..ec94065181f8 --- /dev/null +++ b/doc/users/next_whats_new/imshow_extent_units.rst @@ -0,0 +1,26 @@ +The *extent* of ``imshow`` can now be expressed with units +---------------------------------------------------------- +The *extent* parameter of `~.axes.Axes.imshow` and `~.AxesImage.set_extent` +can now be expressed with units. + +.. plot:: + :include-source: true + + import matplotlib.pyplot as plt + import numpy as np + from matplotlib.dates import HourLocator, DateFormatter + from matplotlib.ticker import AutoMinorLocator + + fig, ax = plt.subplots() + date_first = np.datetime64('2020-01-01', 'D') + date_last = np.datetime64('2020-01-11', 'D') + + arr = [[i+j for i in range(10)] for j in range(10)] + + ax.imshow(arr, origin='lower', extent=[1, 11, date_first, date_last]) + + ax.yaxis.set_major_formatter(DateFormatter('%d/%m/%y:- %H00hours')) + ax.yaxis.set_major_locator(HourLocator(byhour=[0, 6, 12, 18, 24])) + ax.yaxis.set_minor_locator(AutoMinorLocator()) + + plt.show() diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 42891ce558c5..fdd3cd3bc74c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5555,6 +5555,7 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, extent : floats (left, right, bottom, top), optional The bounding box in data coordinates that the image will fill. + These values may be unitful and match the units of the Axes. The image is stretched individually along x and y to fill the box. The default extent is determined by the following conditions. diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 6401b64dc248..895b99a06331 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -953,7 +953,7 @@ def _check_unsampled_image(self): """Return whether the image would be better drawn unsampled.""" return self.get_interpolation() == "none" - def set_extent(self, extent): + def set_extent(self, extent, **kwargs): """ Set the image extent. @@ -962,6 +962,10 @@ def set_extent(self, extent): extent : 4-tuple of float The position and size of the image as tuple ``(left, right, bottom, top)`` in data coordinates. + **kwargs + Other parameters from which unit info (i.e., the *xunits*, + *yunits*, *zunits* (for 3D axes), *runits* and *thetaunits* (for + polar axes) entries are applied, if present. Notes ----- @@ -970,7 +974,26 @@ def set_extent(self, extent): state is not changed, so following this with ``ax.autoscale_view()`` will redo the autoscaling in accord with ``dataLim``. """ - self._extent = xmin, xmax, ymin, ymax = extent + (xmin, xmax), (ymin, ymax) = self.axes._process_unit_info( + [("x", [extent[0], extent[1]]), + ("y", [extent[2], extent[3]])], + kwargs) + if len(kwargs): + raise ValueError( + "set_extent did not consume all of the kwargs passed." + + f"{list(kwargs)!r} were unused" + ) + xmin = self.axes._validate_converted_limits( + xmin, self.convert_xunits) + xmax = self.axes._validate_converted_limits( + xmax, self.convert_xunits) + ymin = self.axes._validate_converted_limits( + ymin, self.convert_yunits) + ymax = self.axes._validate_converted_limits( + ymax, self.convert_yunits) + extent = [xmin, xmax, ymin, ymax] + + self._extent = extent corners = (xmin, ymin), (xmax, ymax) self.axes.update_datalim(corners) self.sticky_edges.x[:] = [xmin, xmax] diff --git a/lib/matplotlib/tests/baseline_images/test_axes/extent_units.png b/lib/matplotlib/tests/baseline_images/test_axes/extent_units.png new file mode 100644 index 000000000000..28bde8bf76ec Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/extent_units.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 00091b9f69b4..f507d31bb268 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8272,3 +8272,39 @@ def test_bar_all_nan(fig_test, fig_ref): ax_ref.bar([1], [1]).remove() ax_ref.bar([1], [1]) + + +@image_comparison(["extent_units.png"], style="mpl20") +def test_extent_units(): + _, axs = plt.subplots(2, 2) + date_first = np.datetime64('2020-01-01', 'D') + date_last = np.datetime64('2020-01-11', 'D') + arr = [[i+j for i in range(10)] for j in range(10)] + + axs[0, 0].set_title('Date extents on y axis') + im = axs[0, 0].imshow(arr, origin='lower', + extent=[1, 11, date_first, date_last], + cmap=mpl.colormaps["plasma"]) + + axs[0, 1].set_title('Date extents on x axis (Day of Jan 2020)') + im = axs[0, 1].imshow(arr, origin='lower', + extent=[date_first, date_last, 1, 11], + cmap=mpl.colormaps["plasma"]) + axs[0, 1].xaxis.set_major_formatter(mdates.DateFormatter('%d')) + + im = axs[1, 0].imshow(arr, origin='lower', + extent=[date_first, date_last, + date_first, date_last], + cmap=mpl.colormaps["plasma"]) + axs[1, 0].xaxis.set_major_formatter(mdates.DateFormatter('%d')) + axs[1, 0].set(xlabel='Day of Jan 2020') + + im = axs[1, 1].imshow(arr, origin='lower', + cmap=mpl.colormaps["plasma"]) + im.set_extent([date_last, date_first, date_last, date_first]) + axs[1, 1].xaxis.set_major_formatter(mdates.DateFormatter('%d')) + axs[1, 1].set(xlabel='Day of Jan 2020') + + with pytest.raises(ValueError, + match="set_extent did not consume all of the kwargs"): + im.set_extent([2, 12, date_first, date_last], clip=False) 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