-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
BUG: fix minpos handling and other log ticker problems #7598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e3de3be
8bdabcd
a8e7575
aa36ec3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -902,10 +902,6 @@ def set_locs(self, locs=None): | |
self._sublabels = None | ||
return | ||
|
||
b = self._base | ||
|
||
vmin, vmax = self.axis.get_view_interval() | ||
|
||
# Handle symlog case: | ||
linthresh = self._linthresh | ||
if linthresh is None: | ||
|
@@ -914,6 +910,18 @@ def set_locs(self, locs=None): | |
except AttributeError: | ||
pass | ||
|
||
vmin, vmax = self.axis.get_view_interval() | ||
if vmin > vmax: | ||
vmin, vmax = vmax, vmin | ||
|
||
if linthresh is None and vmin <= 0: | ||
# It's probably a colorbar with | ||
# a format kwarg setting a LogFormatter in the manner | ||
# that worked with 1.5.x, but that doesn't work now. | ||
self._sublabels = set((1,)) # label powers of base | ||
return | ||
|
||
b = self._base | ||
if linthresh is not None: # symlog | ||
# Only compute the number of decades in the logarithmic part of the | ||
# axis | ||
|
@@ -943,37 +951,38 @@ def set_locs(self, locs=None): | |
# Label all integer multiples of base**n. | ||
self._sublabels = set(np.arange(1, b + 1)) | ||
|
||
def _num_to_string(self, x, vmin, vmax): | ||
if x > 10000: | ||
s = '%1.0e' % x | ||
elif x < 1: | ||
s = '%1.0e' % x | ||
else: | ||
s = self.pprint_val(x, vmax - vmin) | ||
|
||
def __call__(self, x, pos=None): | ||
""" | ||
Return the format for tick val `x`. | ||
""" | ||
vmin, vmax = self.axis.get_view_interval() | ||
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) | ||
d = abs(vmax - vmin) | ||
b = self._base | ||
if x == 0.0: | ||
if x == 0.0: # Symlog | ||
return '0' | ||
|
||
sign = np.sign(x) | ||
x = abs(x) | ||
b = self._base | ||
# only label the decades | ||
fx = math.log(x) / math.log(b) | ||
is_x_decade = is_close_to_int(fx) | ||
exponent = np.round(fx) if is_x_decade else np.floor(fx) | ||
coeff = np.round(x / b ** exponent) | ||
|
||
if self.labelOnlyBase and not is_x_decade: | ||
return '' | ||
if self._sublabels is not None and coeff not in self._sublabels: | ||
return '' | ||
|
||
if x > 10000: | ||
s = '%1.0e' % x | ||
elif x < 1: | ||
s = '%1.0e' % x | ||
else: | ||
s = self.pprint_val(x, d) | ||
if sign == -1: | ||
s = '-%s' % s | ||
|
||
vmin, vmax = self.axis.get_view_interval() | ||
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) | ||
s = self._num_to_string(x, vmin, vmax) | ||
return self.fix_minus(s) | ||
|
||
def format_data(self, value): | ||
|
@@ -1026,41 +1035,16 @@ class LogFormatterExponent(LogFormatter): | |
""" | ||
Format values for log axis using ``exponent = log_base(value)``. | ||
""" | ||
def __call__(self, x, pos=None): | ||
""" | ||
Return the format for tick value `x`. | ||
""" | ||
vmin, vmax = self.axis.get_view_interval() | ||
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) | ||
d = abs(vmax - vmin) | ||
b = self._base | ||
if x == 0: | ||
return '0' | ||
sign = np.sign(x) | ||
x = abs(x) | ||
# only label the decades | ||
fx = math.log(x) / math.log(b) | ||
|
||
is_x_decade = is_close_to_int(fx) | ||
exponent = np.round(fx) if is_x_decade else np.floor(fx) | ||
coeff = np.round(x / b ** exponent) | ||
|
||
if self.labelOnlyBase and not is_x_decade: | ||
return '' | ||
if self._sublabels is not None and coeff not in self._sublabels: | ||
return '' | ||
|
||
def _num_to_string(self, x, vmin, vmax): | ||
fx = math.log(x) / math.log(self._base) | ||
if abs(fx) > 10000: | ||
s = '%1.0g' % fx | ||
elif abs(fx) < 1: | ||
s = '%1.0g' % fx | ||
else: | ||
fd = math.log(abs(d)) / math.log(b) | ||
fd = math.log(vmax - vmin) / math.log(self._base) | ||
s = self.pprint_val(fx, fd) | ||
if sign == -1: | ||
s = '-%s' % s | ||
|
||
return self.fix_minus(s) | ||
return s | ||
|
||
|
||
class LogFormatterMathtext(LogFormatter): | ||
|
@@ -1082,35 +1066,34 @@ def __call__(self, x, pos=None): | |
|
||
The position `pos` is ignored. | ||
""" | ||
b = self._base | ||
usetex = rcParams['text.usetex'] | ||
|
||
# only label the decades | ||
if x == 0: | ||
if x == 0: # Symlog | ||
if usetex: | ||
return '$0$' | ||
else: | ||
return '$%s$' % _mathdefault('0') | ||
|
||
sign_string = '-' if x < 0 else '' | ||
x = abs(x) | ||
b = self._base | ||
|
||
# only label the decades | ||
fx = math.log(x) / math.log(b) | ||
is_x_decade = is_close_to_int(fx) | ||
exponent = np.round(fx) if is_x_decade else np.floor(fx) | ||
coeff = np.round(x / b ** exponent) | ||
|
||
if self.labelOnlyBase and not is_x_decade: | ||
return '' | ||
if self._sublabels is not None and coeff not in self._sublabels: | ||
return '' | ||
|
||
# use string formatting of the base if it is not an integer | ||
if b % 1 == 0.0: | ||
base = '%d' % b | ||
else: | ||
base = '%s' % b | ||
|
||
if self.labelOnlyBase and not is_x_decade: | ||
return '' | ||
if self._sublabels is not None and coeff not in self._sublabels: | ||
return '' | ||
|
||
if not is_x_decade: | ||
return self._non_decade_format(sign_string, base, fx, usetex) | ||
else: | ||
|
@@ -2032,36 +2015,41 @@ def view_limits(self, vmin, vmax): | |
'Try to choose the view limits intelligently' | ||
b = self._base | ||
|
||
if vmax < vmin: | ||
vmin, vmax = vmax, vmin | ||
vmin, vmax = self.nonsingular(vmin, vmax) | ||
|
||
if self.axis.axes.name == 'polar': | ||
vmax = math.ceil(math.log(vmax) / math.log(b)) | ||
vmin = b ** (vmax - self.numdecs) | ||
return vmin, vmax | ||
|
||
minpos = self.axis.get_minpos() | ||
|
||
if minpos <= 0 or not np.isfinite(minpos): | ||
raise ValueError( | ||
"Data has no positive values, and therefore can not be " | ||
"log-scaled.") | ||
|
||
if vmin <= 0: | ||
vmin = minpos | ||
|
||
if rcParams['axes.autolimit_mode'] == 'round_numbers': | ||
if not is_decade(vmin, self._base): | ||
vmin = decade_down(vmin, self._base) | ||
if not is_decade(vmax, self._base): | ||
vmax = decade_up(vmax, self._base) | ||
|
||
if vmin == vmax: | ||
vmin = decade_down(vmin, self._base) | ||
vmax = decade_up(vmax, self._base) | ||
return vmin, vmax | ||
|
||
result = mtransforms.nonsingular(vmin, vmax) | ||
return result | ||
def nonsingular(self, vmin, vmax): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we already have have a nonsingular? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but 'nonsingular' means different things for different transforms--different behaviors are needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this have a leading underscore? I missread and did not notice that this was a method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could, but there are other There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am sold on consistency of naming. |
||
if not np.isfinite(vmin) or not np.isfinite(vmax): | ||
return 1, 10 # initial range, no data plotted yet | ||
|
||
if vmin > vmax: | ||
vmin, vmax = vmax, vmin | ||
if vmax <= 0: | ||
warnings.warn( | ||
"Data has no positive values, and therefore cannot be " | ||
"log-scaled.") | ||
return 1, 10 | ||
|
||
minpos = self.axis.get_minpos() | ||
if not np.isfinite(minpos): | ||
minpos = 1e-300 # This should never take effect. | ||
if vmin <= 0: | ||
vmin = minpos | ||
if vmin == vmax: | ||
vmin = decade_down(vmin, self._base) | ||
vmax = decade_up(vmax, self._base) | ||
return vmin, vmax | ||
|
||
|
||
class SymmetricalLogLocator(Locator): | ||
|
@@ -2260,32 +2248,7 @@ def tick_values(self, vmin, vmax): | |
if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar': | ||
raise NotImplementedError('Polar axis cannot be logit scaled yet') | ||
|
||
# what to do if a window beyond ]0, 1[ is chosen | ||
if vmin <= 0.0: | ||
if self.axis is not None: | ||
vmin = self.axis.get_minpos() | ||
|
||
if (vmin <= 0.0) or (not np.isfinite(vmin)): | ||
raise ValueError( | ||
"Data has no values in ]0, 1[ and therefore can not be " | ||
"logit-scaled.") | ||
|
||
# NOTE: for vmax, we should query a property similar to get_minpos, but | ||
# related to the maximal, less-than-one data point. Unfortunately, | ||
# get_minpos is defined very deep in the BBox and updated with data, | ||
# so for now we use the trick below. | ||
if vmax >= 1.0: | ||
if self.axis is not None: | ||
vmax = 1 - self.axis.get_minpos() | ||
|
||
if (vmax >= 1.0) or (not np.isfinite(vmax)): | ||
raise ValueError( | ||
"Data has no values in ]0, 1[ and therefore can not be " | ||
"logit-scaled.") | ||
|
||
if vmax < vmin: | ||
vmin, vmax = vmax, vmin | ||
|
||
vmin, vmax = self.nonsingular(vmin, vmax) | ||
vmin = np.log10(vmin / (1 - vmin)) | ||
vmax = np.log10(vmax / (1 - vmax)) | ||
|
||
|
@@ -2320,6 +2283,36 @@ def tick_values(self, vmin, vmax): | |
|
||
return self.raise_if_exceeds(np.array(ticklocs)) | ||
|
||
def nonsingular(self, vmin, vmax): | ||
initial_range = (1e-7, 1 - 1e-7) | ||
if not np.isfinite(vmin) or not np.isfinite(vmax): | ||
return initial_range # no data plotted yet | ||
|
||
if vmin > vmax: | ||
vmin, vmax = vmax, vmin | ||
|
||
# what to do if a window beyond ]0, 1[ is chosen | ||
if self.axis is not None: | ||
minpos = self.axis.get_minpos() | ||
if not np.isfinite(minpos): | ||
return initial_range # again, no data plotted | ||
else: | ||
minpos = 1e-7 # should not occur in normal use | ||
|
||
# NOTE: for vmax, we should query a property similar to get_minpos, but | ||
# related to the maximal, less-than-one data point. Unfortunately, | ||
# Bbox._minpos is defined very deep in the BBox and updated with data, | ||
# so for now we use 1 - minpos as a substitute. | ||
|
||
if vmin <= 0: | ||
vmin = minpos | ||
if vmax >= 1: | ||
vmax = 1 - minpos | ||
if vmin == vmax: | ||
return 0.1 * vmin, 1 - 0.1 * vmin | ||
|
||
return vmin, vmax | ||
|
||
|
||
class AutoLocator(MaxNLocator): | ||
def __init__(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With Python 2.7, can use a set literal
{1}
.