Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4728e70

Browse files
authored
Merge pull request #14727 from anntzer/nonlinearaspect
Fix axes aspect for non-linear, non-log, possibly mixed-scale axes.
2 parents 277c042 + 87c742b commit 4728e70

File tree

4 files changed

+73
-74
lines changed

4 files changed

+73
-74
lines changed
Lines 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

Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,20 +1404,21 @@ def set_anchor(self, anchor, share=False):
14041404

14051405
def get_data_ratio(self):
14061406
"""
1407-
Return the aspect ratio of the raw data.
1407+
Return the aspect ratio of the scaled data.
14081408
14091409
Notes
14101410
-----
14111411
This method is intended to be overridden by new projection types.
14121412
"""
1413-
xmin, xmax = self.get_xbound()
1414-
ymin, ymax = self.get_ybound()
1415-
1416-
xsize = max(abs(xmax - xmin), 1e-30)
1417-
ysize = max(abs(ymax - ymin), 1e-30)
1418-
1413+
trf_xmin, trf_xmax = map(
1414+
self.xaxis.get_transform().transform, self.get_xbound())
1415+
trf_ymin, trf_ymax = map(
1416+
self.yaxis.get_transform().transform, self.get_ybound())
1417+
xsize = max(abs(trf_xmax - trf_xmin), 1e-30)
1418+
ysize = max(abs(trf_ymax - trf_ymin), 1e-30)
14191419
return ysize / xsize
14201420

1421+
@cbook.deprecated("3.2")
14211422
def get_data_ratio_log(self):
14221423
"""
14231424
Return the aspect ratio of the raw data in log scale.
@@ -1463,99 +1464,70 @@ def apply_aspect(self, position=None):
14631464

14641465
aspect = self.get_aspect()
14651466

1466-
if self.name != 'polar':
1467-
xscale, yscale = self.get_xscale(), self.get_yscale()
1468-
if xscale == "linear" and yscale == "linear":
1469-
aspect_scale_mode = "linear"
1470-
elif xscale == "log" and yscale == "log":
1471-
aspect_scale_mode = "log"
1472-
elif ((xscale == "linear" and yscale == "log") or
1473-
(xscale == "log" and yscale == "linear")):
1474-
if aspect != "auto":
1475-
cbook._warn_external(
1476-
'aspect is not supported for Axes with xscale=%s, '
1477-
'yscale=%s' % (xscale, yscale))
1478-
aspect = "auto"
1479-
else: # some custom projections have their own scales.
1480-
pass
1481-
else:
1482-
aspect_scale_mode = "linear"
1483-
14841467
if aspect == 'auto':
14851468
self._set_position(position, which='active')
14861469
return
14871470

14881471
if aspect == 'equal':
1489-
A = 1
1490-
else:
1491-
A = aspect
1472+
aspect = 1
1473+
1474+
fig_width, fig_height = self.get_figure().get_size_inches()
1475+
fig_aspect = fig_height / fig_width
14921476

1493-
figW, figH = self.get_figure().get_size_inches()
1494-
fig_aspect = figH / figW
14951477
if self._adjustable == 'box':
14961478
if self in self._twinned_axes:
1497-
raise RuntimeError("Adjustable 'box' is not allowed in a"
1498-
" twinned Axes. Use 'datalim' instead.")
1499-
if aspect_scale_mode == "log":
1500-
box_aspect = A * self.get_data_ratio_log()
1501-
else:
1502-
box_aspect = A * self.get_data_ratio()
1479+
raise RuntimeError("Adjustable 'box' is not allowed in a "
1480+
"twinned Axes; use 'datalim' instead")
1481+
box_aspect = aspect * self.get_data_ratio()
15031482
pb = position.frozen()
15041483
pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
15051484
self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
15061485
return
15071486

1508-
# reset active to original in case it had been changed
1509-
# by prior use of 'box'
1510-
self._set_position(position, which='active')
1511-
1512-
xmin, xmax = self.get_xbound()
1513-
ymin, ymax = self.get_ybound()
1487+
# self._adjustable == 'datalim'
15141488

1515-
if aspect_scale_mode == "log":
1516-
xmin, xmax = math.log10(xmin), math.log10(xmax)
1517-
ymin, ymax = math.log10(ymin), math.log10(ymax)
1489+
# reset active to original in case it had been changed by prior use
1490+
# of 'box'
1491+
self._set_position(position, which='active')
15181492

1493+
x_trf = self.xaxis.get_transform()
1494+
y_trf = self.yaxis.get_transform()
1495+
xmin, xmax = map(x_trf.transform, self.get_xbound())
1496+
ymin, ymax = map(y_trf.transform, self.get_ybound())
15191497
xsize = max(abs(xmax - xmin), 1e-30)
15201498
ysize = max(abs(ymax - ymin), 1e-30)
15211499

15221500
l, b, w, h = position.bounds
15231501
box_aspect = fig_aspect * (h / w)
1524-
data_ratio = box_aspect / A
1502+
data_ratio = box_aspect / aspect
15251503

1526-
y_expander = (data_ratio * xsize / ysize - 1.0)
1504+
y_expander = data_ratio * xsize / ysize - 1
15271505
# If y_expander > 0, the dy/dx viewLim ratio needs to increase
15281506
if abs(y_expander) < 0.005:
15291507
return
15301508

1531-
if aspect_scale_mode == "log":
1532-
dL = self.dataLim
1533-
dL_width = math.log10(dL.x1) - math.log10(dL.x0)
1534-
dL_height = math.log10(dL.y1) - math.log10(dL.y0)
1535-
xr = 1.05 * dL_width
1536-
yr = 1.05 * dL_height
1537-
else:
1538-
dL = self.dataLim
1539-
xr = 1.05 * dL.width
1540-
yr = 1.05 * dL.height
1509+
dL = self.dataLim
1510+
x0, x1 = map(x_trf.inverted().transform, dL.intervalx)
1511+
y0, y1 = map(y_trf.inverted().transform, dL.intervaly)
1512+
xr = 1.05 * (x1 - x0)
1513+
yr = 1.05 * (y1 - y0)
15411514

15421515
xmarg = xsize - xr
15431516
ymarg = ysize - yr
15441517
Ysize = data_ratio * xsize
15451518
Xsize = ysize / data_ratio
15461519
Xmarg = Xsize - xr
15471520
Ymarg = Ysize - yr
1548-
# Setting these targets to, e.g., 0.05*xr does not seem to
1549-
# help.
1521+
# Setting these targets to, e.g., 0.05*xr does not seem to help.
15501522
xm = 0
15511523
ym = 0
15521524

15531525
shared_x = self in self._shared_x_axes
15541526
shared_y = self in self._shared_y_axes
15551527
# Not sure whether we need this check:
15561528
if shared_x and shared_y:
1557-
raise RuntimeError("adjustable='datalim' is not allowed when both"
1558-
" axes are shared.")
1529+
raise RuntimeError("adjustable='datalim' is not allowed when both "
1530+
"axes are shared")
15591531

15601532
# If y is shared, then we are only allowed to change x, etc.
15611533
if shared_y:
@@ -1572,18 +1544,12 @@ def apply_aspect(self, position=None):
15721544
yc = 0.5 * (ymin + ymax)
15731545
y0 = yc - Ysize / 2.0
15741546
y1 = yc + Ysize / 2.0
1575-
if aspect_scale_mode == "log":
1576-
self.set_ybound((10. ** y0, 10. ** y1))
1577-
else:
1578-
self.set_ybound((y0, y1))
1547+
self.set_ybound(*map(y_trf.inverted().transform, (y0, y1)))
15791548
else:
15801549
xc = 0.5 * (xmin + xmax)
15811550
x0 = xc - Xsize / 2.0
15821551
x1 = xc + Xsize / 2.0
1583-
if aspect_scale_mode == "log":
1584-
self.set_xbound((10. ** x0, 10. ** x1))
1585-
else:
1586-
self.set_xbound((x0, x1))
1552+
self.set_xbound(*map(x_trf.inverted().transform, (x0, x1)))
15871553

15881554
def axis(self, *args, emit=True, **kwargs):
15891555
"""

lib/matplotlib/tests/test_axes.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6501,3 +6501,29 @@ def test_set_ticks_inverted():
65016501
ax.invert_xaxis()
65026502
ax.set_xticks([.3, .7])
65036503
assert ax.get_xlim() == (1, 0)
6504+
6505+
6506+
def test_aspect_nonlinear_adjustable_box():
6507+
fig = plt.figure(figsize=(10, 10)) # Square.
6508+
6509+
ax = fig.add_subplot()
6510+
ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
6511+
ax.set(xscale="log", xlim=(1, 10),
6512+
yscale="logit", ylim=(1/11, 1/1001),
6513+
aspect=1, adjustable="box")
6514+
ax.margins(0)
6515+
pos = fig.transFigure.transform_bbox(ax.get_position())
6516+
assert pos.height / pos.width == pytest.approx(2)
6517+
6518+
6519+
def test_aspect_nonlinear_adjustable_datalim():
6520+
fig = plt.figure(figsize=(10, 10)) # Square.
6521+
6522+
ax = fig.add_axes([.1, .1, .8, .8]) # Square.
6523+
ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
6524+
ax.set(xscale="log", xlim=(1, 10),
6525+
yscale="logit", ylim=(1/11, 1/1001),
6526+
aspect=1, adjustable="datalim")
6527+
ax.margins(0)
6528+
ax.apply_aspect()
6529+
assert ax.get_xlim() == pytest.approx(np.array([1/10, 10]) * np.sqrt(10))

lib/matplotlib/tests/test_image.py

Lines 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)