Skip to content

Commit f093129

Browse files
anntzertimhoffm
authored andcommitted
Avoid 1-tick or 0-tick log-scaled axis. (#12865)
... by switching to AutoLocator when the minor LogLocator would produce no more than one tick.
1 parent 0132e12 commit f093129

File tree

3 files changed

+33
-14
lines changed

3 files changed

+33
-14
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Log-scaled axes avoid having zero or only one tick
2+
``````````````````````````````````````````````````
3+
4+
When the default `LogLocator` would generate no ticks for an axis (e.g., an
5+
axis with limits from 0.31 to 0.39) or only a single tick, it now instead falls
6+
back on the linear `AutoLocator` to pick reasonable tick positions.

lib/matplotlib/tests/test_ticker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import warnings
22

33
import numpy as np
4-
from numpy.testing import assert_almost_equal
4+
from numpy.testing import assert_almost_equal, assert_array_equal
55
import pytest
66

77
import matplotlib
@@ -179,6 +179,11 @@ def test_basic(self):
179179
test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.])
180180
assert_almost_equal(loc.tick_values(1, 100), test_value)
181181

182+
def test_switch_to_autolocator(self):
183+
loc = mticker.LogLocator(subs="all")
184+
assert_array_equal(loc.tick_values(0.45, 0.55),
185+
[0.44, 0.46, 0.48, 0.5, 0.52, 0.54, 0.56])
186+
182187
def test_set_params(self):
183188
"""
184189
Create log locator with default value, base=10.0, subs=[1.0],

lib/matplotlib/ticker.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,13 +2156,13 @@ def tick_values(self, vmin, vmax):
21562156
"log-scaled.")
21572157

21582158
_log.debug('vmin %s vmax %s', vmin, vmax)
2159-
vmin = math.log(vmin) / math.log(b)
2160-
vmax = math.log(vmax) / math.log(b)
21612159

21622160
if vmax < vmin:
21632161
vmin, vmax = vmax, vmin
2162+
log_vmin = math.log(vmin) / math.log(b)
2163+
log_vmax = math.log(vmax) / math.log(b)
21642164

2165-
numdec = math.floor(vmax) - math.ceil(vmin)
2165+
numdec = math.floor(log_vmax) - math.ceil(log_vmin)
21662166

21672167
if isinstance(self._subs, str):
21682168
_first = 2.0 if self._subs == 'auto' else 1.0
@@ -2181,32 +2181,40 @@ def tick_values(self, vmin, vmax):
21812181
if rcParams['_internal.classic_mode'] else
21822182
(numdec + 1) // numticks + 1)
21832183

2184-
# Does subs include anything other than 1?
2184+
# Does subs include anything other than 1? Essentially a hack to know
2185+
# whether we're a major or a minor locator.
21852186
have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0)
21862187

2187-
decades = np.arange(math.floor(vmin) - stride,
2188-
math.ceil(vmax) + 2 * stride, stride)
2188+
decades = np.arange(math.floor(log_vmin) - stride,
2189+
math.ceil(log_vmax) + 2 * stride, stride)
21892190

21902191
if hasattr(self, '_transform'):
21912192
ticklocs = self._transform.inverted().transform(decades)
21922193
if have_subs:
21932194
if stride == 1:
21942195
ticklocs = np.ravel(np.outer(subs, ticklocs))
21952196
else:
2196-
# no ticklocs if we have more than one decade
2197-
# between major ticks.
2198-
ticklocs = []
2197+
# No ticklocs if we have >1 decade between major ticks.
2198+
ticklocs = np.array([])
21992199
else:
22002200
if have_subs:
2201-
ticklocs = []
22022201
if stride == 1:
2203-
for decadeStart in b ** decades:
2204-
ticklocs.extend(subs * decadeStart)
2202+
ticklocs = np.concatenate(
2203+
[subs * decade_start for decade_start in b ** decades])
2204+
else:
2205+
ticklocs = np.array([])
22052206
else:
22062207
ticklocs = b ** decades
22072208

22082209
_log.debug('ticklocs %r', ticklocs)
2209-
return self.raise_if_exceeds(np.asarray(ticklocs))
2210+
if (len(subs) > 1
2211+
and stride == 1
2212+
and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1):
2213+
# If we're a minor locator *that expects at least two ticks per
2214+
# decade* and the major locator stride is 1 and there's no more
2215+
# than one minor tick, switch to AutoLocator.
2216+
return AutoLocator().tick_values(vmin, vmax)
2217+
return self.raise_if_exceeds(ticklocs)
22102218

22112219
def view_limits(self, vmin, vmax):
22122220
'Try to choose the view limits intelligently'

0 commit comments

Comments
 (0)