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

Skip to content

Correctly calculate margins on log scales #4592

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

Closed
wants to merge 6 commits into from
Closed
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
6 changes: 5 additions & 1 deletion lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3774,7 +3774,11 @@ def scatter(self, x, y, s=20, c=None, marker='o', cmap=None, norm=None,
self.set_ymargin(0.05)

self.add_collection(collection)
self.autoscale_view()
try:
self.autoscale_view()
except NotImplementedError:
# This happens if the axes are not separable.
pass

return collection

Expand Down
74 changes: 61 additions & 13 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1994,7 +1994,32 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
else:
_tight = self._tight = bool(tight)

if scalex and self._autoscaleXon:
if not self._autoscaleXon:
scalex = False
if not self._autoscaleYon:
scaley = False

if not scalex and not scaley:
return

# In order to implement margins in a fully general way that respects
# all possible (separable) coordinate transformations, we need to do
# the autoscaling in three steps:
# 1. Compute the limits required to include the data points in this
# axis and any shared axes, without accounting for margins.
# 2. If there are nonzero margins, update self.viewLim, so that
# self.transLimits will compute the transformation required to map
# the new view limits to (0,1). Use self.transLimits.inverted()
# to calculate the view limits that correspond
# to (-self._xmargin, -self._ymargin)
# and (1 + self._xmargin, 1 + self._ymargin).
# 3. Finally, let the axis locators expand the view limits if
# necessary to align them with tick marks.
# It's marginally more efficient to do each step for both axes, before
# proceeding to the next step.

# Step 1
if scalex:
xshared = self._shared_x_axes.get_siblings(self)
dl = [ax.dataLim for ax in xshared]
# ignore non-finite data limits if good limits exist
Expand All @@ -2012,15 +2037,8 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
# Default nonsingular for, e.g., MaxNLocator
x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False,
expander=0.05)
if self._xmargin > 0:
delta = (x1 - x0) * self._xmargin
x0 -= delta
x1 += delta
if not _tight:
x0, x1 = xlocator.view_limits(x0, x1)
self.set_xbound(x0, x1)

if scaley and self._autoscaleYon:
if scaley:
yshared = self._shared_y_axes.get_siblings(self)
dl = [ax.dataLim for ax in yshared]
# ignore non-finite data limits if good limits exist
Expand All @@ -2036,10 +2054,40 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
except AttributeError:
y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False,
expander=0.05)
if self._ymargin > 0:
delta = (y1 - y0) * self._ymargin
y0 -= delta
y1 += delta

# Step 2
if self._xmargin > 0 or self._ymargin > 0:
# Handling the x and y transformations at the same time allows us
# to save a couple calls to invTrans.transform().
try:
transLimits = self.transLimits
except AttributeError:
raise NotImplementedError(
'margins are not implemented for non-separable axes')
else:
# Update bounds after checking for transLimits because if the
# axes are not separable, we should allow the caller to catch
# the NotImplementedError without there being any lasting
# changes to the view.
if scalex:
self.set_xbound(x0, x1)
if scaley:
self.set_ybound(y0, y1)
invTrans = (self.transScale + transLimits).inverted()
assert(x0 < x1 and y0 < y1)
p0 = invTrans.transform((-self._xmargin, -self._ymargin))
p1 = invTrans.transform((1 + self._xmargin, 1 + self._ymargin))
if scalex:
x0, x1 = p0[0], p1[0]
if scaley:
y0, y1 = p0[1], p1[1]

# Step 3
if scalex:
if not _tight:
x0, x1 = xlocator.view_limits(x0, x1)
self.set_xbound(x0, x1)
if scaley:
if not _tight:
y0, y1 = ylocator.view_limits(y0, y1)
self.set_ybound(y0, y1)
Expand Down
42 changes: 42 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3645,6 +3645,48 @@ def test_margins():
assert_equal(ax3.margins(), (1, 0.5))


@cleanup
def test_view_limits_with_margins():
'''Tests that autoscale_view correctly calculates the new view limits
with a nonzero margin, for various kinds of plots.

The test uses a linear plot, a logarithmic plot, a symlog plot,
and a polar plot as an example of a nonlinear coordinate transformation.'''

fig1, ax1 = plt.subplots(1, 1)
data = [0.1, 1]
ax1.plot(data, data)
ax1.margins(0.5)
np.testing.assert_array_almost_equal(ax1.get_xbound(), (-0.35, 1.45))
np.testing.assert_array_almost_equal(ax1.get_ybound(), (-0.35, 1.45))

fig2, ax2 = plt.subplots(1, 1)
data = [0.1, 1]
ax2.loglog(data, data)
ax2.margins(0.5)
np.testing.assert_array_almost_equal(ax2.get_xbound(), (0.1 / np.sqrt(10), np.sqrt(10)))
np.testing.assert_array_almost_equal(ax2.get_ybound(), (0.1 / np.sqrt(10), np.sqrt(10)))

fig3, ax3 = plt.subplots(1, 1)
DATAMAX = 10
data = np.linspace(-DATAMAX, DATAMAX, 101)
ax3.plot(data, data)
BASE = 10
ax3.set_xscale('symlog', linthreshx=1, linscalex=1, basex=BASE)
ax3.set_yscale('symlog', linthreshy=1, linscaley=1, basey=BASE)
MARGIN = 0.5
ax3.margins(MARGIN)
# To understand the following line, see the implementation of
# matplotlib.scale.SymmetricalLogTransform.transform_non_affine()
BOUND = DATAMAX * BASE ** (2 * (1 + BASE / (BASE - 1)) * MARGIN)
np.testing.assert_array_almost_equal(ax3.get_xbound(), (-BOUND, BOUND))
np.testing.assert_array_almost_equal(ax3.get_ybound(), (-BOUND, BOUND))

ax4 = plt.subplot(111, polar=True)
data = np.linspace(0, 2*np.pi, 51)
ax4.plot(data, data)
assert_raises(NotImplementedError, ax4.margins, 0.5)

@cleanup
def test_length_one_hist():
fig, ax = plt.subplots()
Expand Down