diff --git a/doc/users/whats_new/adjustable_xlim_ylim.rst b/doc/users/whats_new/adjustable_xlim_ylim.rst new file mode 100644 index 000000000000..3b85aad4ae43 --- /dev/null +++ b/doc/users/whats_new/adjustable_xlim_ylim.rst @@ -0,0 +1,13 @@ +New ``'xlim'`` and ``'ylim'`` options for ``adjustable`` argument +----------------------------------------------------------------- + +The ``adjustable`` argument to the :meth:`~matplotlib.axes.Axes.set_aspect` +method (and the same argument which can be specified when initializing +:meth:`~matplotlib.axes.Axes`) can take two new values: ``'xlim'`` and +``'ylim'``. Previously, users could pass the ``'datalim'`` value to indicate +that Matplotlib should adjust the limits as needed so as to be able to avoid +modifying the position and aspect ratio of the axes, but it was impossible to +know deterministically whether Matplotlib would modify the x or y limits. The +new ``'xlim'`` and ``'ylim'`` options behave like ``'datalim'`` except that +``'xlim'`` causes only the x limits to be adjusted, and ``'ylim'`` causes only +the y limits to be adjusted. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 7948c14de8e9..d6459afd7162 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -438,7 +438,8 @@ def __init__(self, fig, rect, ================ ========================================= Keyword Description ================ ========================================= - *adjustable* [ 'box' | 'datalim' | 'box-forced'] + *adjustable* [ 'box' | 'datalim' | 'xlim' | 'ylim' | \ + 'box-forced' ] *alpha* float: the alpha transparency (can be None) *anchor* [ 'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W' ] @@ -1271,6 +1272,8 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): ============ ===================================== 'box' change physical size of axes 'datalim' change xlim or ylim + 'xlim' change xlim + 'ylim' change ylim 'box-forced' same as 'box', but axes can be shared ============ ===================================== @@ -1307,9 +1310,9 @@ def get_adjustable(self): def set_adjustable(self, adjustable): """ - ACCEPTS: [ 'box' | 'datalim' | 'box-forced'] + ACCEPTS: [ 'box' | 'datalim' | 'xlim' | 'ylim' | 'box-forced'] """ - if adjustable in ('box', 'datalim', 'box-forced'): + if adjustable in ('box', 'datalim', 'xlim', 'ylim', 'box-forced'): if self in self._shared_x_axes or self in self._shared_y_axes: if adjustable == 'box': raise ValueError( @@ -1482,11 +1485,27 @@ def apply_aspect(self, position=None): self not in self._shared_x_axes) changey = (self in self._shared_x_axes and self not in self._shared_y_axes) + if changex and changey: - warnings.warn("adjustable='datalim' cannot work with shared " - "x and y axes") + warnings.warn("adjustable='{0}' cannot work with shared " + "x and y axes".format(self._adjustable)) + return + + if self._adjustable == 'xlim' and changey: + warnings.warn("adjustable='xlim' cannot work with shared " + "x axes".format(self._adjustable)) + return + + if self._adjustable == 'ylim' and changex: + warnings.warn("adjustable='ylim' cannot work with shared " + "y axes".format(self._adjustable)) return - if changex: + + if self._adjustable == 'xlim': + adjust_y = False + elif self._adjustable == 'ylim': + adjust_y = True + elif changex: adjust_y = False else: if xmarg > xm and ymarg > ym: @@ -1495,6 +1514,7 @@ def apply_aspect(self, position=None): else: adjy = y_expander > 0 adjust_y = changey or adjy # (Ymarg > xmarg) + if adjust_y: yc = 0.5 * (ymin + ymax) y0 = yc - Ysize / 2.0 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 86b6f2a3ddc2..9303c494cb94 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5149,3 +5149,95 @@ def test_twinx_knows_limits(): ax2.plot([0, 0.5], [1, 2]) assert((xtwin.viewLim.intervalx == ax2.viewLim.intervalx).all()) + + +def test_adjustable_limits(): + + # Test the 'datalim', 'xlim', and 'ylim' options + + image = np.ones((5, 5)) + + # First, we test adjustable='datalim'. Here, whether xlim or ylim are + # changed is up to Matplotlib. + + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_axes([0, 0, 1, 1], aspect='equal') + ax.imshow(image, origin='lower') + + # Since the axes has a landscape aspect ratio, the image should fit + # vertically and have white padding horizontally: + ax.set_adjustable('datalim') + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [-1.75, 5.75]) + assert_allclose(ax.get_ylim(), [-0.5, 4.5]) + + # Because of the way Matplotlib computes the aspect internally, it turns + # out that in this scenario ylim is the adjustable, so even if we change + # ylim, xlim will stay the same and ylim will get adjusted. This could be + # improved in future by checking in set_xlim and set_ylim whether + # adjustable='datalim' and try and make sure this value is respected. + ax.set_ylim(4, 5) + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [-1.75, 5.75]) + assert_allclose(ax.get_ylim(), [2, 7]) + + # Similarly, if xlim is changed, the values are not necessarily respected + # and in fact ylim is the one that stays constant. This behavior is the + # reason for adding explicit adjustable='xlim' and adjustable='ylim' + # options. + ax.set_xlim(1, 4) + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [-1.25, 6.25]) + assert_allclose(ax.get_ylim(), [2, 7]) + + # We now test adjustable='xlim', which should behave in a much more + # predictable way. + + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_axes([0, 0, 1, 1], aspect='equal') + ax.imshow(image, origin='lower') + + ax.set_adjustable('xlim') + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [-1.75, 5.75]) + assert_allclose(ax.get_ylim(), [-0.5, 4.5]) + + # Changing ylim results in ylim changing predictably and xlim getting + # adjusted + ax.set_ylim(4, 6) + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [0.5, 3.5]) + assert_allclose(ax.get_ylim(), [4, 6]) + + # Changing xlim results in xlim adjusting itself and ylim staying the same + ax.set_xlim(2, 4) + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [1.5, 4.5]) + assert_allclose(ax.get_ylim(), [4, 6]) + + # Finally we test adjustable='ylim', which should behave similarly to + # 'xlim' + + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_axes([0, 0, 1, 1], aspect='equal') + ax.imshow(image, origin='lower') + + # In the case where ylim is the adjustable, the image will fill the axes + # horizontally. + ax.set_adjustable('ylim') + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [-0.5, 4.5]) + assert_allclose(ax.get_ylim(), [1 / 3., 11 / 3.]) + + # Changing xlim results in xlim changing predictably and ylim getting + # adjusted + ax.set_xlim(4, 6) + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [4, 6]) + assert_allclose(ax.get_ylim(), [4 / 3., 8 / 3.]) + + # Changing ylim results in ylim adjusting itself and xlim staying the same + ax.set_ylim(2, 4) + ax.apply_aspect() + assert_allclose(ax.get_xlim(), [4, 6]) + assert_allclose(ax.get_ylim(), [7 / 3., 11 / 3.]) 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