Skip to content

Commit 8168e56

Browse files
committed
Improved minor-tick location for common number bases and widened test coverage
1 parent 65eff09 commit 8168e56

File tree

5 files changed

+88
-10
lines changed

5 files changed

+88
-10
lines changed

examples/scales/asinh_demo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@
6464
# Compare "asinh" graphs with different scale parameter "linear_width":
6565
fig2 = plt.figure(constrained_layout=True)
6666
axs = fig2.subplots(1, 3, sharex=True)
67-
for ax, a0 in zip(axs, (0.2, 1.0, 5.0)):
67+
for ax, (a0, base) in zip(axs, ((0.2, 2), (1.0, 0), (5.0, 3))):
6868
ax.set_title('linear_width={:.3g}'.format(a0))
6969
ax.plot(x, x, label='y=x')
7070
ax.plot(x, 10*x, label='y=10x')
7171
ax.plot(x, 100*x, label='y=100x')
72-
ax.set_yscale('asinh', linear_width=a0)
72+
ax.set_yscale('asinh', linear_width=a0, base=base)
7373
ax.grid()
7474
ax.legend(loc='best', fontsize='small')
7575

lib/matplotlib/scale.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,18 @@ class AsinhScale(ScaleBase):
511511

512512
name = 'asinh'
513513

514+
auto_tick_multipliers = {
515+
3: (2, ),
516+
4: (2, ),
517+
5: (2, ),
518+
8: (2, 4),
519+
10: (2, 5),
520+
16: (2, 4, 8),
521+
64: (4, 16),
522+
1024: (256, 512) }
523+
514524
def __init__(self, axis, *, linear_width=1.0,
515-
base=10, subs=(2, 5), **kwargs):
525+
base=10, subs='auto', **kwargs):
516526
"""
517527
Parameters
518528
----------
@@ -521,11 +531,23 @@ def __init__(self, axis, *, linear_width=1.0,
521531
defining the extent of the quasi-linear region,
522532
and the coordinate values beyond which the transformation
523533
becomes asympotically logarithmic.
534+
base : int, default: 10
535+
The number base used for rounding tick locations
536+
on a logarithmic scale. If this is less than one,
537+
then rounding is to the nearest integer multiple
538+
of powers of ten.
539+
subs : sequence of int
540+
Multiples of the number base used for minor ticks.
541+
If set to 'auto', this will use built-in defaults,
542+
e.g. (2, 5) for base=10.
524543
"""
525544
super().__init__(axis)
526545
self._transform = AsinhTransform(linear_width)
527546
self._base = int(base)
528-
self._subs = subs
547+
if subs == 'auto':
548+
self._subs = self.auto_tick_multipliers.get(self._base)
549+
else:
550+
self._subs = subs
529551

530552
linear_width = property(lambda self: self._transform.linear_width)
531553

lib/matplotlib/tests/test_scale.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,24 @@ def test_init(self):
243243

244244
s = AsinhScale(axis=None, linear_width=23.0)
245245
assert s.linear_width == 23
246+
assert s._base == 10
247+
assert s._subs == (2, 5)
246248

247249
tx = s.get_transform()
248250
assert isinstance(tx, AsinhTransform)
249251
assert tx.linear_width == s.linear_width
250252

253+
def test_base_init(self):
254+
fig, ax = plt.subplots()
255+
256+
s3 = AsinhScale(axis=None, base=3)
257+
assert s3._base == 3
258+
assert s3._subs == (2,)
259+
260+
s7 = AsinhScale(axis=None, base=7)
261+
assert s7._base == 7
262+
assert s7._subs == (2, 5)
263+
251264
def test_bad_scale(self):
252265
fig, ax = plt.subplots()
253266

lib/matplotlib/tests/test_ticker.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,12 +448,16 @@ def test_init(self):
448448
lctr = mticker.AsinhLocator(linear_width=2.718, numticks=19)
449449
assert lctr.linear_width == 2.718
450450
assert lctr.numticks == 19
451+
assert lctr.base == 0
451452

452453
def test_set_params(self):
453454
lctr = mticker.AsinhLocator(linear_width=5,
454-
numticks=17, symthresh=0.125)
455+
numticks=17, symthresh=0.125,
456+
base=4, subs=(2.5, 3.25))
455457
assert lctr.numticks == 17
456458
assert lctr.symthresh == 0.125
459+
assert lctr.base == 4
460+
assert lctr.subs == (2.5, 3.25)
457461

458462
lctr.set_params(numticks=23)
459463
assert lctr.numticks == 23
@@ -465,8 +469,20 @@ def test_set_params(self):
465469
lctr.set_params(symthresh=None)
466470
assert lctr.symthresh == 0.5
467471

472+
lctr.set_params(base=7)
473+
assert lctr.base == 7
474+
lctr.set_params(base=None)
475+
assert lctr.base == 7
476+
477+
lctr.set_params(subs=(2, 4.125))
478+
assert lctr.subs == (2, 4.125)
479+
lctr.set_params(subs=None)
480+
assert lctr.subs == (2, 4.125)
481+
lctr.set_params(subs=[])
482+
assert lctr.subs is None
483+
468484
def test_linear_values(self):
469-
lctr = mticker.AsinhLocator(linear_width=100, numticks=11)
485+
lctr = mticker.AsinhLocator(linear_width=100, numticks=11, base=0)
470486

471487
assert_almost_equal(lctr.tick_values(-1, 1),
472488
np.arange(-1, 1.01, 0.2))
@@ -476,7 +492,7 @@ def test_linear_values(self):
476492
np.arange(-0.01, 0.0101, 0.002))
477493

478494
def test_wide_values(self):
479-
lctr = mticker.AsinhLocator(linear_width=0.1, numticks=11)
495+
lctr = mticker.AsinhLocator(linear_width=0.1, numticks=11, base=0)
480496

481497
assert_almost_equal(lctr.tick_values(-100, 100),
482498
[-100, -20, -5, -1, -0.2,
@@ -487,7 +503,7 @@ def test_wide_values(self):
487503

488504
def test_near_zero(self):
489505
"""Check that manually injected zero will supersede nearby tick"""
490-
lctr = mticker.AsinhLocator(linear_width=100, numticks=3)
506+
lctr = mticker.AsinhLocator(linear_width=100, numticks=3, base=0)
491507

492508
assert_almost_equal(lctr.tick_values(-1.1, 0.9), [-1.0, 0.0, 0.9])
493509

@@ -504,7 +520,7 @@ class DummyAxis:
504520
def get_data_interval(cls): return cls.bounds
505521

506522
lctr = mticker.AsinhLocator(linear_width=1, numticks=3,
507-
symthresh=0.25)
523+
symthresh=0.25, base=0)
508524
lctr.axis = DummyAxis
509525

510526
DummyAxis.bounds = (-1, 2)
@@ -519,6 +535,20 @@ def get_data_interval(cls): return cls.bounds
519535
DummyAxis.bounds = (1, 1.1)
520536
assert_almost_equal(lctr(), [1, 1.05, 1.1])
521537

538+
def test_base_rounding(self):
539+
lctr10 = mticker.AsinhLocator(linear_width=1, numticks=8,
540+
base=10, subs=(1, 3, 5))
541+
assert_almost_equal(lctr10.tick_values(-110, 110),
542+
[-500, -300, -100, -50, -30, -10, -5, -3, -1,
543+
-0.5, -0.3, -0.1, 0, 0.1, 0.3, 0.5,
544+
1, 3, 5, 10, 30, 50, 100, 300, 500])
545+
546+
lctr5 = mticker.AsinhLocator(linear_width=1, numticks=20, base=5)
547+
assert_almost_equal(lctr5.tick_values(-1050, 1050),
548+
[-625, -125, -25, -5, -1, -0.2, 0,
549+
0.2, 1, 5, 25, 125, 625])
550+
551+
522552

523553
class TestScalarFormatter:
524554
offset_data = [

lib/matplotlib/ticker.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2607,6 +2607,14 @@ def __init__(self, linear_width, numticks=11, symthresh=0.2,
26072607
The fractional threshold beneath which data which covers
26082608
a range that is approximately symmetric about zero
26092609
will have ticks that are exactly symmetric.
2610+
base : int, default: 0
2611+
The number base used for rounding tick locations
2612+
on a logarithmic scale. If this is less than one,
2613+
then rounding is to the nearest integer multiple
2614+
of powers of ten.
2615+
subs : tuple, default: None
2616+
Multiples of the number base, typically used
2617+
for the minor ticks, e.g. (2, 5) when base=10.
26102618
"""
26112619
super().__init__()
26122620
self.linear_width = linear_width
@@ -2615,12 +2623,17 @@ def __init__(self, linear_width, numticks=11, symthresh=0.2,
26152623
self.base = base
26162624
self.subs = subs
26172625

2618-
def set_params(self, numticks=None, symthresh=None):
2626+
def set_params(self, numticks=None, symthresh=None,
2627+
base=None, subs=None):
26192628
"""Set parameters within this locator."""
26202629
if numticks is not None:
26212630
self.numticks = numticks
26222631
if symthresh is not None:
26232632
self.symthresh = symthresh
2633+
if base is not None:
2634+
self.base = base
2635+
if subs is not None:
2636+
self.subs = subs if len(subs) > 0 else None
26242637

26252638
def __call__(self):
26262639
dmin, dmax = self.axis.get_data_interval()

0 commit comments

Comments
 (0)