@@ -1661,13 +1661,12 @@ def view_limits(self, vmin, vmax):
1661
1661
return mtransforms .nonsingular (vmin , vmax )
1662
1662
1663
1663
1664
+ @cbook .deprecated ("3.0" )
1664
1665
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
1669
1667
1670
1668
1669
+ @cbook .deprecated ("3.0" )
1671
1670
class Base (object ):
1672
1671
'this solution has some hacks to deal with floating point inaccuracies'
1673
1672
def __init__ (self , base ):
@@ -1711,17 +1710,16 @@ def get_base(self):
1711
1710
1712
1711
class MultipleLocator (Locator ):
1713
1712
"""
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.
1716
1714
"""
1717
1715
1718
1716
def __init__ (self , base = 1.0 ):
1719
- self ._base = Base (base )
1717
+ self ._edge = _Edge_integer (base , 0 )
1720
1718
1721
1719
def set_params (self , base ):
1722
1720
"""Set parameters within this locator."""
1723
1721
if base is not None :
1724
- self ._base = base
1722
+ self ._edge = _Edge_integer ( base , 0 )
1725
1723
1726
1724
def __call__ (self ):
1727
1725
'Return the locations of the ticks'
@@ -1731,20 +1729,20 @@ def __call__(self):
1731
1729
def tick_values (self , vmin , vmax ):
1732
1730
if vmax < vmin :
1733
1731
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
1738
1736
return self .raise_if_exceeds (locs )
1739
1737
1740
1738
def view_limits (self , dmin , dmax ):
1741
1739
"""
1742
1740
Set the view limits to the nearest multiples of base that
1743
- contain the data
1741
+ contain the data.
1744
1742
"""
1745
1743
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
1748
1746
if vmin == vmax :
1749
1747
vmin -= 1
1750
1748
vmax += 1
@@ -1766,6 +1764,49 @@ def scale_range(vmin, vmax, n=1, threshold=100):
1766
1764
return scale , offset
1767
1765
1768
1766
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
+
1769
1810
class MaxNLocator (Locator ):
1770
1811
"""
1771
1812
Select no more than N intervals at nice locations.
@@ -1880,6 +1921,12 @@ def set_params(self, **kwargs):
1880
1921
self ._integer = kwargs ['integer' ]
1881
1922
1882
1923
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
+ """
1883
1930
if self ._nbins == 'auto' :
1884
1931
if self .axis is not None :
1885
1932
nbins = np .clip (self .axis .get_tick_space (),
@@ -1892,7 +1939,7 @@ def _raw_ticks(self, vmin, vmax):
1892
1939
scale , offset = scale_range (vmin , vmax , nbins )
1893
1940
_vmin = vmin - offset
1894
1941
_vmax = vmax - offset
1895
- raw_step = (vmax - vmin ) / nbins
1942
+ raw_step = (_vmax - _vmin ) / nbins
1896
1943
steps = self ._extended_steps * scale
1897
1944
if self ._integer :
1898
1945
# For steps > 1, keep only integer values.
@@ -1911,20 +1958,27 @@ def _raw_ticks(self, vmin, vmax):
1911
1958
break
1912
1959
1913
1960
# 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
+
1916
1964
if (self ._integer and
1917
1965
np .floor (_vmax ) - np .ceil (_vmin ) >= self ._min_n_ticks - 1 ):
1918
1966
step = max (1 , step )
1919
1967
best_vmin = (_vmin // step ) * step
1920
1968
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 ()
1925
1979
if nticks >= self ._min_n_ticks :
1926
1980
break
1927
- return ticks
1981
+ return ticks + offset
1928
1982
1929
1983
def __call__ (self ):
1930
1984
vmin , vmax = self .axis .get_view_interval ()
0 commit comments