diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a5a248c156bd..f0d3fa2d7462 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1093,6 +1093,7 @@ def validate_animation_writer_path(p): 'axes.formatter.use_locale': [False, validate_bool], # Use the current locale to format ticks 'axes.formatter.use_mathtext': [False, validate_bool], + 'axes.formatter.min_exponent': [0, validate_int], # minimum exponent to format in scientific notation 'axes.formatter.useoffset': [True, validate_bool], 'axes.formatter.offset_threshold': [4, validate_int], 'axes.unicode_minus': [True, validate_bool], diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index d6eee0d6d7bd..346ad27f1b65 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -332,6 +332,26 @@ def test_blank(self): assert formatter(10**0.1) == '' +class TestLogFormatterMathtext(): + fmt = mticker.LogFormatterMathtext() + test_data = [ + (0, 1, '$\\mathdefault{10^{0}}$'), + (0, 1e-2, '$\\mathdefault{10^{-2}}$'), + (0, 1e2, '$\\mathdefault{10^{2}}$'), + (3, 1, '$\\mathdefault{1}$'), + (3, 1e-2, '$\\mathdefault{0.01}$'), + (3, 1e2, '$\\mathdefault{100}$'), + (3, 1e-3, '$\\mathdefault{10^{-3}}$'), + (3, 1e3, '$\\mathdefault{10^{3}}$'), + ] + + @pytest.mark.parametrize('min_exponent, value, expected', test_data) + def test_min_exponent(self, min_exponent, value, expected): + with matplotlib.rc_context({'axes.formatter.min_exponent': + min_exponent}): + assert self.fmt(value) == expected + + class TestLogFormatterSciNotation(object): test_data = [ (2, 0.03125, '${2^{-5}}$'), diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index ddfae88619a5..cec1476a99d4 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1084,7 +1084,7 @@ def _non_decade_format(self, sign_string, base, fx, usetex): return (r'$%s%s^{%.2f}$') % (sign_string, base, fx) else: return ('$%s$' % _mathdefault('%s%s^{%.2f}' % - (sign_string, base, fx))) + (sign_string, base, fx))) def __call__(self, x, pos=None): """ @@ -1093,6 +1093,8 @@ def __call__(self, x, pos=None): The position `pos` is ignored. """ usetex = rcParams['text.usetex'] + min_exp = rcParams['axes.formatter.min_exponent'] + if x == 0: # Symlog if usetex: return '$0$' @@ -1108,6 +1110,8 @@ def __call__(self, x, pos=None): 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 is_x_decade: + fx = nearest_long(fx) if self.labelOnlyBase and not is_x_decade: return '' @@ -1120,7 +1124,13 @@ def __call__(self, x, pos=None): else: base = '%s' % b - if not is_x_decade: + if np.abs(fx) < min_exp: + if usetex: + return r'${0}{1:g}$'.format(sign_string, x) + else: + return '${0}$'.format(_mathdefault( + '{0}{1:g}'.format(sign_string, x))) + elif not is_x_decade: return self._non_decade_format(sign_string, base, fx, usetex) else: if usetex: diff --git a/matplotlibrc.template b/matplotlibrc.template index e57dfd9ada2d..9568e27d10d2 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -315,6 +315,7 @@ backend : $TEMPLATE_BACKEND # separator in the fr_FR locale. #axes.formatter.use_mathtext : False # When True, use mathtext for scientific # notation. +#axes.formatter.min_exponent: 0 # minimum exponent to format in scientific notation #axes.formatter.useoffset : True # If True, the tick label formatter # will default to labeling ticks relative # to an offset when the data range is