Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 87c742b

Browse filesBrowse files
committed
Fix axes aspect for non-linear, non-log, possibly mixed-scale axes.
The main change is to make Axes.get_data_ratio take axes scales into account. This is a breaking change in get_data_ratio, but also the most reasonable way I could think of to implement the feature while also supporting third-party Axes subclasses that override this method (given that it is explicitly documented as being overridable for this purpose). (Compare, for example, with a patch that also deprecates get_data_ratio and moves the whole computation to apply_aspect; now what do we do with third-party overrides?) Also move the adjustable="datalim"-part of the implementation of apply_aspect down one indentation block for symmetry with adjustable="box". The change in test_log_scale_image is because we can't rely on aspect=1 not being implemented for semilog plots anymore...
1 parent 9f1c730 commit 87c742b
Copy full SHA for 87c742b

File tree

Expand file treeCollapse file tree

4 files changed

+73
-74
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+73
-74
lines changed
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
API changes
2+
```````````
3+
4+
``Axes.get_data_ratio`` now takes the axes scale into account (linear, log,
5+
logit, etc.) before computing the y-to-x ratio. This change allows fixed
6+
aspects to be applied to any combination of x and y scales.
7+
8+
``Axes.get_data_ratio_log`` is deprecated.

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+35-69Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,20 +1396,21 @@ def set_anchor(self, anchor, share=False):
13961396

13971397
def get_data_ratio(self):
13981398
"""
1399-
Return the aspect ratio of the raw data.
1399+
Return the aspect ratio of the scaled data.
14001400
14011401
Notes
14021402
-----
14031403
This method is intended to be overridden by new projection types.
14041404
"""
1405-
xmin, xmax = self.get_xbound()
1406-
ymin, ymax = self.get_ybound()
1407-
1408-
xsize = max(abs(xmax - xmin), 1e-30)
1409-
ysize = max(abs(ymax - ymin), 1e-30)
1410-
1405+
trf_xmin, trf_xmax = map(
1406+
self.xaxis.get_transform().transform, self.get_xbound())
1407+
trf_ymin, trf_ymax = map(
1408+
self.yaxis.get_transform().transform, self.get_ybound())
1409+
xsize = max(abs(trf_xmax - trf_xmin), 1e-30)
1410+
ysize = max(abs(trf_ymax - trf_ymin), 1e-30)
14111411
return ysize / xsize
14121412

1413+
@cbook.deprecated("3.2")
14131414
def get_data_ratio_log(self):
14141415
"""
14151416
Return the aspect ratio of the raw data in log scale.
@@ -1455,99 +1456,70 @@ def apply_aspect(self, position=None):
14551456

14561457
aspect = self.get_aspect()
14571458

1458-
if self.name != 'polar':
1459-
xscale, yscale = self.get_xscale(), self.get_yscale()
1460-
if xscale == "linear" and yscale == "linear":
1461-
aspect_scale_mode = "linear"
1462-
elif xscale == "log" and yscale == "log":
1463-
aspect_scale_mode = "log"
1464-
elif ((xscale == "linear" and yscale == "log") or
1465-
(xscale == "log" and yscale == "linear")):
1466-
if aspect != "auto":
1467-
cbook._warn_external(
1468-
'aspect is not supported for Axes with xscale=%s, '
1469-
'yscale=%s' % (xscale, yscale))
1470-
aspect = "auto"
1471-
else: # some custom projections have their own scales.
1472-
pass
1473-
else:
1474-
aspect_scale_mode = "linear"
1475-
14761459
if aspect == 'auto':
14771460
self._set_position(position, which='active')
14781461
return
14791462

14801463
if aspect == 'equal':
1481-
A = 1
1482-
else:
1483-
A = aspect
1464+
aspect = 1
1465+
1466+
fig_width, fig_height = self.get_figure().get_size_inches()
1467+
fig_aspect = fig_height / fig_width
14841468

1485-
figW, figH = self.get_figure().get_size_inches()
1486-
fig_aspect = figH / figW
14871469
if self._adjustable == 'box':
14881470
if self in self._twinned_axes:
1489-
raise RuntimeError("Adjustable 'box' is not allowed in a"
1490-
" twinned Axes. Use 'datalim' instead.")
1491-
if aspect_scale_mode == "log":
1492-
box_aspect = A * self.get_data_ratio_log()
1493-
else:
1494-
box_aspect = A * self.get_data_ratio()
1471+
raise RuntimeError("Adjustable 'box' is not allowed in a "
1472+
"twinned Axes; use 'datalim' instead")
1473+
box_aspect = aspect * self.get_data_ratio()
14951474
pb = position.frozen()
14961475
pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
14971476
self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
14981477
return
14991478

1500-
# reset active to original in case it had been changed
1501-
# by prior use of 'box'
1502-
self._set_position(position, which='active')
1503-
1504-
xmin, xmax = self.get_xbound()
1505-
ymin, ymax = self.get_ybound()
1479+
# self._adjustable == 'datalim'
15061480

1507-
if aspect_scale_mode == "log":
1508-
xmin, xmax = math.log10(xmin), math.log10(xmax)
1509-
ymin, ymax = math.log10(ymin), math.log10(ymax)
1481+
# reset active to original in case it had been changed by prior use
1482+
# of 'box'
1483+
self._set_position(position, which='active')
15101484

1485+
x_trf = self.xaxis.get_transform()
1486+
y_trf = self.yaxis.get_transform()
1487+
xmin, xmax = map(x_trf.transform, self.get_xbound())
1488+
ymin, ymax = map(y_trf.transform, self.get_ybound())
15111489
xsize = max(abs(xmax - xmin), 1e-30)
15121490
ysize = max(abs(ymax - ymin), 1e-30)
15131491

15141492
l, b, w, h = position.bounds
15151493
box_aspect = fig_aspect * (h / w)
1516-
data_ratio = box_aspect / A
1494+
data_ratio = box_aspect / aspect
15171495

1518-
y_expander = (data_ratio * xsize / ysize - 1.0)
1496+
y_expander = data_ratio * xsize / ysize - 1
15191497
# If y_expander > 0, the dy/dx viewLim ratio needs to increase
15201498
if abs(y_expander) < 0.005:
15211499
return
15221500

1523-
if aspect_scale_mode == "log":
1524-
dL = self.dataLim
1525-
dL_width = math.log10(dL.x1) - math.log10(dL.x0)
1526-
dL_height = math.log10(dL.y1) - math.log10(dL.y0)
1527-
xr = 1.05 * dL_width
1528-
yr = 1.05 * dL_height
1529-
else:
1530-
dL = self.dataLim
1531-
xr = 1.05 * dL.width
1532-
yr = 1.05 * dL.height
1501+
dL = self.dataLim
1502+
x0, x1 = map(x_trf.inverted().transform, dL.intervalx)
1503+
y0, y1 = map(y_trf.inverted().transform, dL.intervaly)
1504+
xr = 1.05 * (x1 - x0)
1505+
yr = 1.05 * (y1 - y0)
15331506

15341507
xmarg = xsize - xr
15351508
ymarg = ysize - yr
15361509
Ysize = data_ratio * xsize
15371510
Xsize = ysize / data_ratio
15381511
Xmarg = Xsize - xr
15391512
Ymarg = Ysize - yr
1540-
# Setting these targets to, e.g., 0.05*xr does not seem to
1541-
# help.
1513+
# Setting these targets to, e.g., 0.05*xr does not seem to help.
15421514
xm = 0
15431515
ym = 0
15441516

15451517
shared_x = self in self._shared_x_axes
15461518
shared_y = self in self._shared_y_axes
15471519
# Not sure whether we need this check:
15481520
if shared_x and shared_y:
1549-
raise RuntimeError("adjustable='datalim' is not allowed when both"
1550-
" axes are shared.")
1521+
raise RuntimeError("adjustable='datalim' is not allowed when both "
1522+
"axes are shared")
15511523

15521524
# If y is shared, then we are only allowed to change x, etc.
15531525
if shared_y:
@@ -1564,18 +1536,12 @@ def apply_aspect(self, position=None):
15641536
yc = 0.5 * (ymin + ymax)
15651537
y0 = yc - Ysize / 2.0
15661538
y1 = yc + Ysize / 2.0
1567-
if aspect_scale_mode == "log":
1568-
self.set_ybound((10. ** y0, 10. ** y1))
1569-
else:
1570-
self.set_ybound((y0, y1))
1539+
self.set_ybound(*map(y_trf.inverted().transform, (y0, y1)))
15711540
else:
15721541
xc = 0.5 * (xmin + xmax)
15731542
x0 = xc - Xsize / 2.0
15741543
x1 = xc + Xsize / 2.0
1575-
if aspect_scale_mode == "log":
1576-
self.set_xbound((10. ** x0, 10. ** x1))
1577-
else:
1578-
self.set_xbound((x0, x1))
1544+
self.set_xbound(*map(x_trf.inverted().transform, (x0, x1)))
15791545

15801546
def axis(self, *args, emit=True, **kwargs):
15811547
"""

‎lib/matplotlib/tests/test_axes.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_axes.py
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6495,3 +6495,29 @@ def test_set_ticks_inverted():
64956495
ax.invert_xaxis()
64966496
ax.set_xticks([.3, .7])
64976497
assert ax.get_xlim() == (1, 0)
6498+
6499+
6500+
def test_aspect_nonlinear_adjustable_box():
6501+
fig = plt.figure(figsize=(10, 10)) # Square.
6502+
6503+
ax = fig.add_subplot()
6504+
ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
6505+
ax.set(xscale="log", xlim=(1, 10),
6506+
yscale="logit", ylim=(1/11, 1/1001),
6507+
aspect=1, adjustable="box")
6508+
ax.margins(0)
6509+
pos = fig.transFigure.transform_bbox(ax.get_position())
6510+
assert pos.height / pos.width == pytest.approx(2)
6511+
6512+
6513+
def test_aspect_nonlinear_adjustable_datalim():
6514+
fig = plt.figure(figsize=(10, 10)) # Square.
6515+
6516+
ax = fig.add_axes([.1, .1, .8, .8]) # Square.
6517+
ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
6518+
ax.set(xscale="log", xlim=(1, 10),
6519+
yscale="logit", ylim=(1/11, 1/1001),
6520+
aspect=1, adjustable="datalim")
6521+
ax.margins(0)
6522+
ax.apply_aspect()
6523+
assert ax.get_xlim() == pytest.approx(np.array([1/10, 10]) * np.sqrt(10))

‎lib/matplotlib/tests/test_image.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_image.py
+4-5Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -686,15 +686,14 @@ def test_load_from_url():
686686

687687

688688
@image_comparison(['log_scale_image'], remove_text=True)
689-
# The recwarn fixture captures a warning in image_comparison.
690-
def test_log_scale_image(recwarn):
689+
def test_log_scale_image():
691690
Z = np.zeros((10, 10))
692691
Z[::2] = 1
693692

694693
fig, ax = plt.subplots()
695-
ax.imshow(Z, extent=[1, 100, 1, 100], cmap='viridis',
696-
vmax=1, vmin=-1)
697-
ax.set_yscale('log')
694+
ax.imshow(Z, extent=[1, 100, 1, 100], cmap='viridis', vmax=1, vmin=-1,
695+
aspect='auto')
696+
ax.set(yscale='log')
698697

699698

700699
@image_comparison(['rotate_image'], remove_text=True)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.