Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0ad43a5

Browse files
committed
Improved handling of data ranges almost symmetrical about zero
1 parent 800ef38 commit 0ad43a5

File tree

4 files changed

+59
-14
lines changed

4 files changed

+59
-14
lines changed

examples/scales/asinh_demo.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
Asinh Demo
44
============
55
6-
Illustration of the `asinh` axis scaling, which uses the transformation
6+
Illustration of the `asinh <.scale.AsinhScale>` axis scaling,
7+
which uses the transformation
78
89
.. math::
910
@@ -22,12 +23,13 @@
2223
2324
a \\rightarrow a_0 \\, {\\rm sgn}(a) \\ln |a| + {\\cal O}(1)
2425
25-
As with the `symlog` scaling, this allows one to plot quantities
26+
As with the `symlog <.scale.SymmetricalLogScale>` scaling,
27+
this allows one to plot quantities
2628
that cover a very wide dynamic range that includes both positive
27-
and negative values. However, `symlog` involves a tranformation
29+
and negative values. However, ``symlog`` involves a transformation
2830
that has discontinuities in its gradient because it is built
2931
from *separate* linear and logarithmic transformations.
30-
The `asinh` scaling uses a transformation that is smooth
32+
The ``asinh`` scaling uses a transformation that is smooth
3133
for all (finite) values, which is both mathematically cleaner
3234
and should reduce visual artifacts associated with an abrupt
3335
transition between linear and logarithmic regions of the plot.
@@ -79,7 +81,7 @@
7981
# due to the gradient-discontinuity in "symlog":
8082
fig3 = plt.figure()
8183
ax = fig3.subplots(1, 1)
82-
r = 3 * np.tan(np.random.uniform(-np.pi / 2.01, np.pi / 2.01,
84+
r = 3 * np.tan(np.random.uniform(-np.pi / 2.02, np.pi / 2.02,
8385
size=(5000,)))
8486
th = np.random.uniform(0, 2*np.pi, size=r.shape)
8587

lib/matplotlib/scale.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ class AsinhScale(ScaleBase):
496496
but for larger values (either positive or negative) it is asymptotically
497497
logarithmic. The transition between these linear and logarithmic regimes
498498
is smooth, and has no discontinuities in the function gradient
499-
in contrast to the `symlog` scale.
499+
in contrast to the `.SymmetricalLogScale` ("symlog") scale.
500500
501501
Specifically, the transformation of an axis coordinate :math:`a` is
502502
:math:`a \\rightarrow a_0 \\sinh^{-1} (a / a_0)` where :math:`a_0`

lib/matplotlib/tests/test_ticker.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,13 +450,21 @@ def test_init(self):
450450
assert lctr.numticks == 19
451451

452452
def test_set_params(self):
453-
lctr = mticker.AsinhLocator(linear_width=5, numticks=17)
453+
lctr = mticker.AsinhLocator(linear_width=5,
454+
numticks=17, symthresh=0.125)
454455
assert lctr.numticks == 17
456+
assert lctr.symthresh == 0.125
457+
455458
lctr.set_params(numticks=23)
456459
assert lctr.numticks == 23
457460
lctr.set_params(None)
458461
assert lctr.numticks == 23
459462

463+
lctr.set_params(symthresh=0.5)
464+
assert lctr.symthresh == 0.5
465+
lctr.set_params(symthresh=None)
466+
assert lctr.symthresh == 0.5
467+
460468
def test_linear_values(self):
461469
lctr = mticker.AsinhLocator(linear_width=100, numticks=11)
462470

@@ -489,6 +497,28 @@ def test_fallback(self):
489497
assert_almost_equal(lctr.tick_values(100, 101),
490498
np.arange(100, 101.01, 0.1))
491499

500+
def test_symmetrizing(self):
501+
class DummyAxis:
502+
bounds = (-1, 1)
503+
@classmethod
504+
def get_data_interval(cls): return cls.bounds
505+
506+
lctr = mticker.AsinhLocator(linear_width=1, numticks=3,
507+
symthresh=0.25)
508+
lctr.axis = DummyAxis
509+
510+
DummyAxis.bounds = (-1, 2)
511+
assert_almost_equal(lctr(), [-1, 0, 2])
512+
513+
DummyAxis.bounds = (-1, 0.9)
514+
assert_almost_equal(lctr(), [-1, 0, 1])
515+
516+
DummyAxis.bounds = (-0.85, 1.05)
517+
assert_almost_equal(lctr(), [-1, 0, 1])
518+
519+
DummyAxis.bounds = (1, 1.1)
520+
assert_almost_equal(lctr(), [1, 1.05, 1.1])
521+
492522

493523
class TestScalarFormatter:
494524
offset_data = [

lib/matplotlib/ticker.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2589,31 +2589,44 @@ class AsinhLocator(Locator):
25892589
"""
25902590
An axis tick locator specialized for the inverse-sinh scale
25912591
2592-
This is very unlikely to have any use beyond the AsinhScale class.
2592+
This is very unlikely to have any use beyond
2593+
the `~.scale.AsinhScale` class.
25932594
"""
2594-
def __init__(self, linear_width, numticks=11):
2595+
def __init__(self, linear_width, numticks=11, symthresh=0.2):
25952596
"""
25962597
Parameters
25972598
----------
25982599
linear_width : float
25992600
The scale parameter defining the extent
26002601
of the quasi-linear region.
2601-
numticks : int, default: 12
2602+
numticks : int, default: 11
26022603
The approximate number of major ticks that will fit
26032604
along the entire axis
2605+
symthresh : float, default: 0.2
2606+
The fractional threshold beneath which data which covers
2607+
a range that is approximately symmetric about zero
2608+
will have ticks that are exactly symmetric.
26042609
"""
26052610
super().__init__()
26062611
self.linear_width = linear_width
26072612
self.numticks = numticks
2613+
self.symthresh = symthresh
26082614

2609-
def set_params(self, numticks=None):
2615+
def set_params(self, numticks=None, symthresh=None):
26102616
"""Set parameters within this locator."""
26112617
if numticks is not None:
26122618
self.numticks = numticks
2619+
if symthresh is not None:
2620+
self.symthresh = symthresh
26132621

26142622
def __call__(self):
26152623
dmin, dmax = self.axis.get_data_interval()
2616-
return self.tick_values(dmin, dmax)
2624+
if (dmin * dmax) < 0 and abs(1 + dmax / dmin) < self.symthresh:
2625+
# Data-range appears to be almost symmetric, so round up:
2626+
bound = max(abs(dmin), abs(dmax))
2627+
return self.tick_values(-bound, bound)
2628+
else:
2629+
return self.tick_values(dmin, dmax)
26172630

26182631
def tick_values(self, vmin, vmax):
26192632
# Construct a set of "on-screen" locations
@@ -2622,9 +2635,9 @@ def tick_values(self, vmin, vmax):
26222635
/ self.linear_width)
26232636
ys = np.linspace(ymin, ymax, self.numticks)
26242637
zero_dev = np.abs(ys / (ymax - ymin))
2625-
if (ymin * ymax) < 0 and min(zero_dev) > 1e-6:
2638+
if (ymin * ymax) < 0 and min(zero_dev) > 0:
26262639
# Ensure that the zero tick-mark is included,
2627-
# if the axis stradles zero
2640+
# if the axis straddles zero
26282641
ys = np.hstack([ys[(zero_dev > 0.5 / self.numticks)], 0.0])
26292642

26302643
# Transform the "on-screen" grid to the data space:

0 commit comments

Comments
 (0)