Skip to content

Commit be0b3b7

Browse files
committed
API: make MaxNLocator trim out-of-view ticks before returning
1 parent ef04ad9 commit be0b3b7

12 files changed

+48
-16
lines changed

lib/matplotlib/colorbar.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,14 @@ def __init__(self, colorbar):
234234
self._colorbar = colorbar
235235
nbins = 'auto'
236236
steps = [1, 2, 2.5, 5, 10]
237-
ticker.MaxNLocator.__init__(self, nbins=nbins, steps=steps)
237+
ticker.MaxNLocator.__init__(self, nbins=nbins, steps=steps,
238+
trim_outside=True)
238239

239240
def tick_values(self, vmin, vmax):
240241
vmin = max(vmin, self._colorbar.norm.vmin)
241242
vmax = min(vmax, self._colorbar.norm.vmax)
242243
ticks = ticker.MaxNLocator.tick_values(self, vmin, vmax)
243-
return ticks[(ticks >= vmin) & (ticks <= vmax)]
244+
return ticks
244245

245246

246247
class _ColorbarAutoMinorLocator(ticker.AutoMinorLocator):

lib/matplotlib/contour.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,8 @@ def _autolev(self, N):
11891189
if self.logscale:
11901190
self.locator = ticker.LogLocator()
11911191
else:
1192-
self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1)
1192+
self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1,
1193+
trim_outside=False)
11931194

11941195
lev = self.locator.tick_values(self.zmin, self.zmax)
11951196

@@ -1200,10 +1201,12 @@ def _autolev(self, N):
12001201
pass
12011202

12021203
# Trim excess levels the locator may have supplied.
1203-
under = np.nonzero(lev < self.zmin)[0]
1204+
rtol = (self.zmin - self.zmax) * 1e-10
1205+
under = np.nonzero(lev < self.zmin - rtol)[0]
12041206
i0 = under[-1] if len(under) else 0
1205-
over = np.nonzero(lev > self.zmax)[0]
1207+
over = np.nonzero(lev > self.zmax + rtol)[0]
12061208
i1 = over[0] + 1 if len(over) else len(lev)
1209+
# put back extra levels if we want to extend...
12071210
if self.extend in ('min', 'both'):
12081211
i0 += 1
12091212
if self.extend in ('max', 'both'):

lib/matplotlib/tests/test_axes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5196,14 +5196,14 @@ def _helper_x(ax):
51965196
ax2.remove()
51975197
ax.set_xlim(0, 15)
51985198
r = ax.xaxis.get_major_locator()()
5199-
assert r[-1] > 14
5199+
assert np.allclose(r[-1], 14)
52005200

52015201
def _helper_y(ax):
52025202
ax2 = ax.twiny()
52035203
ax2.remove()
52045204
ax.set_ylim(0, 15)
52055205
r = ax.yaxis.get_major_locator()()
5206-
assert r[-1] > 14
5206+
assert np.allclose(r[-1], 14)
52075207

52085208
return {"x": _helper_x, "y": _helper_y}[request.param]
52095209

lib/matplotlib/ticker.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@
189189
'SymmetricalLogLocator', 'LogitLocator')
190190

191191

192+
def _keep_in_vlim(locs, vmin, vmax, rtol=1e-10):
193+
"""
194+
trim array locs to be between vmin and vmax within
195+
tolerance.
196+
"""
197+
if vmin > vmax:
198+
vmax, vmin = vmin, vmax
199+
200+
rtol = (vmax - vmin) * rtol
201+
locs = locs[locs >= vmin - rtol]
202+
locs = locs[locs <= vmax + rtol]
203+
return locs
204+
192205
# Work around numpy/numpy#6127.
193206
def _divmod(x, y):
194207
if isinstance(x, np.generic):
@@ -1823,37 +1836,45 @@ class MaxNLocator(Locator):
18231836
steps=None,
18241837
integer=False,
18251838
symmetric=False,
1839+
trim_outside=True,
18261840
prune=None,
18271841
min_n_ticks=2)
18281842

18291843
def __init__(self, *args, **kwargs):
18301844
"""
1831-
Keyword args:
1845+
Parameters
1846+
----------
18321847
1833-
*nbins*
1848+
nbins : integer
18341849
Maximum number of intervals; one less than max number of
18351850
ticks. If the string `'auto'`, the number of bins will be
18361851
automatically determined based on the length of the axis.
18371852
1838-
*steps*
1853+
steps : integer
18391854
Sequence of nice numbers starting with 1 and ending with 10;
18401855
e.g., [1, 2, 4, 5, 10], where the values are acceptable
18411856
tick multiples. i.e. for the example, 20, 40, 60 would be
18421857
an acceptable set of ticks, as would 0.4, 0.6, 0.8, because
18431858
they are multiples of 2. However, 30, 60, 90 would not
18441859
be allowed because 3 does not appear in the list of steps.
18451860
1846-
*integer*
1861+
integer : bool
18471862
If True, ticks will take only integer values, provided
18481863
at least `min_n_ticks` integers are found within the
18491864
view limits.
18501865
1851-
*symmetric*
1866+
symmetric : bool
18521867
If True, autoscaling will result in a range symmetric
18531868
about zero.
18541869
1855-
*prune*
1856-
['lower' | 'upper' | 'both' | None]
1870+
trim_outside: bool
1871+
By default (``False``) calling ``MaxNLocator`` will return one
1872+
tick thats less than vmin, and one tick thats greater than vmax.
1873+
This flag suppresses that behaviour. Note its different than
1874+
``prune`` (below), which prunes the lower or upper tick, regardless
1875+
of whether it is in the view limits.
1876+
1877+
prune : ['lower' | 'upper' | 'both' | None]
18571878
Remove edge ticks -- useful for stacked or ganged plots where
18581879
the upper tick of one axes overlaps with the lower tick of the
18591880
axes above it, primarily when :rc:`axes.autolimit_mode` is
@@ -1862,7 +1883,7 @@ def __init__(self, *args, **kwargs):
18621883
removed. If ``prune == 'both'``, the largest and smallest ticks
18631884
will be removed. If ``prune == None``, no ticks will be removed.
18641885
1865-
*min_n_ticks*
1886+
min_n_ticks : integer
18661887
Relax `nbins` and `integer` constraints if necessary to
18671888
obtain this minimum number of ticks.
18681889
@@ -1910,6 +1931,9 @@ def set_params(self, **kwargs):
19101931
self._nbins = int(self._nbins)
19111932
if 'symmetric' in kwargs:
19121933
self._symmetric = kwargs['symmetric']
1934+
1935+
self._trim_outside = kwargs.pop('trim_outside', True)
1936+
19131937
if 'prune' in kwargs:
19141938
prune = kwargs['prune']
19151939
if prune is not None and prune not in ['upper', 'lower', 'both']:
@@ -1993,13 +2017,17 @@ def __call__(self):
19932017
return self.tick_values(vmin, vmax)
19942018

19952019
def tick_values(self, vmin, vmax):
2020+
19962021
if self._symmetric:
19972022
vmax = max(abs(vmin), abs(vmax))
19982023
vmin = -vmax
19992024
vmin, vmax = mtransforms.nonsingular(
20002025
vmin, vmax, expander=1e-13, tiny=1e-14)
20012026
locs = self._raw_ticks(vmin, vmax)
20022027

2028+
if self._trim_outside:
2029+
locs = _keep_in_vlim(locs, vmin, vmax)
2030+
20032031
prune = self._prune
20042032
if prune == 'lower':
20052033
locs = locs[1:]
@@ -2543,7 +2571,7 @@ def __init__(self):
25432571
else:
25442572
nbins = 'auto'
25452573
steps = [1, 2, 2.5, 5, 10]
2546-
MaxNLocator.__init__(self, nbins=nbins, steps=steps)
2574+
MaxNLocator.__init__(self, nbins=nbins, steps=steps, trim_outside=True)
25472575

25482576

25492577
class AutoMinorLocator(Locator):

0 commit comments

Comments
 (0)