Skip to content

Commit 5d48e42

Browse files
committed
Adapt LogFormatter for minor symlog ticks
1 parent fa94cc3 commit 5d48e42

File tree

1 file changed

+56
-31
lines changed

1 file changed

+56
-31
lines changed

lib/matplotlib/ticker.py

+56-31
Original file line numberDiff line numberDiff line change
@@ -926,9 +926,9 @@ class LogFormatter(Formatter):
926926
avoid crowding. If ``numdec > subset`` then no minor ticks will
927927
be labeled.
928928
929-
linthresh : None or float, default: None
930-
If a symmetric log scale is in use, its ``linthresh``
931-
parameter must be supplied here.
929+
linthresh, linscale : None or float, default: None
930+
If a symmetric log scale is in use, its ``linthresh`` and ``linscale``
931+
parameters must be supplied here.
932932
933933
Notes
934934
-----
@@ -958,7 +958,7 @@ class LogFormatter(Formatter):
958958

959959
def __init__(self, base=10.0, labelOnlyBase=False,
960960
minor_thresholds=None,
961-
linthresh=None):
961+
linthresh=None, linscale=None):
962962

963963
self.set_base(base)
964964
self.set_label_minor(labelOnlyBase)
@@ -970,6 +970,9 @@ def __init__(self, base=10.0, labelOnlyBase=False,
970970
self.minor_thresholds = minor_thresholds
971971
self._sublabels = None
972972
self._linthresh = linthresh
973+
self._linscale = linscale
974+
self._symlogutil = None
975+
self._firstsublabels = None
973976

974977
def set_base(self, base):
975978
"""
@@ -991,6 +994,21 @@ def set_label_minor(self, labelOnlyBase):
991994
"""
992995
self.labelOnlyBase = labelOnlyBase
993996

997+
@property
998+
def _symlog(self):
999+
if self._symlogutil is not None:
1000+
return True
1001+
if self._linthresh is not None and self._linscale is not None:
1002+
self._symlogutil = _SymmetricalLogUtil(base=self._base,
1003+
linthresh=self._linthresh,
1004+
linscale=self._linscale)
1005+
return True
1006+
transf = self.axis.get_transform()
1007+
if hasattr(transf, 'linthresh'):
1008+
self._symlogutil = _SymmetricalLogUtil(transf)
1009+
return True
1010+
return False
1011+
9941012
def set_locs(self, locs=None):
9951013
"""
9961014
Use axis view limits to control which ticks are labeled.
@@ -1001,36 +1019,20 @@ def set_locs(self, locs=None):
10011019
self._sublabels = None
10021020
return
10031021

1004-
# Handle symlog case:
1005-
linthresh = self._linthresh
1006-
if linthresh is None:
1007-
try:
1008-
linthresh = self.axis.get_transform().linthresh
1009-
except AttributeError:
1010-
pass
1011-
10121022
vmin, vmax = self.axis.get_view_interval()
10131023
if vmin > vmax:
10141024
vmin, vmax = vmax, vmin
10151025

1016-
if linthresh is None and vmin <= 0:
1026+
if not self._symlog and vmin <= 0:
10171027
# It's probably a colorbar with
10181028
# a format kwarg setting a LogFormatter in the manner
10191029
# that worked with 1.5.x, but that doesn't work now.
10201030
self._sublabels = {1} # label powers of base
10211031
return
10221032

10231033
b = self._base
1024-
if linthresh is not None: # symlog
1025-
# Only compute the number of decades in the logarithmic part of the
1026-
# axis
1027-
numdec = 0
1028-
if vmin < -linthresh:
1029-
rhs = min(vmax, -linthresh)
1030-
numdec += math.log(vmin / rhs) / math.log(b)
1031-
if vmax > linthresh:
1032-
lhs = max(vmin, linthresh)
1033-
numdec += math.log(vmax / lhs) / math.log(b)
1034+
if self._symlog:
1035+
numdec = self._symlogutil.pos(vmax) - self._symlogutil.pos(vmin)
10341036
else:
10351037
vmin = math.log(vmin) / math.log(b)
10361038
vmax = math.log(vmax) / math.log(b)
@@ -1039,16 +1041,25 @@ def set_locs(self, locs=None):
10391041
if numdec > self.minor_thresholds[0]:
10401042
# Label only bases
10411043
self._sublabels = {1}
1044+
if self._symlog:
1045+
self._firstsublabels = {0}
10421046
elif numdec > self.minor_thresholds[1]:
10431047
# Add labels between bases at log-spaced coefficients;
10441048
# include base powers in case the locations include
10451049
# "major" and "minor" points, as in colorbar.
10461050
c = np.geomspace(1, b, int(b)//2 + 1)
10471051
self._sublabels = set(np.round(c))
10481052
# For base 10, this yields (1, 2, 3, 4, 6, 10).
1053+
if self._symlog:
1054+
# For the linear part of the scale we use an analog selection.
1055+
c = np.linspace(2, b, int(b) // 2)
1056+
self._firstsublabels = set(np.round(c))
1057+
# For base 10, this yields (0, 2, 4, 6, 8, 10).
10491058
else:
10501059
# Label all integer multiples of base**n.
10511060
self._sublabels = set(np.arange(1, b + 1))
1061+
if self._symlog:
1062+
self._firstsublabels = set(np.arange(0, b + 1))
10521063

10531064
def _num_to_string(self, x, vmin, vmax):
10541065
if x > 10000:
@@ -1073,10 +1084,17 @@ def __call__(self, x, pos=None):
10731084
exponent = round(fx) if is_x_decade else np.floor(fx)
10741085
coeff = round(b ** (fx - exponent))
10751086

1076-
if self.labelOnlyBase and not is_x_decade:
1077-
return ''
1078-
if self._sublabels is not None and coeff not in self._sublabels:
1079-
return ''
1087+
_, firstpow = self._symlogutil.firstdec() if self._symlog else None, 0
1088+
if x < firstpow:
1089+
if self.labelOnlyBase:
1090+
return ''
1091+
if self._firstsublabels is not None and coeff not in self._firstsublabels:
1092+
return ''
1093+
else:
1094+
if self.labelOnlyBase and not is_x_decade:
1095+
return ''
1096+
if self._sublabels is not None and coeff not in self._sublabels:
1097+
return ''
10801098

10811099
vmin, vmax = self.axis.get_view_interval()
10821100
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
@@ -1154,10 +1172,17 @@ def __call__(self, x, pos=None):
11541172
exponent = round(fx) if is_x_decade else np.floor(fx)
11551173
coeff = round(b ** (fx - exponent))
11561174

1157-
if self.labelOnlyBase and not is_x_decade:
1158-
return ''
1159-
if self._sublabels is not None and coeff not in self._sublabels:
1160-
return ''
1175+
_, firstpow = self._symlogutil.firstdec() if self._symlog else (None, 0)
1176+
if x < firstpow:
1177+
if self.labelOnlyBase:
1178+
return ''
1179+
if self._firstsublabels is not None and coeff not in self._firstsublabels:
1180+
return ''
1181+
else:
1182+
if self.labelOnlyBase and not is_x_decade:
1183+
return ''
1184+
if self._sublabels is not None and coeff not in self._sublabels:
1185+
return ''
11611186

11621187
if is_x_decade:
11631188
fx = round(fx)

0 commit comments

Comments
 (0)