@@ -1661,13 +1661,12 @@ def view_limits(self, vmin, vmax):
16611661 return mtransforms .nonsingular (vmin , vmax )
16621662
16631663
1664+ @cbook .deprecated ("3.0" )
16641665def 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" )
16711670class 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
17121711class 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+
17691810class 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