Skip to content

Commit d327e74

Browse files
committed
Fix floating point issues in axes limits.
plt.plot([-.1, .2]) used to pick (in round numbers mode) [-.1, .25] as ylims due to floating point inaccuracies; change it to pick [-.1, .2] (up to floating point inaccuracies). Note that this requires working around a bug in numpy's implementation of divmod (numpy/numpy#6127). Many test images have changed! See #5767. Also some more progress on #5738.
1 parent 892fc5f commit d327e74

23 files changed

+3479
-3566
lines changed
Binary file not shown.

lib/matplotlib/tests/baseline_images/test_axes/autoscale_tiny_range.svg

+194-242
Loading
Binary file not shown.

lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.svg

+506-506
Loading
Binary file not shown.

lib/matplotlib/tests/baseline_images/test_axes/formatter_large_small.svg

+107-127
Loading
Binary file not shown.

lib/matplotlib/tests/baseline_images/test_bbox_tight/bbox_inches_tight_raster.svg

+30-42
Loading

lib/matplotlib/tests/test_axes.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from matplotlib.testing.decorators import image_comparison, cleanup
2222
import matplotlib.pyplot as plt
2323
import matplotlib.markers as mmarkers
24-
from numpy.testing import assert_array_equal
24+
from numpy.testing import assert_allclose, assert_array_equal
2525
import warnings
2626
from matplotlib.cbook import IgnoredKeywordWarning
2727

@@ -3708,8 +3708,7 @@ def test_vline_limit():
37083708
ax.axvline(0.5)
37093709
ax.plot([-0.1, 0, 0.2, 0.1])
37103710
(ymin, ymax) = ax.get_ylim()
3711-
assert ymin == -0.1
3712-
assert ymax == 0.25
3711+
assert_allclose(ax.get_ylim(), (-.1, .2))
37133712

37143713

37153714
@cleanup

lib/matplotlib/ticker.py

+27-11
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@
169169
long = int
170170

171171

172+
# Work around numpy/numpy#6127.
173+
def divmod(x, y):
174+
if isinstance(x, np.generic):
175+
x = x.item()
176+
if isinstance(y, np.generic):
177+
y = y.item()
178+
return six.moves.builtins.divmod(x, y)
179+
180+
172181
def _mathdefault(s):
173182
"""
174183
For backward compatibility, in classic mode we display
@@ -1324,7 +1333,7 @@ def view_limits(self, dmin, dmax):
13241333

13251334

13261335
def scale_range(vmin, vmax, n=1, threshold=100):
1327-
dv = abs(vmax - vmin) # > 0 as nonsingular is called before.
1336+
dv = abs(vmax - vmin) # > 0 as nonsingular is called before.
13281337
meanv = (vmax + vmin) / 2
13291338
offset = (math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv)
13301339
if abs(meanv) / dv >= threshold
@@ -1415,7 +1424,7 @@ def set_params(self, **kwargs):
14151424
if self._integer:
14161425
self._steps = [n for n in self._steps if divmod(n, 1)[1] < 0.001]
14171426

1418-
def bin_boundaries(self, vmin, vmax):
1427+
def _raw_ticks(self, vmin, vmax):
14191428
nbins = self._nbins
14201429
if nbins == 'auto':
14211430
nbins = self.axis.get_tick_space()
@@ -1438,18 +1447,19 @@ def bin_boundaries(self, vmin, vmax):
14381447
if best_vmax >= vmax:
14391448
break
14401449

1441-
bounds = np.arange(nbins + 1) * step + best_vmin
1442-
bounds = bounds[(bounds >= vmin) & (bounds <= vmax)]
1443-
return bounds + offset
1450+
bounds = np.arange(nbins + 1) * step
1451+
return (bounds[(bounds >= Base(step).le(vmin - best_vmin)) &
1452+
(bounds <= Base(step).ge(vmax - best_vmin))] +
1453+
best_vmin + offset)
14441454

14451455
def __call__(self):
14461456
vmin, vmax = self.axis.get_view_interval()
14471457
return self.tick_values(vmin, vmax)
14481458

14491459
def tick_values(self, vmin, vmax):
1450-
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=1e-13,
1451-
tiny=1e-14)
1452-
locs = self.bin_boundaries(vmin, vmax)
1460+
vmin, vmax = mtransforms.nonsingular(
1461+
vmin, vmax, expander=1e-13, tiny=1e-14)
1462+
locs = self._raw_ticks(vmin, vmax)
14531463
prune = self._prune
14541464
if prune == 'lower':
14551465
locs = locs[1:]
@@ -1466,11 +1476,17 @@ def view_limits(self, dmin, dmax):
14661476
dmin = -maxabs
14671477
dmax = maxabs
14681478

1469-
dmin, dmax = mtransforms.nonsingular(dmin, dmax, expander=1e-12,
1470-
tiny=1.e-13)
1479+
dmin, dmax = mtransforms.nonsingular(
1480+
dmin, dmax, expander=1e-12, tiny=1e-13)
14711481

14721482
if rcParams['axes.autolimit_mode'] == 'round_numbers':
1473-
return np.take(self.bin_boundaries(dmin, dmax), [0, -1])
1483+
locs = self._raw_ticks(dmin, dmax)
1484+
step = locs[1] - locs[0]
1485+
low = (locs[0] if locs[0] < dmin or closeto(locs[0], dmin)
1486+
else locs[0] - step)
1487+
high = (locs[-1] if locs[-1] > dmax or closeto(locs[-1], dmax)
1488+
else locs[-1] + step)
1489+
return low, high
14741490
else:
14751491
return dmin, dmax
14761492

Binary file not shown.

0 commit comments

Comments
 (0)