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

Skip to content

Add nonsingular to the locator base class, and use it in set_*lim too. #13409

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
Feb 11, 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
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/2019-02-12-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Changes in handling of degenerate bounds passed to `set_xlim`
`````````````````````````````````````````````````````````````

When bounds passed to `set_xlim` (`set_xlim`, etc.) are degenerate (i.e. the
lower and upper value are equal), the method used to "expand" the bounds now
matches the expansion behavior of autoscaling when the plot contains a single
x-value, and should in particular produce nicer limits for non-linear scales.
37 changes: 13 additions & 24 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2423,13 +2423,7 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval,
bb = mtransforms.BboxBase.union(dl)
x0, x1 = getattr(bb, interval)
locator = axis.get_major_locator()
try:
# e.g., DateLocator has its own nonsingular()
x0, x1 = locator.nonsingular(x0, x1)
except AttributeError:
# Default nonsingular for, e.g., MaxNLocator
x0, x1 = mtransforms.nonsingular(
x0, x1, increasing=False, expander=0.05)
x0, x1 = locator.nonsingular(x0, x1)

# Add the margin in figure space and then transform back, to handle
# non-linear scales.
Expand All @@ -2443,7 +2437,7 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval,
x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos)
x0t, x1t = transform.transform([x0, x1])

if (np.isfinite(x1t) and np.isfinite(x0t)):
if np.isfinite(x1t) and np.isfinite(x0t):
delta = (x1t - x0t) * margin
else:
# If at least one bound isn't finite, set margin to zero
Expand Down Expand Up @@ -3205,13 +3199,6 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False,
if right is None:
right = old_right

if left == right:
cbook._warn_external(
('Attempting to set identical left==right results\n'
'in singular transformations; automatically expanding.\n'
'left=%s, right=%s') % (left, right))
left, right = mtransforms.nonsingular(left, right, increasing=False)

if self.get_xscale() == 'log':
if left <= 0:
cbook._warn_external(
Expand All @@ -3225,7 +3212,11 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False,
'log-scaled axis.\n'
'Invalid limit will be ignored.')
right = old_right

if left == right:
cbook._warn_external(
f"Attempting to set identical left == right == {left} results "
f"in singular transformations; automatically expanding.")
left, right = self.xaxis.get_major_locator().nonsingular(left, right)
left, right = self.xaxis.limit_range_for_scale(left, right)

self.viewLim.intervalx = (left, right)
Expand Down Expand Up @@ -3592,14 +3583,6 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
if top is None:
top = old_top

if bottom == top:
cbook._warn_external(
('Attempting to set identical bottom==top results\n'
'in singular transformations; automatically expanding.\n'
'bottom=%s, top=%s') % (bottom, top))

bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)

if self.get_yscale() == 'log':
if bottom <= 0:
cbook._warn_external(
Expand All @@ -3613,6 +3596,12 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
'log-scaled axis.\n'
'Invalid limit will be ignored.')
top = old_top
if bottom == top:
cbook._warn_external(
f"Attempting to set identical bottom == top == {bottom} "
f"results in singular transformations; automatically "
f"expanding.")
bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top)
bottom, top = self.yaxis.limit_range_for_scale(bottom, top)

self.viewLim.intervaly = (bottom, top)
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/tests/test_dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ def test_too_many_date_ticks():
with pytest.warns(UserWarning) as rec:
ax.set_xlim((t0, tf), auto=True)
assert len(rec) == 1
assert 'Attempting to set identical left==right' in str(rec[0].message)
assert \
'Attempting to set identical left == right' in str(rec[0].message)
ax.plot([], [])
ax.xaxis.set_major_locator(mdates.DayLocator())
with pytest.raises(RuntimeError):
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/tests/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ class TestScalarFormatter(object):
(1233999, 1234001, 1234000),
(-1234001, -1233999, -1234000),
(1, 1, 1),
(123, 123, 120),
(123, 123, 0),
# Test cases courtesy of @WeatherGod
(.4538, .4578, .45),
(3789.12, 3783.1, 3780),
Expand Down
15 changes: 9 additions & 6 deletions lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1411,9 +1411,9 @@ class Locator(TickHelper):
"""
Determine the tick locations;

Note, you should not use the same locator between different
:class:`~matplotlib.axis.Axis` because the locator stores references to
the Axis data and view limits
Note that the same locator should not be used across multiple
`~matplotlib.axis.Axis` because the locator stores references to the Axis
data and view limits.
"""

# Some automatic tick locators can generate so many ticks they
Expand Down Expand Up @@ -1463,12 +1463,15 @@ def raise_if_exceeds(self, locs):
len(locs), locs[0], locs[-1]))
return locs

def nonsingular(self, v0, v1):
"""Modify the endpoints of a range as needed to avoid singularities."""
return mtransforms.nonsingular(v0, v1, increasing=False, expander=.05)

def view_limits(self, vmin, vmax):
"""
select a scale for the range from vmin to vmax
Select a scale for the range from vmin to vmax.

Normally this method is overridden by subclasses to
change locator behaviour.
Subclasses should override this method to change locator behaviour.
"""
return mtransforms.nonsingular(vmin, vmax)

Expand Down
45 changes: 16 additions & 29 deletions lib/mpl_toolkits/mplot3d/axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,11 +536,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
self._shared_x_axes.clean()
x0, x1 = self.xy_dataLim.intervalx
xlocator = self.xaxis.get_major_locator()
try:
x0, x1 = xlocator.nonsingular(x0, x1)
except AttributeError:
x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False,
expander=0.05)
x0, x1 = xlocator.nonsingular(x0, x1)
if self._xmargin > 0:
delta = (x1 - x0) * self._xmargin
x0 -= delta
Expand All @@ -553,11 +549,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
self._shared_y_axes.clean()
y0, y1 = self.xy_dataLim.intervaly
ylocator = self.yaxis.get_major_locator()
try:
y0, y1 = ylocator.nonsingular(y0, y1)
except AttributeError:
y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False,
expander=0.05)
y0, y1 = ylocator.nonsingular(y0, y1)
if self._ymargin > 0:
delta = (y1 - y0) * self._ymargin
y0 -= delta
Expand All @@ -570,11 +562,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
self._shared_z_axes.clean()
z0, z1 = self.zz_dataLim.intervalx
zlocator = self.zaxis.get_major_locator()
try:
z0, z1 = zlocator.nonsingular(z0, z1)
except AttributeError:
z0, z1 = mtransforms.nonsingular(z0, z1, increasing=False,
expander=0.05)
z0, z1 = zlocator.nonsingular(z0, z1)
if self._zmargin > 0:
delta = (z1 - z0) * self._zmargin
z0 -= delta
Expand Down Expand Up @@ -633,10 +621,9 @@ def set_xlim3d(self, left=None, right=None, emit=True, auto=False,

if left == right:
cbook._warn_external(
('Attempting to set identical left==right results\n'
'in singular transformations; automatically expanding.\n'
'left=%s, right=%s') % (left, right))
left, right = mtransforms.nonsingular(left, right, increasing=False)
f"Attempting to set identical left == right == {left} results "
f"in singular transformations; automatically expanding.")
left, right = self.xaxis.get_major_locator().nonsingular(left, right)
left, right = self.xaxis.limit_range_for_scale(left, right)
self.xy_viewLim.intervalx = (left, right)

Expand Down Expand Up @@ -689,12 +676,12 @@ def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False,
if top is None:
top = old_top

if top == bottom:
if bottom == top:
cbook._warn_external(
('Attempting to set identical bottom==top results\n'
'in singular transformations; automatically expanding.\n'
'bottom=%s, top=%s') % (bottom, top))
bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
f"Attempting to set identical bottom == top == {bottom} "
f"results in singular transformations; automatically "
f"expanding.")
bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top)
bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
self.xy_viewLim.intervaly = (bottom, top)

Expand Down Expand Up @@ -747,12 +734,12 @@ def set_zlim3d(self, bottom=None, top=None, emit=True, auto=False,
if top is None:
top = old_top

if top == bottom:
if bottom == top:
cbook._warn_external(
('Attempting to set identical bottom==top results\n'
'in singular transformations; automatically expanding.\n'
'bottom=%s, top=%s') % (bottom, top))
bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
f"Attempting to set identical bottom == top == {bottom} "
f"results in singular transformations; automatically "
f"expanding.")
bottom, top = self.zaxis.get_major_locator().nonsingular(bottom, top)
bottom, top = self.zaxis.limit_range_for_scale(bottom, top)
self.zz_viewLim.intervalx = (bottom, top)

Expand Down