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

Skip to content

Commit e44c8cc

Browse files
committed
Add nonsingular to the locator base class, and use it in set_*lim too.
Currently, some Locator subclasses define a nonsingular() method, which is used by autoscale_view() in case the autoscaling would return a degenerate interval, to expand the view limits; autoscale_view() falls back on a default in case the nonsingular() method does not exist. Move that default to the Locator base class. Also use that nonsingular() method when a degenerate interval is passed to set_xlim/set_ylim/etc., instead of always hardcoding the same linear expansion. (Semantically the correct place for this nonsingular() method is probably on the scale class rather than on the locator...) The changes to test_ticker are due to the fact that the default expansion is now by 5%, so there's no offset text anymore in the left=right=123 case (as it gets expanded to 116.85, 129.15).
1 parent d31d102 commit e44c8cc

File tree

6 files changed

+48
-61
lines changed

6 files changed

+48
-61
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Changes in handling of degenerate bounds passed to `set_xlim`
2+
`````````````````````````````````````````````````````````````
3+
4+
When bounds passed to `set_xlim` (`set_xlim`, etc.) are degenerate (i.e. the
5+
lower and upper value are equal), the method used to "expand" the bounds now
6+
matches the expansion behavior of autoscaling when the plot contains a single
7+
x-value, and should in particular produce nicer limits for non-linear scales.

lib/matplotlib/axes/_base.py

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2423,13 +2423,7 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval,
24232423
bb = mtransforms.BboxBase.union(dl)
24242424
x0, x1 = getattr(bb, interval)
24252425
locator = axis.get_major_locator()
2426-
try:
2427-
# e.g., DateLocator has its own nonsingular()
2428-
x0, x1 = locator.nonsingular(x0, x1)
2429-
except AttributeError:
2430-
# Default nonsingular for, e.g., MaxNLocator
2431-
x0, x1 = mtransforms.nonsingular(
2432-
x0, x1, increasing=False, expander=0.05)
2426+
x0, x1 = locator.nonsingular(x0, x1)
24332427

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

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

3208-
if left == right:
3209-
cbook._warn_external(
3210-
('Attempting to set identical left==right results\n'
3211-
'in singular transformations; automatically expanding.\n'
3212-
'left=%s, right=%s') % (left, right))
3213-
left, right = mtransforms.nonsingular(left, right, increasing=False)
3214-
32153202
if self.get_xscale() == 'log':
32163203
if left <= 0:
32173204
cbook._warn_external(
@@ -3225,7 +3212,11 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False,
32253212
'log-scaled axis.\n'
32263213
'Invalid limit will be ignored.')
32273214
right = old_right
3228-
3215+
if left == right:
3216+
cbook._warn_external(
3217+
f"Attempting to set identical left == right == {left} results "
3218+
f"in singular transformations; automatically expanding.")
3219+
left, right = self.xaxis.get_major_locator().nonsingular(left, right)
32293220
left, right = self.xaxis.limit_range_for_scale(left, right)
32303221

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

3595-
if bottom == top:
3596-
cbook._warn_external(
3597-
('Attempting to set identical bottom==top results\n'
3598-
'in singular transformations; automatically expanding.\n'
3599-
'bottom=%s, top=%s') % (bottom, top))
3600-
3601-
bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
3602-
36033586
if self.get_yscale() == 'log':
36043587
if bottom <= 0:
36053588
cbook._warn_external(
@@ -3613,6 +3596,12 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
36133596
'log-scaled axis.\n'
36143597
'Invalid limit will be ignored.')
36153598
top = old_top
3599+
if bottom == top:
3600+
cbook._warn_external(
3601+
f"Attempting to set identical bottom == top == {bottom} "
3602+
f"results in singular transformations; automatically "
3603+
f"expanding.")
3604+
bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top)
36163605
bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
36173606

36183607
self.viewLim.intervaly = (bottom, top)

lib/matplotlib/tests/test_dates.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ def test_too_many_date_ticks():
156156
with pytest.warns(UserWarning) as rec:
157157
ax.set_xlim((t0, tf), auto=True)
158158
assert len(rec) == 1
159-
assert 'Attempting to set identical left==right' in str(rec[0].message)
159+
assert \
160+
'Attempting to set identical left == right' in str(rec[0].message)
160161
ax.plot([], [])
161162
ax.xaxis.set_major_locator(mdates.DayLocator())
162163
with pytest.raises(RuntimeError):

lib/matplotlib/tests/test_ticker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ class TestScalarFormatter(object):
302302
(1233999, 1234001, 1234000),
303303
(-1234001, -1233999, -1234000),
304304
(1, 1, 1),
305-
(123, 123, 120),
305+
(123, 123, 0),
306306
# Test cases courtesy of @WeatherGod
307307
(.4538, .4578, .45),
308308
(3789.12, 3783.1, 3780),

lib/matplotlib/ticker.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,9 +1411,9 @@ class Locator(TickHelper):
14111411
"""
14121412
Determine the tick locations;
14131413
1414-
Note, you should not use the same locator between different
1415-
:class:`~matplotlib.axis.Axis` because the locator stores references to
1416-
the Axis data and view limits
1414+
Note that the same locator should not be used across multiple
1415+
`~matplotlib.axis.Axis` because the locator stores references to the Axis
1416+
data and view limits.
14171417
"""
14181418

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

1466+
def nonsingular(self, v0, v1):
1467+
"""Modify the endpoints of a range as needed to avoid singularities."""
1468+
return mtransforms.nonsingular(v0, v1, increasing=False, expander=.05)
1469+
14661470
def view_limits(self, vmin, vmax):
14671471
"""
1468-
select a scale for the range from vmin to vmax
1472+
Select a scale for the range from vmin to vmax.
14691473
1470-
Normally this method is overridden by subclasses to
1471-
change locator behaviour.
1474+
Subclasses should override this method to change locator behaviour.
14721475
"""
14731476
return mtransforms.nonsingular(vmin, vmax)
14741477

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -536,11 +536,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
536536
self._shared_x_axes.clean()
537537
x0, x1 = self.xy_dataLim.intervalx
538538
xlocator = self.xaxis.get_major_locator()
539-
try:
540-
x0, x1 = xlocator.nonsingular(x0, x1)
541-
except AttributeError:
542-
x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False,
543-
expander=0.05)
539+
x0, x1 = xlocator.nonsingular(x0, x1)
544540
if self._xmargin > 0:
545541
delta = (x1 - x0) * self._xmargin
546542
x0 -= delta
@@ -553,11 +549,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
553549
self._shared_y_axes.clean()
554550
y0, y1 = self.xy_dataLim.intervaly
555551
ylocator = self.yaxis.get_major_locator()
556-
try:
557-
y0, y1 = ylocator.nonsingular(y0, y1)
558-
except AttributeError:
559-
y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False,
560-
expander=0.05)
552+
y0, y1 = ylocator.nonsingular(y0, y1)
561553
if self._ymargin > 0:
562554
delta = (y1 - y0) * self._ymargin
563555
y0 -= delta
@@ -570,11 +562,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True,
570562
self._shared_z_axes.clean()
571563
z0, z1 = self.zz_dataLim.intervalx
572564
zlocator = self.zaxis.get_major_locator()
573-
try:
574-
z0, z1 = zlocator.nonsingular(z0, z1)
575-
except AttributeError:
576-
z0, z1 = mtransforms.nonsingular(z0, z1, increasing=False,
577-
expander=0.05)
565+
z0, z1 = zlocator.nonsingular(z0, z1)
578566
if self._zmargin > 0:
579567
delta = (z1 - z0) * self._zmargin
580568
z0 -= delta
@@ -633,10 +621,9 @@ def set_xlim3d(self, left=None, right=None, emit=True, auto=False,
633621

634622
if left == right:
635623
cbook._warn_external(
636-
('Attempting to set identical left==right results\n'
637-
'in singular transformations; automatically expanding.\n'
638-
'left=%s, right=%s') % (left, right))
639-
left, right = mtransforms.nonsingular(left, right, increasing=False)
624+
f"Attempting to set identical left == right == {left} results "
625+
f"in singular transformations; automatically expanding.")
626+
left, right = self.xaxis.get_major_locator().nonsingular(left, right)
640627
left, right = self.xaxis.limit_range_for_scale(left, right)
641628
self.xy_viewLim.intervalx = (left, right)
642629

@@ -689,12 +676,12 @@ def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False,
689676
if top is None:
690677
top = old_top
691678

692-
if top == bottom:
679+
if bottom == top:
693680
cbook._warn_external(
694-
('Attempting to set identical bottom==top results\n'
695-
'in singular transformations; automatically expanding.\n'
696-
'bottom=%s, top=%s') % (bottom, top))
697-
bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
681+
f"Attempting to set identical bottom == top == {bottom} "
682+
f"results in singular transformations; automatically "
683+
f"expanding.")
684+
bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top)
698685
bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
699686
self.xy_viewLim.intervaly = (bottom, top)
700687

@@ -747,12 +734,12 @@ def set_zlim3d(self, bottom=None, top=None, emit=True, auto=False,
747734
if top is None:
748735
top = old_top
749736

750-
if top == bottom:
737+
if bottom == top:
751738
cbook._warn_external(
752-
('Attempting to set identical bottom==top results\n'
753-
'in singular transformations; automatically expanding.\n'
754-
'bottom=%s, top=%s') % (bottom, top))
755-
bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
739+
f"Attempting to set identical bottom == top == {bottom} "
740+
f"results in singular transformations; automatically "
741+
f"expanding.")
742+
bottom, top = self.zaxis.get_major_locator().nonsingular(bottom, top)
756743
bottom, top = self.zaxis.limit_range_for_scale(bottom, top)
757744
self.zz_viewLim.intervalx = (bottom, top)
758745

0 commit comments

Comments
 (0)