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

Skip to content

Fix axes aspect for non-linear, non-log, possibly mixed-scale axes. #14727

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/api/next_api_changes/2019-07-09-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
API changes
```````````

``Axes.get_data_ratio`` now takes the axes scale into account (linear, log,
logit, etc.) before computing the y-to-x ratio. This change allows fixed
aspects to be applied to any combination of x and y scales.

``Axes.get_data_ratio_log`` is deprecated.
104 changes: 35 additions & 69 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1396,20 +1396,21 @@ def set_anchor(self, anchor, share=False):

def get_data_ratio(self):
"""
Return the aspect ratio of the raw data.
Return the aspect ratio of the scaled data.

Notes
-----
This method is intended to be overridden by new projection types.
"""
xmin, xmax = self.get_xbound()
ymin, ymax = self.get_ybound()

xsize = max(abs(xmax - xmin), 1e-30)
ysize = max(abs(ymax - ymin), 1e-30)

trf_xmin, trf_xmax = map(
self.xaxis.get_transform().transform, self.get_xbound())
trf_ymin, trf_ymax = map(
self.yaxis.get_transform().transform, self.get_ybound())
xsize = max(abs(trf_xmax - trf_xmin), 1e-30)
ysize = max(abs(trf_ymax - trf_ymin), 1e-30)
return ysize / xsize

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

aspect = self.get_aspect()

if self.name != 'polar':
xscale, yscale = self.get_xscale(), self.get_yscale()
if xscale == "linear" and yscale == "linear":
aspect_scale_mode = "linear"
elif xscale == "log" and yscale == "log":
aspect_scale_mode = "log"
elif ((xscale == "linear" and yscale == "log") or
(xscale == "log" and yscale == "linear")):
if aspect != "auto":
cbook._warn_external(
'aspect is not supported for Axes with xscale=%s, '
'yscale=%s' % (xscale, yscale))
aspect = "auto"
else: # some custom projections have their own scales.
pass
else:
aspect_scale_mode = "linear"

if aspect == 'auto':
self._set_position(position, which='active')
return

if aspect == 'equal':
A = 1
else:
A = aspect
aspect = 1

fig_width, fig_height = self.get_figure().get_size_inches()
fig_aspect = fig_height / fig_width

figW, figH = self.get_figure().get_size_inches()
fig_aspect = figH / figW
if self._adjustable == 'box':
if self in self._twinned_axes:
raise RuntimeError("Adjustable 'box' is not allowed in a"
" twinned Axes. Use 'datalim' instead.")
if aspect_scale_mode == "log":
box_aspect = A * self.get_data_ratio_log()
else:
box_aspect = A * self.get_data_ratio()
raise RuntimeError("Adjustable 'box' is not allowed in a "
"twinned Axes; use 'datalim' instead")
box_aspect = aspect * self.get_data_ratio()
pb = position.frozen()
pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
return

# reset active to original in case it had been changed
# by prior use of 'box'
self._set_position(position, which='active')

xmin, xmax = self.get_xbound()
ymin, ymax = self.get_ybound()
# self._adjustable == 'datalim'

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

x_trf = self.xaxis.get_transform()
y_trf = self.yaxis.get_transform()
xmin, xmax = map(x_trf.transform, self.get_xbound())
ymin, ymax = map(y_trf.transform, self.get_ybound())
xsize = max(abs(xmax - xmin), 1e-30)
ysize = max(abs(ymax - ymin), 1e-30)

l, b, w, h = position.bounds
box_aspect = fig_aspect * (h / w)
data_ratio = box_aspect / A
data_ratio = box_aspect / aspect

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

if aspect_scale_mode == "log":
dL = self.dataLim
dL_width = math.log10(dL.x1) - math.log10(dL.x0)
dL_height = math.log10(dL.y1) - math.log10(dL.y0)
xr = 1.05 * dL_width
yr = 1.05 * dL_height
else:
dL = self.dataLim
xr = 1.05 * dL.width
yr = 1.05 * dL.height
dL = self.dataLim
x0, x1 = map(x_trf.inverted().transform, dL.intervalx)
y0, y1 = map(y_trf.inverted().transform, dL.intervaly)
xr = 1.05 * (x1 - x0)
yr = 1.05 * (y1 - y0)

xmarg = xsize - xr
ymarg = ysize - yr
Ysize = data_ratio * xsize
Xsize = ysize / data_ratio
Xmarg = Xsize - xr
Ymarg = Ysize - yr
# Setting these targets to, e.g., 0.05*xr does not seem to
# help.
# Setting these targets to, e.g., 0.05*xr does not seem to help.
xm = 0
ym = 0

shared_x = self in self._shared_x_axes
shared_y = self in self._shared_y_axes
# Not sure whether we need this check:
if shared_x and shared_y:
raise RuntimeError("adjustable='datalim' is not allowed when both"
" axes are shared.")
raise RuntimeError("adjustable='datalim' is not allowed when both "
"axes are shared")

# If y is shared, then we are only allowed to change x, etc.
if shared_y:
Expand All @@ -1564,18 +1536,12 @@ def apply_aspect(self, position=None):
yc = 0.5 * (ymin + ymax)
y0 = yc - Ysize / 2.0
y1 = yc + Ysize / 2.0
if aspect_scale_mode == "log":
self.set_ybound((10. ** y0, 10. ** y1))
else:
self.set_ybound((y0, y1))
self.set_ybound(*map(y_trf.inverted().transform, (y0, y1)))
else:
xc = 0.5 * (xmin + xmax)
x0 = xc - Xsize / 2.0
x1 = xc + Xsize / 2.0
if aspect_scale_mode == "log":
self.set_xbound((10. ** x0, 10. ** x1))
else:
self.set_xbound((x0, x1))
self.set_xbound(*map(x_trf.inverted().transform, (x0, x1)))

def axis(self, *args, emit=True, **kwargs):
"""
Expand Down
26 changes: 26 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6495,3 +6495,29 @@ def test_set_ticks_inverted():
ax.invert_xaxis()
ax.set_xticks([.3, .7])
assert ax.get_xlim() == (1, 0)


def test_aspect_nonlinear_adjustable_box():
fig = plt.figure(figsize=(10, 10)) # Square.

ax = fig.add_subplot()
ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
ax.set(xscale="log", xlim=(1, 10),
yscale="logit", ylim=(1/11, 1/1001),
aspect=1, adjustable="box")
ax.margins(0)
pos = fig.transFigure.transform_bbox(ax.get_position())
assert pos.height / pos.width == pytest.approx(2)


def test_aspect_nonlinear_adjustable_datalim():
fig = plt.figure(figsize=(10, 10)) # Square.

ax = fig.add_axes([.1, .1, .8, .8]) # Square.
ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy.
ax.set(xscale="log", xlim=(1, 10),
yscale="logit", ylim=(1/11, 1/1001),
aspect=1, adjustable="datalim")
ax.margins(0)
ax.apply_aspect()
assert ax.get_xlim() == pytest.approx(np.array([1/10, 10]) * np.sqrt(10))
9 changes: 4 additions & 5 deletions lib/matplotlib/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,15 +686,14 @@ def test_load_from_url():


@image_comparison(['log_scale_image'], remove_text=True)
# The recwarn fixture captures a warning in image_comparison.
def test_log_scale_image(recwarn):
def test_log_scale_image():
Z = np.zeros((10, 10))
Z[::2] = 1

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


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