diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 32def5d9cbc9..c95526ecf987 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -38,6 +38,27 @@ def test_integer(self, vmin, vmax, steps, expected): loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps) assert_almost_equal(loc.tick_values(vmin, vmax), expected) + @pytest.mark.parametrize('kwargs, errortype, match', [ + ({'foo': 0}, TypeError, + re.escape("set_params() got an unexpected keyword argument 'foo'")), + ({'steps': [2, 1]}, ValueError, "steps argument must be an increasing"), + ({'steps': 2}, ValueError, "steps argument must be an increasing"), + ({'steps': [2, 11]}, ValueError, "steps argument must be an increasing"), + ]) + def test_errors(self, kwargs, errortype, match): + with pytest.raises(errortype, match=match): + mticker.MaxNLocator(**kwargs) + + @pytest.mark.parametrize('steps, result', [ + ([1, 2, 10], [1, 2, 10]), + ([2, 10], [1, 2, 10]), + ([1, 2], [1, 2, 10]), + ([2], [1, 2, 10]), + ]) + def test_padding(self, steps, result): + loc = mticker.MaxNLocator(steps=steps) + assert (loc._steps == result).all() + class TestLinearLocator: def test_basic(self): @@ -45,6 +66,10 @@ def test_basic(self): test_value = np.array([-0.8, -0.3, 0.2]) assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value) + def test_zero_numticks(self): + loc = mticker.LinearLocator(numticks=0) + loc.tick_values(-0.8, 0.2) == [] + def test_set_params(self): """ Create linear locator with presets={}, numticks=2 and change it to @@ -55,6 +80,15 @@ def test_set_params(self): assert loc.numticks == 8 assert loc.presets == {(0, 1): []} + def test_presets(self): + loc = mticker.LinearLocator(presets={(1, 2): [1, 1.25, 1.75], + (0, 2): [0.5, 1.5]}) + assert loc.tick_values(1, 2) == [1, 1.25, 1.75] + assert loc.tick_values(2, 1) == [1, 1.25, 1.75] + assert loc.tick_values(0, 2) == [0.5, 1.5] + assert loc.tick_values(0.0, 2.0) == [0.5, 1.5] + assert (loc.tick_values(0, 1) == np.linspace(0, 1, 11)).all() + class TestMultipleLocator: def test_basic(self): @@ -590,6 +624,23 @@ def test_values(self, vmin, vmax, expected): ticks = sym.tick_values(vmin=vmin, vmax=vmax) assert_array_equal(ticks, expected) + def test_subs(self): + sym = mticker.SymmetricalLogLocator(base=10, linthresh=1, subs=[2.0, 4.0]) + sym.create_dummy_axis() + sym.axis.set_view_interval(-10, 10) + assert (sym() == [-20., -40., -2., -4., 0., 2., 4., 20., 40.]).all() + + def test_extending(self): + sym = mticker.SymmetricalLogLocator(base=10, linthresh=1) + sym.create_dummy_axis() + sym.axis.set_view_interval(8, 9) + assert (sym() == [1.0]).all() + sym.axis.set_view_interval(8, 12) + assert (sym() == [1.0, 10.0]).all() + assert sym.view_limits(10, 10) == (1, 100) + assert sym.view_limits(-10, -10) == (-100, -1) + assert sym.view_limits(0, 0) == (-0.001, 0.001) + class TestAsinhLocator: def test_init(self): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index b4793929de9b..df848ef04ad8 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -657,12 +657,12 @@ def format_data(self, value): # docstring inherited e = math.floor(math.log10(abs(value))) s = round(value / 10**e, 10) - exponent = self._format_maybe_minus_and_locale("%d", e) significand = self._format_maybe_minus_and_locale( "%d" if s % 1 == 0 else "%1.10g", s) if e == 0: return significand - elif self._useMathText or self._usetex: + exponent = self._format_maybe_minus_and_locale("%d", e) + if self._useMathText or self._usetex: exponent = "10^{%s}" % exponent return (exponent if s == 1 # reformat 1x10^y as 10^y else rf"{significand} \times {exponent}") @@ -675,7 +675,6 @@ def get_offset(self): """ if len(self.locs) == 0: return '' - s = '' if self.orderOfMagnitude or self.offset: offsetStr = '' sciNotStr = '' @@ -694,8 +693,8 @@ def get_offset(self): s = fr'${sciNotStr}\mathdefault{{{offsetStr}}}$' else: s = ''.join((sciNotStr, offsetStr)) - - return self.fix_minus(s) + return self.fix_minus(s) + return '' def set_locs(self, locs): # docstring inherited @@ -1055,9 +1054,6 @@ def _non_decade_format(self, sign_string, base, fx, usetex): def __call__(self, x, pos=None): # docstring inherited - usetex = mpl.rcParams['text.usetex'] - min_exp = mpl.rcParams['axes.formatter.min_exponent'] - if x == 0: # Symlog return r'$\mathdefault{0}$' @@ -1070,23 +1066,25 @@ def __call__(self, x, pos=None): is_x_decade = _is_close_to_int(fx) exponent = round(fx) if is_x_decade else np.floor(fx) coeff = round(b ** (fx - exponent)) - if is_x_decade: - fx = round(fx) if self.labelOnlyBase and not is_x_decade: return '' if self._sublabels is not None and coeff not in self._sublabels: return '' + if is_x_decade: + fx = round(fx) + # 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 abs(fx) < min_exp: + if abs(fx) < mpl.rcParams['axes.formatter.min_exponent']: return r'$\mathdefault{%s%g}$' % (sign_string, x) elif not is_x_decade: + usetex = mpl.rcParams['text.usetex'] return self._non_decade_format(sign_string, base, fx, usetex) else: return r'$\mathdefault{%s%s^{%d}}$' % (sign_string, base, fx) @@ -1554,7 +1552,7 @@ def symbol(self): symbol = self._symbol if not symbol: symbol = '' - elif mpl.rcParams['text.usetex'] and not self._is_latex: + elif not self._is_latex and mpl.rcParams['text.usetex']: # Source: http://www.personal.ceu.hu/tex/specchar.htm # Backslash must be first for this to work correctly since # it keeps getting added in @@ -1801,8 +1799,6 @@ def __call__(self): def tick_values(self, vmin, vmax): vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) - if vmax < vmin: - vmin, vmax = vmax, vmin if (vmin, vmax) in self.presets: return self.presets[(vmin, vmax)] @@ -2356,13 +2352,13 @@ def tick_values(self, vmin, vmax): numdec = math.floor(log_vmax) - math.ceil(log_vmin) if isinstance(self._subs, str): - _first = 2.0 if self._subs == 'auto' else 1.0 if numdec > 10 or b < 3: if self._subs == 'auto': return np.array([]) # no minor or major ticks else: subs = np.array([1.0]) # major ticks else: + _first = 2.0 if self._subs == 'auto' else 1.0 subs = np.arange(_first, b) else: subs = self._subs @@ -2386,23 +2382,14 @@ def tick_values(self, vmin, vmax): decades = np.arange(math.floor(log_vmin) - stride, math.ceil(log_vmax) + 2 * stride, stride) - if hasattr(self, '_transform'): - ticklocs = self._transform.inverted().transform(decades) - if have_subs: - if stride == 1: - ticklocs = np.ravel(np.outer(subs, ticklocs)) - else: - # No ticklocs if we have >1 decade between major ticks. - ticklocs = np.array([]) - else: - if have_subs: - if stride == 1: - ticklocs = np.concatenate( - [subs * decade_start for decade_start in b ** decades]) - else: - ticklocs = np.array([]) + if have_subs: + if stride == 1: + ticklocs = np.concatenate( + [subs * decade_start for decade_start in b ** decades]) else: - ticklocs = b ** decades + ticklocs = np.array([]) + else: + ticklocs = b ** decades _log.debug('ticklocs %r', ticklocs) if (len(subs) > 1 @@ -2422,8 +2409,8 @@ def view_limits(self, vmin, vmax): vmin, vmax = self.nonsingular(vmin, vmax) if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': - vmin = _decade_less_equal(vmin, self._base) - vmax = _decade_greater_equal(vmax, self._base) + vmin = _decade_less_equal(vmin, b) + vmax = _decade_greater_equal(vmax, b) return vmin, vmax @@ -2503,7 +2490,6 @@ def __call__(self): return self.tick_values(vmin, vmax) def tick_values(self, vmin, vmax): - base = self._base linthresh = self._linthresh if vmax < vmin: @@ -2540,6 +2526,8 @@ def tick_values(self, vmin, vmax): # Check if linear range is present has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh) + base = self._base + def get_log_range(lo, hi): lo = np.floor(np.log(lo) / np.log(base)) hi = np.ceil(np.log(hi) / np.log(base)) @@ -2573,11 +2561,7 @@ def get_log_range(lo, hi): if has_c: decades.extend(base ** (np.arange(c_lo, c_hi, stride))) - # Add the subticks if requested - if self._subs is None: - subs = np.arange(2.0, base) - else: - subs = np.asarray(self._subs) + subs = np.asarray(self._subs) if len(subs) > 1 or subs[0] != 1.0: ticklocs = [] @@ -2604,8 +2588,7 @@ def view_limits(self, vmin, vmax): vmin = _decade_less(vmin, b) vmax = _decade_greater(vmax, b) - result = mtransforms.nonsingular(vmin, vmax) - return result + return mtransforms.nonsingular(vmin, vmax) class AsinhLocator(Locator): @@ -2773,7 +2756,7 @@ def tick_values(self, vmin, vmax): # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ... # b-scale : ... -3 -2 -1 0 1 2 3 ... def ideal_ticks(x): - return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 1 / 2 + return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 0.5 vmin, vmax = self.nonsingular(vmin, vmax) binf = int(