Skip to content

Commit 1918f00

Browse files
committed
ENH: add two new modes for 'adjustable' option
The new modes are 'xlim' and 'ylim' - these behave like the 'datalim' option except that only the x or y limits are adjusted, in a deterministic way.
1 parent da711e1 commit 1918f00

File tree

2 files changed

+115
-6
lines changed

2 files changed

+115
-6
lines changed

lib/matplotlib/axes/_base.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ def __init__(self, fig, rect,
438438
================ =========================================
439439
Keyword Description
440440
================ =========================================
441-
*adjustable* [ 'box' | 'datalim' | 'box-forced']
441+
*adjustable* [ 'box' | 'datalim' | 'xlim' | 'ylim' | 'box-forced' ]
442442
*alpha* float: the alpha transparency (can be None)
443443
*anchor* [ 'C', 'SW', 'S', 'SE', 'E', 'NE', 'N',
444444
'NW', 'W' ]
@@ -1271,6 +1271,8 @@ def set_aspect(self, aspect, adjustable=None, anchor=None):
12711271
============ =====================================
12721272
'box' change physical size of axes
12731273
'datalim' change xlim or ylim
1274+
'xlim' change xlim
1275+
'ylim' change ylim
12741276
'box-forced' same as 'box', but axes can be shared
12751277
============ =====================================
12761278
@@ -1307,9 +1309,9 @@ def get_adjustable(self):
13071309

13081310
def set_adjustable(self, adjustable):
13091311
"""
1310-
ACCEPTS: [ 'box' | 'datalim' | 'box-forced']
1312+
ACCEPTS: [ 'box' | 'datalim' | 'xlim' | 'ylim' | 'box-forced']
13111313
"""
1312-
if adjustable in ('box', 'datalim', 'box-forced'):
1314+
if adjustable in ('box', 'datalim', 'xlim', 'ylim', 'box-forced'):
13131315
if self in self._shared_x_axes or self in self._shared_y_axes:
13141316
if adjustable == 'box':
13151317
raise ValueError(
@@ -1482,11 +1484,27 @@ def apply_aspect(self, position=None):
14821484
self not in self._shared_x_axes)
14831485
changey = (self in self._shared_x_axes and
14841486
self not in self._shared_y_axes)
1487+
14851488
if changex and changey:
1486-
warnings.warn("adjustable='datalim' cannot work with shared "
1487-
"x and y axes")
1489+
warnings.warn("adjustable='{0}' cannot work with shared "
1490+
"x and y axes".format(self._adjustable))
1491+
return
1492+
1493+
if self._adjustable == 'xlim' and changey:
1494+
warnings.warn("adjustable='xlim' cannot work with shared "
1495+
"x axes".format(self._adjustable))
1496+
return
1497+
1498+
if self._adjustable == 'ylim' and changex:
1499+
warnings.warn("adjustable='ylim' cannot work with shared "
1500+
"y axes".format(self._adjustable))
14881501
return
1489-
if changex:
1502+
1503+
if self._adjustable == 'xlim':
1504+
adjust_y = False
1505+
elif self._adjustable == 'ylim':
1506+
adjust_y = True
1507+
elif changex:
14901508
adjust_y = False
14911509
else:
14921510
if xmarg > xm and ymarg > ym:
@@ -1495,6 +1513,7 @@ def apply_aspect(self, position=None):
14951513
else:
14961514
adjy = y_expander > 0
14971515
adjust_y = changey or adjy # (Ymarg > xmarg)
1516+
14981517
if adjust_y:
14991518
yc = 0.5 * (ymin + ymax)
15001519
y0 = yc - Ysize / 2.0

lib/matplotlib/tests/test_axes.py

+90
Original file line numberDiff line numberDiff line change
@@ -5149,3 +5149,93 @@ def test_twinx_knows_limits():
51495149
ax2.plot([0, 0.5], [1, 2])
51505150

51515151
assert((xtwin.viewLim.intervalx == ax2.viewLim.intervalx).all())
5152+
5153+
5154+
def test_adjustable_limits():
5155+
5156+
# Test the 'datalim', 'xlim', and 'ylim' options
5157+
5158+
image = np.ones((5, 5))
5159+
5160+
# First, we test adjustable='datalim'. Here, whether xlim or ylim are
5161+
# changed is up to Matplotlib.
5162+
5163+
fig = plt.figure(figsize=(6, 4))
5164+
ax = fig.add_axes([0, 0, 1, 1], aspect='equal')
5165+
ax.imshow(image, origin='lower')
5166+
5167+
# Since the axes has a landscape aspect ratio, the image should fit
5168+
# vertically and have white padding horizontally:
5169+
ax.set_adjustable('datalim')
5170+
ax.apply_aspect()
5171+
assert_allclose(ax.get_xlim(), [-1.75, 5.75])
5172+
assert_allclose(ax.get_ylim(), [-0.5, 4.5])
5173+
5174+
# Because of the way Matplotlib computes the aspect internally, it turns out
5175+
# that in this scenario ylim is the adjustable, so even if we change ylim,
5176+
# xlim will stay the same and ylim will get adjusted. This could be improved
5177+
# in future by checking in set_xlim and set_ylim whether adjustable='datalim'
5178+
# and try and make sure this value is respected.
5179+
ax.set_ylim(4, 5)
5180+
ax.apply_aspect()
5181+
assert_allclose(ax.get_xlim(), [-1.75, 5.75])
5182+
assert_allclose(ax.get_ylim(), [2, 7])
5183+
5184+
# Similarly, if xlim is changed, the values are not necessarily respected
5185+
# and in fact ylim is the one that stays constant. This behavior is the
5186+
# reason for adding explicit adjustable='xlim' and adjustable='ylim' options.
5187+
ax.set_xlim(1, 4)
5188+
ax.apply_aspect()
5189+
assert_allclose(ax.get_xlim(), [-1.25, 6.25])
5190+
assert_allclose(ax.get_ylim(), [2, 7])
5191+
5192+
# We now test adjustable='xlim', which should behave in a much more
5193+
# predictable way.
5194+
5195+
fig = plt.figure(figsize=(6, 4))
5196+
ax = fig.add_axes([0, 0, 1, 1], aspect='equal')
5197+
ax.imshow(image, origin='lower')
5198+
5199+
ax.set_adjustable('xlim')
5200+
ax.apply_aspect()
5201+
assert_allclose(ax.get_xlim(), [-1.75, 5.75])
5202+
assert_allclose(ax.get_ylim(), [-0.5, 4.5])
5203+
5204+
# Changing ylim results in ylim changing predictably and xlim getting
5205+
# adjusted
5206+
ax.set_ylim(4, 6)
5207+
ax.apply_aspect()
5208+
assert_allclose(ax.get_xlim(), [0.5, 3.5])
5209+
assert_allclose(ax.get_ylim(), [4, 6])
5210+
5211+
# Changing xlim results in xlim adjusting itself and ylim staying the same
5212+
ax.set_xlim(2, 4)
5213+
ax.apply_aspect()
5214+
assert_allclose(ax.get_xlim(), [1.5, 4.5])
5215+
assert_allclose(ax.get_ylim(), [4, 6])
5216+
5217+
# Finally we test adjustable='ylim', which should behave similarly to 'xlim'
5218+
5219+
fig = plt.figure(figsize=(6, 4))
5220+
ax = fig.add_axes([0, 0, 1, 1], aspect='equal')
5221+
ax.imshow(image, origin='lower')
5222+
5223+
# In the case where ylim is the adjustable, the image will fill the axes
5224+
# horizontally.
5225+
ax.set_adjustable('ylim')
5226+
ax.apply_aspect()
5227+
assert_allclose(ax.get_xlim(), [-0.5, 4.5])
5228+
assert_allclose(ax.get_ylim(), [1 / 3., 11 / 3.])
5229+
5230+
# Changing xlim results in xlim changing predictably and ylim getting
5231+
# adjusted
5232+
ax.set_xlim(4, 6)
5233+
ax.apply_aspect()
5234+
assert_allclose(ax.get_xlim(), [4, 6])
5235+
assert_allclose(ax.get_ylim(), [4 / 3., 8 / 3.])
5236+
5237+
# Changing ylim results in ylim adjusting itself and xlim staying the same
5238+
ax.set_ylim(2, 4)
5239+
ax.apply_aspect()
5240+
assert_allclose(ax.get_xlim(), [4, 6])
5241+
assert_allclose(ax.get_ylim(), [7 / 3., 11 / 3.])

0 commit comments

Comments
 (0)