@@ -812,6 +812,92 @@ def _set_format(self):
812
812
self .format = r'$\mathdefault{%s}$' % self .format
813
813
814
814
815
+ class _SymmetricalLogUtil :
816
+ """
817
+ Helper class for working with symmetrical log scales.
818
+
819
+ Parameters
820
+ ----------
821
+ transform : `~.scale.SymmetricalLogTransform`, optional
822
+ If set, defines *base*, *linthresh* and *linscale* of the symlog transform.
823
+ base, linthresh, linscale : float, optional
824
+ The *base*, *linthresh* and *linscale* of the symlog transform, as
825
+ documented for `.SymmetricalLogScale`. These parameters are only used
826
+ if *transform* is not set.
827
+ """
828
+
829
+ def __init__ (self , transform = None , base = None , linthresh = None , linscale = None ):
830
+ if transform is not None :
831
+ self .base = transform .base
832
+ self .linthresh = transform .linthresh
833
+ self .linscale = transform .linscale
834
+ elif base is not None and linthresh is not None and linscale is not None :
835
+ self .base = base
836
+ self .linthresh = linthresh
837
+ self .linscale = linscale
838
+ else :
839
+ raise ValueError ("Either transform, or all of base, linthresh and "
840
+ "linscale must be provided." )
841
+
842
+ def pos (self , val ):
843
+ """
844
+ Calculate the normalized position of the value on the axis.
845
+ It is normalized such that the distance between two logarithmic decades
846
+ is 1 and the position of linthresh is linscale.
847
+ """
848
+ sign , val = np .sign (val ), np .abs (val ) / self .linthresh
849
+ if val > 1 :
850
+ val = self .linscale + np .log (val ) / np .log (self .base )
851
+ else :
852
+ val *= self .linscale
853
+ return sign * val
854
+
855
+ def unpos (self , val ):
856
+ """The inverse of _pos."""
857
+ sign , val = np .sign (val ), np .abs (val )
858
+ if val > self .linscale :
859
+ val = np .power (self .base , val - self .linscale )
860
+ else :
861
+ val /= self .linscale
862
+ return sign * val * self .linthresh
863
+
864
+ def firstdec (self ):
865
+ """
866
+ Get the first decade (i.e. first positive major tick candidate).
867
+ It shall be at least half the width of a logarithmic decade from the
868
+ origin (i.e. its _pos shall be at least 0.5).
869
+ """
870
+ firstexp = np .ceil (np .log (self .unpos (0.5 )) / np .log (self .base ))
871
+ firstpow = np .power (self .base , firstexp )
872
+ return firstexp , firstpow
873
+
874
+ def dec (self , val ):
875
+ """
876
+ Calculate the decade number of the value. The first decade to have a
877
+ position (given by _pos) of at least 0.5 is given the number 1, the
878
+ value 0 is given the decade number 0.
879
+ """
880
+ firstexp , firstpow = self .firstdec ()
881
+ sign , val = np .sign (val ), np .abs (val )
882
+ if val > firstpow :
883
+ val = np .log (val ) / np .log (self .base ) - firstexp + 1
884
+ else :
885
+ # We scale linearly in order to get a monotonous mapping between
886
+ # 0 and 1, though the linear nature is arbitrary.
887
+ val /= firstpow
888
+ return sign * val
889
+
890
+ def undec (self , val ):
891
+ """The inverse of _dec."""
892
+ firstexp , firstpow = self .firstdec ()
893
+ sign , val = np .sign (val ), np .abs (val )
894
+ if val > 1 :
895
+ val = np .power (self .base , val - 1 + firstexp )
896
+ else :
897
+ val *= firstpow
898
+ return sign * val
899
+
900
+
815
901
class LogFormatter (Formatter ):
816
902
"""
817
903
Base class for formatting ticks on a log or symlog scale.
@@ -2474,17 +2560,7 @@ class SymmetricalLogLocator(Locator):
2474
2560
def __init__ (self , transform = None , subs = None , numticks = None ,
2475
2561
base = None , linthresh = None , linscale = None ):
2476
2562
"""Place ticks on the locations : subs[j] * base**i."""
2477
- if transform is not None :
2478
- self ._base = transform .base
2479
- self ._linthresh = transform .linthresh
2480
- self ._linscale = transform .linscale
2481
- elif base is not None and linthresh is not None and linscale is not None :
2482
- self ._base = base
2483
- self ._linthresh = linthresh
2484
- self ._linscale = linscale
2485
- else :
2486
- raise ValueError ("Either transform, or all of base, linthresh and "
2487
- "linscale must be provided." )
2563
+ self ._symlogutil = _SymmetricalLogUtil (transform , base , linthresh , linscale )
2488
2564
self ._set_subs (subs )
2489
2565
if numticks is None :
2490
2566
if mpl .rcParams ['_internal.classic_mode' ]:
@@ -2501,11 +2577,11 @@ def set_params(self, subs=None, numticks=None,
2501
2577
if numticks is not None :
2502
2578
self .numticks = numticks
2503
2579
if base is not None :
2504
- self ._base = float (base )
2580
+ self ._symlogutil . base = float (base )
2505
2581
if linthresh is not None :
2506
- self ._linthresh = float (linthresh )
2582
+ self ._symlogutil . linthresh = float (linthresh )
2507
2583
if linscale is not None :
2508
- self ._linscale = float (linscale )
2584
+ self ._symlogutil . linscale = float (linscale )
2509
2585
2510
2586
def _set_subs (self , subs ):
2511
2587
"""
@@ -2547,8 +2623,8 @@ def tick_values(self, vmin, vmax):
2547
2623
vmin , vmax = vmax , vmin
2548
2624
2549
2625
haszero = vmin <= 0 <= vmax
2550
- firstdec = np .ceil (self ._dec (vmin ))
2551
- lastdec = np .floor (self ._dec (vmax ))
2626
+ firstdec = np .ceil (self ._symlogutil . dec (vmin ))
2627
+ lastdec = np .floor (self ._symlogutil . dec (vmax ))
2552
2628
maxdec = max (abs (firstdec ), abs (lastdec ))
2553
2629
# Number of decades completely contained in the range.
2554
2630
numdec = lastdec - firstdec
@@ -2565,7 +2641,7 @@ def tick_values(self, vmin, vmax):
2565
2641
subs = np .array ([1.0 ])
2566
2642
else :
2567
2643
_first = 2.0 if self ._subs == 'auto' else 1.0
2568
- subs = np .arange (_first , self ._base )
2644
+ subs = np .arange (_first , self ._symlogutil . base )
2569
2645
else :
2570
2646
subs = self ._subs
2571
2647
@@ -2599,16 +2675,16 @@ def tick_values(self, vmin, vmax):
2599
2675
ticklocs = []
2600
2676
for dec in decades :
2601
2677
if dec > 0 :
2602
- ticklocs .append (subs * self ._undec (dec ))
2678
+ ticklocs .append (subs * self ._symlogutil . undec (dec ))
2603
2679
elif dec < 0 :
2604
- ticklocs .append (np .flip (subs * self ._undec (dec )))
2680
+ ticklocs .append (np .flip (subs * self ._symlogutil . undec (dec )))
2605
2681
else :
2606
- if self ._linscale < 0.5 :
2682
+ if self ._symlogutil . linscale < 0.5 :
2607
2683
# Don't add minor ticks around 0, it's too camped.
2608
2684
zeroticks = np .array ([])
2609
2685
else :
2610
2686
# We add the usual subs as well as the next lower decade.
2611
- zeropow = self ._undec (1 ) / self ._base
2687
+ zeropow = self ._symlogutil . undec (1 ) / self ._symlogutil . base
2612
2688
zeroticks = subs * zeropow
2613
2689
if subs [0 ] != 1.0 :
2614
2690
zeroticks = np .concatenate (([zeropow ], zeroticks ))
@@ -2620,7 +2696,7 @@ def tick_values(self, vmin, vmax):
2620
2696
ticklocs = np .array ([])
2621
2697
else :
2622
2698
# Major locator.
2623
- ticklocs = np .array ([self ._undec (dec ) for dec in decades ])
2699
+ ticklocs = np .array ([self ._symlogutil . undec (dec ) for dec in decades ])
2624
2700
2625
2701
_log .debug ('ticklocs %r' , ticklocs )
2626
2702
if (len (subs ) > 1
@@ -2633,70 +2709,12 @@ def tick_values(self, vmin, vmax):
2633
2709
else :
2634
2710
return self .raise_if_exceeds (ticklocs )
2635
2711
2636
- def _pos (self , val ):
2637
- """
2638
- Calculate the normalized position of the value on the axis.
2639
- It is normalized such that the distance between two logarithmic decades
2640
- is 1 and the position of linthresh is linscale.
2641
- """
2642
- sign , val = np .sign (val ), np .abs (val ) / self ._linthresh
2643
- if val > 1 :
2644
- val = self ._linscale + np .log (val ) / np .log (self ._base )
2645
- else :
2646
- val *= self ._linscale
2647
- return sign * val
2648
-
2649
- def _unpos (self , val ):
2650
- """The inverse of _pos."""
2651
- sign , val = np .sign (val ), np .abs (val )
2652
- if val > self ._linscale :
2653
- val = np .power (self ._base , val - self ._linscale )
2654
- else :
2655
- val /= self ._linscale
2656
- return sign * val * self ._linthresh
2657
-
2658
- def _firstdec (self ):
2659
- """
2660
- Get the first decade (i.e. first positive major tick candidate).
2661
- It shall be at least half the width of a logarithmic decade from the
2662
- origin (i.e. its _pos shall be at least 0.5).
2663
- """
2664
- firstexp = np .ceil (np .log (self ._unpos (0.5 )) / np .log (self ._base ))
2665
- firstpow = np .power (self ._base , firstexp )
2666
- return firstexp , firstpow
2667
-
2668
- def _dec (self , val ):
2669
- """
2670
- Calculate the decade number of the value. The first decade to have a
2671
- position (given by _pos) of at least 0.5 is given the number 1, the
2672
- value 0 is given the decade number 0.
2673
- """
2674
- firstexp , firstpow = self ._firstdec ()
2675
- sign , val = np .sign (val ), np .abs (val )
2676
- if val > firstpow :
2677
- val = np .log (val ) / np .log (self ._base ) - firstexp + 1
2678
- else :
2679
- # We scale linearly in order to get a monotonous mapping between
2680
- # 0 and 1, though the linear nature is arbitrary.
2681
- val /= firstpow
2682
- return sign * val
2683
-
2684
- def _undec (self , val ):
2685
- """The inverse of _dec."""
2686
- firstexp , firstpow = self ._firstdec ()
2687
- sign , val = np .sign (val ), np .abs (val )
2688
- if val > 1 :
2689
- val = np .power (self ._base , val - 1 + firstexp )
2690
- else :
2691
- val *= firstpow
2692
- return sign * val
2693
-
2694
2712
def view_limits (self , vmin , vmax ):
2695
2713
"""Try to choose the view limits intelligently."""
2696
2714
vmin , vmax = self .nonsingular (vmin , vmax )
2697
2715
if mpl .rcParams ['axes.autolimit_mode' ] == 'round_numbers' :
2698
- vmin = self ._undec (np .floor (self ._dec (vmin )))
2699
- vmax = self ._undec (np .ceil (self ._dec (vmax )))
2716
+ vmin = self ._symlogutil . undec (np .floor (self ._symlogutil . dec (vmin )))
2717
+ vmax = self ._symlogutil . undec (np .ceil (self ._symlogutil . dec (vmax )))
2700
2718
return vmin , vmax
2701
2719
2702
2720
class AsinhLocator (Locator ):
0 commit comments