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

Skip to content

Commit e3de3be

Browse files
committed
BUG: fix minpos handling and other log ticker problems
Closes #7595, #7493, #7587. Bbox._minpos is now initialized to [np.inf, np.inf] instead of [1e-7, 1e-7]. Old code with a colorbar in which the formatter is set to a LogFormatter and a SymlogNorm is used now works again (although the results are better in 2.0 if the colorbar is left to handle the formatter automatically). LogLocator now has its own nonsingular() method which provides a reasonable starting point for a log axis when no data have been plotted.
1 parent e09a6f7 commit e3de3be

File tree

3 files changed

+74
-47
lines changed

3 files changed

+74
-47
lines changed

lib/matplotlib/scale.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
264264
"""
265265
Limit the domain to positive values.
266266
"""
267+
if not np.isfinite(minpos):
268+
minpos = 1e-300 # This value should rarely if ever
269+
# end up with a visible effect.
270+
267271
return (minpos if vmin <= 0 else vmin,
268272
minpos if vmax <= 0 else vmax)
269273

@@ -499,7 +503,10 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
499503
"""
500504
Limit the domain to values between 0 and 1 (excluded).
501505
"""
502-
return (minpos if vmin <= 0 else minpos,
506+
if not np.isfinite(minpos):
507+
minpos = 1e-7 # This value should rarely if ever
508+
# end up with a visible effect.
509+
return (minpos if vmin <= 0 else vmin,
503510
1 - minpos if vmax >= 1 else vmax)
504511

505512

lib/matplotlib/ticker.py

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,16 @@ def set_locs(self, locs=None):
914914
except AttributeError:
915915
pass
916916

917+
if vmin > vmax:
918+
vmin, vmax = vmax, vmin
919+
920+
if linthresh is None and vmin <= 0:
921+
# It's probably a colorbar with
922+
# a format kwarg setting a LogFormatter in the manner
923+
# that worked with 1.5.x, but that doesn't work now.
924+
self._sublabels = set((1,)) # label powers of base
925+
return
926+
917927
if linthresh is not None: # symlog
918928
# Only compute the number of decades in the logarithmic part of the
919929
# axis
@@ -951,7 +961,7 @@ def __call__(self, x, pos=None):
951961
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
952962
d = abs(vmax - vmin)
953963
b = self._base
954-
if x == 0.0:
964+
if x == 0.0: # Symlog
955965
return '0'
956966
sign = np.sign(x)
957967
x = abs(x)
@@ -2032,36 +2042,41 @@ def view_limits(self, vmin, vmax):
20322042
'Try to choose the view limits intelligently'
20332043
b = self._base
20342044

2035-
if vmax < vmin:
2036-
vmin, vmax = vmax, vmin
2045+
vmin, vmax = self.nonsingular(vmin, vmax)
20372046

20382047
if self.axis.axes.name == 'polar':
20392048
vmax = math.ceil(math.log(vmax) / math.log(b))
20402049
vmin = b ** (vmax - self.numdecs)
2041-
return vmin, vmax
2042-
2043-
minpos = self.axis.get_minpos()
2044-
2045-
if minpos <= 0 or not np.isfinite(minpos):
2046-
raise ValueError(
2047-
"Data has no positive values, and therefore can not be "
2048-
"log-scaled.")
2049-
2050-
if vmin <= 0:
2051-
vmin = minpos
20522050

20532051
if rcParams['axes.autolimit_mode'] == 'round_numbers':
20542052
if not is_decade(vmin, self._base):
20552053
vmin = decade_down(vmin, self._base)
20562054
if not is_decade(vmax, self._base):
20572055
vmax = decade_up(vmax, self._base)
20582056

2059-
if vmin == vmax:
2060-
vmin = decade_down(vmin, self._base)
2061-
vmax = decade_up(vmax, self._base)
2057+
return vmin, vmax
20622058

2063-
result = mtransforms.nonsingular(vmin, vmax)
2064-
return result
2059+
def nonsingular(self, vmin, vmax):
2060+
if not np.isfinite(vmin) or not np.isfinite(vmax):
2061+
return 1, 10 # initial range, no data plotted yet
2062+
2063+
if vmin > vmax:
2064+
vmin, vmax = vmax, vmin
2065+
if vmax <= 0:
2066+
warnings.warn(
2067+
"Data has no positive values, and therefore cannot be "
2068+
"log-scaled.")
2069+
return 1, 10
2070+
2071+
minpos = self.axis.get_minpos()
2072+
if not np.isfinite(minpos):
2073+
minpos = 1e-300 # This should never take effect.
2074+
if vmin <= 0:
2075+
vmin = minpos
2076+
if vmin == vmax:
2077+
vmin = decade_down(vmin, self._base)
2078+
vmax = decade_up(vmax, self._base)
2079+
return vmin, vmax
20652080

20662081

20672082
class SymmetricalLogLocator(Locator):
@@ -2260,32 +2275,7 @@ def tick_values(self, vmin, vmax):
22602275
if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar':
22612276
raise NotImplementedError('Polar axis cannot be logit scaled yet')
22622277

2263-
# what to do if a window beyond ]0, 1[ is chosen
2264-
if vmin <= 0.0:
2265-
if self.axis is not None:
2266-
vmin = self.axis.get_minpos()
2267-
2268-
if (vmin <= 0.0) or (not np.isfinite(vmin)):
2269-
raise ValueError(
2270-
"Data has no values in ]0, 1[ and therefore can not be "
2271-
"logit-scaled.")
2272-
2273-
# NOTE: for vmax, we should query a property similar to get_minpos, but
2274-
# related to the maximal, less-than-one data point. Unfortunately,
2275-
# get_minpos is defined very deep in the BBox and updated with data,
2276-
# so for now we use the trick below.
2277-
if vmax >= 1.0:
2278-
if self.axis is not None:
2279-
vmax = 1 - self.axis.get_minpos()
2280-
2281-
if (vmax >= 1.0) or (not np.isfinite(vmax)):
2282-
raise ValueError(
2283-
"Data has no values in ]0, 1[ and therefore can not be "
2284-
"logit-scaled.")
2285-
2286-
if vmax < vmin:
2287-
vmin, vmax = vmax, vmin
2288-
2278+
vmin, vmax = self.nonsingular(vmin, vmax)
22892279
vmin = np.log10(vmin / (1 - vmin))
22902280
vmax = np.log10(vmax / (1 - vmax))
22912281

@@ -2320,6 +2310,36 @@ def tick_values(self, vmin, vmax):
23202310

23212311
return self.raise_if_exceeds(np.array(ticklocs))
23222312

2313+
def nonsingular(self, vmin, vmax):
2314+
initial_range = (1e-7, 1 - 1e-7)
2315+
if not np.isfinite(vmin) or not np.isfinite(vmax):
2316+
return initial_range # no data plotted yet
2317+
2318+
if vmin > vmax:
2319+
vmin, vmax = vmax, vmin
2320+
2321+
# what to do if a window beyond ]0, 1[ is chosen
2322+
if self.axis is not None:
2323+
minpos = self.axis.get_minpos()
2324+
if not np.isfinite(minpos):
2325+
return initial_range # again, no data plotted
2326+
else:
2327+
minpos = 1e-7 # should not occur in normal use
2328+
2329+
# NOTE: for vmax, we should query a property similar to get_minpos, but
2330+
# related to the maximal, less-than-one data point. Unfortunately,
2331+
# Bbox._minpos is defined very deep in the BBox and updated with data,
2332+
# so for now we use 1 - minpos as a substitute.
2333+
2334+
if vmin <= 0:
2335+
vmin = minpos
2336+
if vmax >= 1:
2337+
vmax = 1 - minpos
2338+
if vmin == vmax:
2339+
return 0.1 * vmin, 1 - 0.1 * vmin
2340+
2341+
return vmin, vmax
2342+
23232343

23242344
class AutoLocator(MaxNLocator):
23252345
def __init__(self):

lib/matplotlib/transforms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ def __init__(self, points, **kwargs):
792792
raise ValueError('Bbox points must be of the form '
793793
'"[[x0, y0], [x1, y1]]".')
794794
self._points = points
795-
self._minpos = np.array([0.0000001, 0.0000001])
795+
self._minpos = np.array([np.inf, np.inf])
796796
self._ignore = True
797797
# it is helpful in some contexts to know if the bbox is a
798798
# default or has been mutated; we store the orig points to

0 commit comments

Comments
 (0)