8000 More precise choice of axes limits. · matplotlib/matplotlib@4fd8a4e · GitHub
[go: up one dir, main page]

Skip to content

Commit 4fd8a4e

Browse files
committed
More precise choice of 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). Support for the (unused and deprecated-in-comment) "trim" keyword argument has been dropped as the way ticks are picked has changed. Many test images have changed! Implementation notes: - A bug in numpy's implementation of divmod (numpy/numpy#6127) is worked around. - The implementation of scale_range has also been cleaned. See #5767, #5738.
1 parent c708029 commit 4fd8a4e

23 files changed

+3486
-3590
lines changed
Binary file not shown.
Loading

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

Lines changed: 194 additions & 242 deletions
Loading
Loading
Binary file not shown.

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

Lines changed: 506 additions & 506 deletions
Loading
Binary file not shown.
Loading

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

Lines changed: 107 additions & 127 deletions
Loading
Loading
Binary file not shown.
Loading

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

Lines changed: 30 additions & 42 deletions
Loading

lib/matplotlib/tests/test_axes.py

Lines changed: 2 additions & 3 deletions
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

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,23 @@
161161
from matplotlib import rcParams
162162
from matplotlib import cbook
163163
from matplotlib import transforms as mtransforms
164+
from matplotlib.cbook import mplDeprecation
164165

165166
import warnings
166167

167168
if six.PY3:
168169
long = int
169170

170171

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+
171181
def _mathdefault(s):
172182
"""
173183
For backward compatibility, in classic mode we display
@@ -1323,23 +1333,12 @@ def view_limits(self, dmin, dmax):
13231333

13241334

13251335
def scale_range(vmin, vmax, n=1, threshold=100):
1326-
dv = abs(vmax - vmin)
1327-
if dv == 0: # maxabsv == 0 is a special case of this.
1328-
return 1.0, 0.0
1329-
# Note: this should never occur because
1330-
# vmin, vma 10000 x should have been checked by nonsingular(),
1331-
# and spread apart if necessary.
1332-
meanv = 0.5 * (vmax + vmin)
1333-
if abs(meanv) / dv < threshold:
1334-
offset = 0
1335-
elif meanv > 0:
1336-
ex = divmod(math.log10(meanv), 1)[0]
1337-
offset = 10 ** ex
1338-
else:
1339-
ex = divmod(math.log10(-meanv), 1)[0]
1340-
offset = -10 ** ex
1341-
ex = divmod(math.log10(dv / n), 1)[0]
1342-
scale = 10 ** ex
1336+
dv = abs(vmax - vmin) # > 0 as nonsingular is called before.
1337+
meanv = (vmax + vmin) / 2
1338+
offset = (math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv)
1339+
if abs(meanv) / dv >= threshold
1340+
else 0)
1341+
scale = 10 ** (math.log10(dv / n) // 1)
13431342
return scale, offset
13441343

13451344

@@ -1349,7 +1348,6 @@ class MaxNLocator(Locator):
13491348
"""
13501349
default_params = dict(nbins=10,
13511350
steps=None,
1352-
trim=True,
13531351
integer=False,
13541352
symmetric=False,
13551353
prune=None)
@@ -1385,9 +1383,6 @@ def __init__(self, *args, **kwargs):
13851383
will be removed. If prune==None, no ticks will be removed.
13861384
13871385
"""
1388-
# I left "trim" out; it defaults to True, and it is not
1389-
# clear that there is any use case for False, so we may
1390-
# want to remove that kwarg. EF 2010/04/18
13911386
if args:
13921387
kwargs['nbins'] = args[0]
13931388
if len(args) > 1:
@@ -1403,7 +1398,8 @@ def set_params(self, **kwargs):
14031398
if self._nbins != 'auto':
14041399
self._nbins = int(self._nbins)
14051400
if 'trim' in kwargs:
1406-
self._trim = kwargs['trim']
1401+
warnings.warn("The 'trim' keyword has no effect anymore",
1402+
mplDeprecation)
14071403
if 'integer' in kwargs:
14081404
self._integer = kwargs['integer']
14091405
if 'symmetric' in kwargs:
@@ -1428,7 +1424,7 @@ def set_params(self, **kwargs):
14281424
if self._integer:
14291425
self._steps = [n for n in self._steps if divmod(n, 1)[1] < 0.001]
14301426

1431-
def bin_boundaries(self, vmin, vmax):
1427+
def _raw_ticks(self, vmin, vmax):
14321428
nbins = self._nbins
14331429
if nbins == 'auto':
14341430
nbins = self.axis.get_tick_space()
@@ -1446,23 +1442,26 @@ def bin_boundaries(self, vmin, vmax):
14461442
if step < scaled_raw_step:
14471443
continue
14481444
step *= scale
1449-
best_vmin = step * divmod(vmin, step)[0]
1445+
best_vmin = vmin // step * step
14501446
best_vmax = best_vmin + step * nbins
1451-
if (best_vmax >= vmax):
1447+
if best_vmax >= vmax:
14521448
break
1453-
if self._trim:
1454-
extra_bins = int(divmod((best_vmax - vmax), step)[0])
1455-
nbins -= extra_bins
1456-
return (np.arange(nbins + 1) * step + best_vmin + offset)
1449+
1450+
# More than nbins may be required, e.g. vmin, vmax = -4.1, 4.1 gives
1451+
# nbins=9 but 10 bins are actually required after rounding. So we just
1452+
# create the bins that span the range we need instead.
1453+
low = round(Base(step).le(vmin - best_vmin) / step)
1454+
high = round(Base(step).ge(vmax - best_vmin) / step)
1455+
return np.arange(low, high + 1) * step + best_vmin + offset
14571456

14581457
def __call__(self):
14591458
vmin, vmax = self.axis.get_view_interval()
14601459
return self.tick_values(vmin, vmax)
14611460

14621461
def tick_values(self, vmin, vmax):
1463-
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=1e-13,
1464-
tiny=1e-14)
1465-
locs = self.bin_boundaries(vmin, vmax)
1462+
vmin, vmax = mtransforms.nonsingular(
1463+
vmin, vmax, expander=1e-13, tiny=1e-14)
1464+
locs = self._raw_ticks(vmin, vmax)
14661465
prune = self._prune
14671466
if prune == 'lower':
14681467
locs = locs[1:]
@@ -1479,11 +1478,11 @@ def view_limits(self, dmin, dmax):
14791478
dmin = -maxabs
14801479
dmax = maxabs
14811480

1482-
dmin, dmax = mtransforms.nonsingular(dmin, dmax, expander=1e-12,
1483-
tiny=1.e-13)
1481+
dmin, dmax = mtransforms.nonsingular(
1482+
dmin, dmax, expander=1e-12, tiny=1e-13)
14841483

14851484
if rcParams['axes.autolimit_mode'] == 'round_numbers':
1486-
return np.take(self.bin_boundaries(dmin, dmax), [0, -1])
1485+
return self._raw_ticks(dmin, dmax)[[0, -1]]
14871486
else:
14881487
return dmin, dmax
14891488

Binary file not shown.
Loading

0 commit comments

Comments
 (0)
0