Skip to content

Commit 1ec0b22

Browse files
committed
Improved documentation of asinh transformation and parameter naming
1 parent dd4f4d8 commit 1ec0b22

File tree

3 files changed

+59
-26
lines changed

3 files changed

+59
-26
lines changed

examples/scales/asinh_demo.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,34 @@
2525
ax1.set_title(r'$sinh^{-1}$')
2626

2727

28-
# Compare "asinh" graphs with different scale parameter "a0":
28+
# Compare "asinh" graphs with different scale parameter "linear_width":
2929
fig2 = plt.figure()
3030
axs = fig2.subplots(1, 3, sharex=True)
3131
for ax, a0 in zip(axs, (0.2, 1.0, 5.0)):
32-
ax.set_title('a0={:.3g}'.format(a0))
32+
ax.set_title('linear_width={:.3g}'.format(a0))
3333
ax.plot(x, x, label='y=x')
3434
ax.plot(x, 10*x, label='y=10x')
3535
ax.plot(x, 100*x, label='y=100x')
36-
ax.set_yscale('asinh', a0=a0)
36+
ax.set_yscale('asinh', linear_width=a0)
3737
ax.grid()
3838
ax.legend(loc='best', fontsize='small')
3939

40+
41+
# Compare "symlog" and "asinh" scalings
42+
# on 2D Cauchy-distributed random numbers:
43+
fig3 = plt.figure()
44+
ax = fig3.subplots(1, 1)
45+
r = numpy.tan(numpy.random.uniform(-numpy.pi / 2.02, numpy.pi / 2.02,
46+
size=(5000,)))
47+
th = numpy.random.uniform(0, 2*numpy.pi, size=r.shape)
48+
49+
ax.scatter(r * numpy.cos(th), r * numpy.sin(th), s=4, alpha=0.5)
50+
ax.set_xscale('asinh')
51+
ax.set_yscale('symlog')
52+
ax.set_xlabel('asinh')
53+
ax.set_ylabel('symlog')
54+
ax.set_title('2D Cauchy random deviates')
55+
ax.grid()
56+
57+
4058
plt.show()

lib/matplotlib/scale.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -467,75 +467,85 @@ class AsinhScale(ScaleBase):
467467
logarithmic. The transition between these linear and logarithmic regimes
468468
is smooth, and has no discontinutities in the function gradient
469469
in contrast to the "symlog" scale.
470+
471+
Specifically, the transformation of an axis coordinate :math:`a` is
472+
is :math:`a \\rightarrow a_0 \sinh^{-1} (a / a_0)` where :math:`a_0`
473+
is the effective width of the linear region of the transformation.
474+
In that region, the transformation is
475+
:math:`a \\rightarrow a + {\cal O}(a^3)`.
476+
For large values of :math:`a` the transformation behaves as
477+
:math:`a \\rightarrow a_0 \ln (a) + {\cal O}(1)`.
470478
"""
471479

472480
name = 'asinh'
473481

474-
def __init__(self, axis, *, a0=1.0, **kwargs):
482+
def __init__(self, axis, *, linear_width=1.0, **kwargs):
475483
"""
476484
Parameters
477485
----------
478-
a0 : float, default: 1
479-
The scale parameter defining the extent of the quasi-linear region.
486+
linear_width : float, default: 1
487+
The scale parameter defining the extent of the quasi-linear region,
488+
and the coordinate values beyond which the transformation
489+
becomes asympoticially logarithmic.
480490
"""
481491
super().__init__(axis)
482-
if a0 <= 0.0:
492+
if linear_width <= 0.0:
483493
raise ValueError("Scale parameter 'a0' must be strictly positive")
484-
self.a0 = a0
494+
self.linear_width = linear_width
485495

486496
def get_transform(self):
487-
return self.AsinhTransform(self.a0)
497+
return self.AsinhTransform(self.linear_width)
488498

489499
def set_default_locators_and_formatters(self, axis):
490-
axis.set(major_locator=AsinhScale.AsinhLocator(self.a0),
500+
axis.set(major_locator=AsinhScale.AsinhLocator(self.linear_width),
491501
major_formatter='{x:.3g}')
492502

493503
class AsinhTransform(Transform):
494504
input_dims = output_dims = 1
495505

496-
def __init__(self, a0):
506+
def __init__(self, linear_width):
497507
super().__init__()
498-
self.a0 = a0
508+
self.linear_width = linear_width
499509

500510
def transform_non_affine(self, a):
501-
return self.a0 * np.arcsinh(a / self.a0)
511+
return self.linear_width * np.arcsinh(a / self.linear_width)
502512

503513
def inverted(self):
504-
return AsinhScale.InvertedAsinhTransform(self.a0)
514+
return AsinhScale.InvertedAsinhTransform(self.linear_width)
505515

506516
class InvertedAsinhTransform(Transform):
507517
input_dims = output_dims = 1
508518

509-
def __init__(self, a0):
519+
def __init__(self, linear_width):
510520
super().__init__()
511-
self.a0 = a0
521+
self.linear_width = linear_width
512522

513523
def transform_non_affine(self, a):
514-
return self.a0 * np.sinh(a / self.a0)
524+
return self.linear_width * np.sinh(a / self.linear_width)
515525

516526
def inverted(self):
517-
return AsinhScale.AsinhTransform(self.a0)
527+
return AsinhScale.AsinhTransform(self.linear_width)
518528

519529
class AsinhLocator(Locator):
520530
"""
521531
An axis tick locator specialized for the arcsinh scale
522532
523533
This is very unlikely to have any use beyond the AsinhScale class.
524534
"""
525-
def __init__(self, a0, apx_tick_count=12):
535+
def __init__(self, linear_width, numticks=12):
526536
"""
527537
Parameters
528538
----------
529-
a0 : float
539+
linear_width : float
530540
The scale parameter defining the extent
531541
of the quasi-linear region.
532-
apx_tick_count : int, default: 12
542+
numticks : int, default: 12
533543
The approximate number of major ticks that will fit
534544
along the entire axis
535545
"""
536546
super().__init__()
537-
self.a0 = a0
538-
self.apx_tick_count = apx_tick_count
547+
self.linear_width = linear_width
548+
self.numticks = numticks
539549

540550
def __call__(self):
541551
dmin, dmax = self.axis.get_data_interval()
@@ -544,15 +554,15 @@ def __call__(self):
544554
def tick_values(self, vmin, vmax):
545555
# Construct a set of "on-screen" locations
546556
# that are uniformly spaced:
547-
ymin, ymax = self.a0 * np.arcsinh(np.array([vmin, vmax]) / self.a0)
548-
ys = np.linspace(ymin, ymax, self.apx_tick_count)
557+
ymin, ymax = self.linear_width * np.arcsinh(np.array([vmin, vmax]) / self.linear_width)
558+
ys = np.linspace(ymin, ymax, self.numticks)
549559
if (ymin * ymax) < 0:
550560
# Ensure that the zero tick-mark is included,
551561
# if the axis stradles zero
552562
ys = np.hstack([ys, 0.0])
553563

554564
# Transform the "on-screen" grid to the data space:
555-
xs = self.a0 * np.sinh(ys / self.a0)
565+
xs = self.linear_width * np.sinh(ys / self.linear_width)
556566
zero_xs = (xs == 0)
557567

558568
# Round the data-space values to be intuitive decimal numbers:

lib/matplotlib/tests/test_scale.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,8 @@ def test_scale_deepcopy():
219219
sc2 = copy.deepcopy(sc)
220220
assert str(sc.get_transform()) == str(sc2.get_transform())
221221
assert sc._transform is not sc2._transform
222+
223+
224+
def test_asinh_transforms():
225+
# FIXME - more here soon
226+
pass

0 commit comments

Comments
 (0)