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

Skip to content

Commit 5a4b8d8

Browse files
authored
Merge pull request #11915 from meeseeksmachine/auto-backport-of-pr-11591-on-v3.0.x
Backport PR #11591 on branch v3.0.x
2 parents eb81611 + 1ecc985 commit 5a4b8d8

File tree

3 files changed

+84
-28
lines changed

3 files changed

+84
-28
lines changed

lib/matplotlib/dates.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,7 +1384,7 @@ def __init__(self, base=1, month=1, day=1, tz=None):
13841384
(default jan 1).
13851385
"""
13861386
DateLocator.__init__(self, tz)
1387-
self.base = ticker.Base(base)
1387+
self.base = ticker._Edge_integer(base, 0)
13881388
self.replaced = {'month': month,
13891389
'day': day,
13901390
'hour': 0,
@@ -1403,15 +1403,15 @@ def __call__(self):
14031403
return self.tick_values(dmin, dmax)
14041404

14051405
def tick_values(self, vmin, vmax):
1406-
ymin = self.base.le(vmin.year)
1407-
ymax = self.base.ge(vmax.year)
1406+
ymin = self.base.le(vmin.year) * self.base.step
1407+
ymax = self.base.ge(vmax.year) * self.base.step
14081408

14091409
ticks = [vmin.replace(year=ymin, **self.replaced)]
14101410
while True:
14111411
dt = ticks[-1]
14121412
if dt.year >= ymax:
14131413
return date2num(ticks)
1414-
year = dt.year + self.base.get_base()
1414+
year = dt.year + self.base.step
14151415
ticks.append(dt.replace(year=year, **self.replaced))
14161416

14171417
def autoscale(self):

lib/matplotlib/tests/test_ticker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class TestMaxNLocator(object):
1414
(20, 100, np.array([20., 40., 60., 80., 100.])),
1515
(0.001, 0.0001, np.array([0., 0.0002, 0.0004, 0.0006, 0.0008, 0.001])),
1616
(-1e15, 1e15, np.array([-1.0e+15, -5.0e+14, 0e+00, 5e+14, 1.0e+15])),
17+
(0, 0.85e-50, np.arange(6) * 2e-51),
18+
(-0.85e-50, 0, np.arange(-5, 1) * 2e-51),
1719
]
1820

1921
integer_data = [
@@ -64,7 +66,7 @@ def test_set_params(self):
6466
"""
6567
mult = mticker.MultipleLocator(base=0.7)
6668
mult.set_params(base=1.7)
67-
assert mult._base == 1.7
69+
assert mult._edge.step == 1.7
6870

6971

7072
class TestAutoMinorLocator(object):

lib/matplotlib/ticker.py

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,13 +1661,12 @@ def view_limits(self, vmin, vmax):
16611661
return mtransforms.nonsingular(vmin, vmax)
16621662

16631663

1664+
@cbook.deprecated("3.0")
16641665
def closeto(x, y):
1665-
if abs(x - y) < 1e-10:
1666-
return True
1667-
else:
1668-
return False
1666+
return abs(x - y) < 1e-10
16691667

16701668

1669+
@cbook.deprecated("3.0")
16711670
class Base(object):
16721671
'this solution has some hacks to deal with floating point inaccuracies'
16731672
def __init__(self, base):
@@ -1711,17 +1710,16 @@ def get_base(self):
17111710

17121711
class MultipleLocator(Locator):
17131712
"""
1714-
Set a tick on every integer that is multiple of base in the
1715-
view interval
1713+
Set a tick on each integer multiple of a base within the view interval.
17161714
"""
17171715

17181716
def __init__(self, base=1.0):
1719-
self._base = Base(base)
1717+
self._edge = _Edge_integer(base, 0)
17201718

17211719
def set_params(self, base):
17221720
"""Set parameters within this locator."""
17231721
if base is not None:
1724-
self._base = base
1722+
self._edge = _Edge_integer(base, 0)
17251723

17261724
def __call__(self):
17271725
'Return the locations of the ticks'
@@ -1731,20 +1729,20 @@ def __call__(self):
17311729
def tick_values(self, vmin, vmax):
17321730
if vmax < vmin:
17331731
vmin, vmax = vmax, vmin
1734-
vmin = self._base.ge(vmin)
1735-
base = self._base.get_base()
1736-
n = (vmax - vmin + 0.001 * base) // base
1737-
locs = vmin - base + np.arange(n + 3) * base
1732+
step = self._edge.step
1733+
vmin = self._edge.ge(vmin) * step
1734+
n = (vmax - vmin + 0.001 * step) // step
1735+
locs = vmin - step + np.arange(n + 3) * step
17381736
return self.raise_if_exceeds(locs)
17391737

17401738
def view_limits(self, dmin, dmax):
17411739
"""
17421740
Set the view limits to the nearest multiples of base that
1743-
contain the data
1741+
contain the data.
17441742
"""
17451743
if rcParams['axes.autolimit_mode'] == 'round_numbers':
1746-
vmin = self._base.le(dmin)
1747-
vmax = self._base.ge(dmax)
1744+
vmin = self._edge.le(dmin) * self._edge.step
1745+
vmax = self._base.ge(dmax) * self._edge.step
17481746
if vmin == vmax:
17491747
vmin -= 1
17501748
vmax += 1
@@ -1766,6 +1764,49 @@ def scale_range(vmin, vmax, n=1, threshold=100):
17661764
return scale, offset
17671765

17681766

1767+
class _Edge_integer:
1768+
"""
1769+
Helper for MaxNLocator, MultipleLocator, etc.
1770+
1771+
Take floating point precision limitations into account when calculating
1772+
tick locations as integer multiples of a step.
1773+
"""
1774+
def __init__(self, step, offset):
1775+
"""
1776+
*step* is a positive floating-point interval between ticks.
1777+
*offset* is the offset subtracted from the data limits
1778+
prior to calculating tick locations.
1779+
"""
1780+
if step <= 0:
1781+
raise ValueError("'step' must be positive")
1782+
self.step = step
1783+
self._offset = abs(offset)
1784+
1785+
def closeto(self, ms, edge):
1786+
# Allow more slop when the offset is large compared to the step.
1787+
if self._offset > 0:
1788+
digits = np.log10(self._offset / self.step)
1789+
tol = max(1e-10, 10 ** (digits - 12))
1790+
tol = min(0.4999, tol)
1791+
else:
1792+
tol = 1e-10
1793+
return abs(ms - edge) < tol
1794+
1795+
def le(self, x):
1796+
'Return the largest n: n*step <= x.'
1797+
d, m = _divmod(x, self.step)
1798+
if self.closeto(m / self.step, 1):
1799+
return (d + 1)
1800+
return d
1801+
1802+
def ge(self, x):
1803+
'Return the smallest n: n*step >= x.'
1804+
d, m = _divmod(x, self.step)
1805+
if self.closeto(m / self.step, 0):
1806+
return d
1807+
return (d + 1)
1808+
1809+
17691810
class MaxNLocator(Locator):
17701811
"""
17711812
Select no more than N intervals at nice locations.
@@ -1880,6 +1921,12 @@ def set_params(self, **kwargs):
18801921
self._integer = kwargs['integer']
18811922

18821923
def _raw_ticks(self, vmin, vmax):
1924+
"""
1925+
Generate a list of tick locations including the range *vmin* to
1926+
*vmax*. In some applications, one or both of the end locations
1927+
will not be needed, in which case they are trimmed off
1928+
elsewhere.
1929+
"""
18831930
if self._nbins == 'auto':
18841931
if self.axis is not None:
18851932
nbins = np.clip(self.axis.get_tick_space(),
@@ -1892,7 +1939,7 @@ def _raw_ticks(self, vmin, vmax):
18921939
scale, offset = scale_range(vmin, vmax, nbins)
18931940
_vmin = vmin - offset
18941941
_vmax = vmax - offset
1895-
raw_step = (vmax - vmin) / nbins
1942+
raw_step = (_vmax - _vmin) / nbins
18961943
steps = self._extended_steps * scale
18971944
if self._integer:
18981945
# For steps > 1, keep only integer values.
@@ -1911,20 +1958,27 @@ def _raw_ticks(self, vmin, vmax):
19111958
break
19121959

19131960
# This is an upper limit; move to smaller steps if necessary.
1914-
for i in range(istep):
1915-
step = steps[istep - i]
1961+
for istep in reversed(range(istep + 1)):
1962+
step = steps[istep]
1963+
19161964
if (self._integer and
19171965
np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1):
19181966
step = max(1, step)
19191967
best_vmin = (_vmin // step) * step
19201968

1921-
low = np.round(Base(step).le(_vmin - best_vmin) / step)
1922-
high = np.round(Base(step).ge(_vmax - best_vmin) / step)
1923-
ticks = np.arange(low, high + 1) * step + best_vmin + offset
1924-
nticks = ((ticks <= vmax) & (ticks >= vmin)).sum()
1969+
# Find tick locations spanning the vmin-vmax range, taking into
1970+
# account degradation of precision when there is a large offset.
1971+
# The edge ticks beyond vmin and/or vmax are needed for the
1972+
# "round_numbers" autolimit mode.
1973+
edge = _Edge_integer(step, offset)
1974+
low = edge.le(_vmin - best_vmin)
1975+
high = edge.ge(_vmax - best_vmin)
1976+
ticks = np.arange(low, high + 1) * step + best_vmin
1977+
# Count only the ticks that will be displayed.
1978+
nticks = ((ticks <= _vmax) & (ticks >= _vmin)).sum()
19251979
if nticks >= self._min_n_ticks:
19261980
break
1927-
return ticks
1981+
return ticks + offset
19281982

19291983
def __call__(self):
19301984
vmin, vmax = self.axis.get_view_interval()

0 commit comments

Comments
 (0)