From 1320261881b990ad070620a2c1a06c1defc5cd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Thu, 1 Oct 2015 12:44:28 +0100 Subject: [PATCH 1/9] add LogFormatterSciNotation move print logic from fromatter to locator move label logic to independent function remove redundancy LogFormatterSciNotation is now a subclass of LogFormatterMathtext, and the differences (how labels are formatted for non-decade ticks) have been factored out in a `_non_decade_format` function. fix default showLabel bool --- lib/matplotlib/axis.py | 6 ++-- lib/matplotlib/scale.py | 10 +++--- lib/matplotlib/ticker.py | 71 +++++++++++++++++++++++++++++++++++----- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 276c09605a70..f29978728860 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -889,15 +889,17 @@ def iter_ticks(self): Iterate through all of the major and minor ticks. """ majorLocs = self.major.locator() + hasLabel = self.major.locator.show_tick_label(majorLocs) majorTicks = self.get_major_ticks(len(majorLocs)) self.major.formatter.set_locs(majorLocs) - majorLabels = [self.major.formatter(val, i) + majorLabels = [self.major.formatter(val, i) if hasLabel[i] else '' for i, val in enumerate(majorLocs)] minorLocs = self.minor.locator() + hasLabel = self.major.locator.show_tick_label(minorLocs) minorTicks = self.get_minor_ticks(len(minorLocs)) self.minor.formatter.set_locs(minorLocs) - minorLabels = [self.minor.formatter(val, i) + minorLabels = [self.minor.formatter(val, i) if hasLabel[i] else '' for i, val in enumerate(minorLocs)] major_minor = [ diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 7193d1e7edf6..d1b9d4dca597 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -8,7 +8,7 @@ from matplotlib.cbook import dedent from matplotlib.ticker import (NullFormatter, ScalarFormatter, - LogFormatterMathtext, LogitFormatter) + LogFormatterSciNotation, LogitFormatter) from matplotlib.ticker import (NullLocator, LogLocator, AutoLocator, SymmetricalLogLocator, LogitLocator) from matplotlib.transforms import Transform, IdentityTransform @@ -304,9 +304,9 @@ def set_default_locators_and_formatters(self, axis): log scaling. """ axis.set_major_locator(LogLocator(self.base)) - axis.set_major_formatter(LogFormatterMathtext(self.base)) + axis.set_major_formatter(LogFormatterSciNotation(self.base)) axis.set_minor_locator(LogLocator(self.base, self.subs)) - axis.set_minor_formatter(NullFormatter()) + axis.set_minor_formatter(LogFormatterSciNotation(self.base)) def get_transform(self): """ @@ -462,10 +462,10 @@ def set_default_locators_and_formatters(self, axis): symmetrical log scaling. """ axis.set_major_locator(SymmetricalLogLocator(self.get_transform())) - axis.set_major_formatter(LogFormatterMathtext(self.base)) + axis.set_major_formatter(LogFormatterSciNotation(self.base)) axis.set_minor_locator(SymmetricalLogLocator(self.get_transform(), self.subs)) - axis.set_minor_formatter(NullFormatter()) + axis.set_minor_formatter(LogFormatterSciNotation(self.base)) def get_transform(self): """ diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index a2be7cbd805e..0996f27e2465 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -910,7 +910,6 @@ def pprint_val(self, x, d): s = s.rstrip('0').rstrip('.') return s - class LogFormatterExponent(LogFormatter): """ Format values for log axis using ``exponent = log_base(value)``. @@ -951,6 +950,14 @@ class LogFormatterMathtext(LogFormatter): Format values for log axis using ``exponent = log_base(value)``. """ + def _non_decade_format(self, sign_string, base, fx, usetex): + 'Return string for non-decade locations' + if usetex: + return (r'$%s%s^{%.2f}$') % (sign_string, base, fx) + else: + return ('$%s$' % _mathdefault('%s%s^{%.2f}' % + (sign_string, base, fx))) + def __call__(self, x, pos=None): """ Return the format for tick value `x`. @@ -981,13 +988,7 @@ def __call__(self, x, pos=None): if not is_decade and self.labelOnlyBase: return '' elif not is_decade: - if usetex: - return (r'$%s%s^{%.2f}$') % \ - (sign_string, base, fx) - else: - return ('$%s$' % _mathdefault( - '%s%s^{%.2f}' % - (sign_string, base, fx))) + return self._non_decade_format(sign_string, base, fx, usetex) else: if usetex: return (r'$%s%s^{%d}$') % (sign_string, @@ -998,6 +999,28 @@ def __call__(self, x, pos=None): '%s%s^{%d}' % (sign_string, base, nearest_long(fx)))) +class LogFormatterSciNotation(LogFormatterMathtext): + """ + Format values following scientific notation in a logarithmic axis + """ + + def __init__(self, base=10.0, labelOnlyBase=False): + super(LogFormatterSciNotation, self).__init__(base=base, + labelOnlyBase=labelOnlyBase) + + def _non_decade_format(self, sign_string, base, fx, usetex): + 'Return string for non-decade locations' + b = float(base) + exponent = math.floor(fx) + coeff = b ** fx / b ** exponent + if is_close_to_int(coeff): + coeff = nearest_long(coeff) + if usetex: + return (r'$%g\times%s^{%d}$') % \ + (coeff, base, exponent) + else: + return (r'$\mathdefault{%g\times%s^{%d}}$') % \ + (coeff, base, exponent) class LogitFormatter(Formatter): """ @@ -1250,6 +1273,11 @@ def __call__(self): # hence there is no *one* interface to call self.tick_values. raise NotImplementedError('Derived must override') + def show_tick_label(self, locs): + """Return boolean array on whether to show a label for the given + locations""" + return np.ones(np.asarray(locs).size, dtype=np.bool) + def raise_if_exceeds(self, locs): """raise a RuntimeError if Locator attempts to create more than MAXTICKS locs""" @@ -1894,6 +1922,33 @@ def tick_values(self, vmin, vmax): return self.raise_if_exceeds(np.asarray(ticklocs)) + def show_tick_label(self, ticklocs): + b = self._base + + vmin, vmax = self.axis.get_view_interval() + vmin = math.log(vmin) / math.log(b) + vmax = math.log(vmax) / math.log(b) + + numdec = abs(vmax - vmin) + + if numdec > 3: + sublabel = set((1)) + elif numdec > 2: + sublabel = set((1, 3)) + elif numdec > 1: + sublabel = set((1, 2, 5)) + else: + sublabel = set((1, 2, 4, 7)) + + label = np.ones(ticklocs.size, dtype=np.bool) + for i, loc in enumerate(ticklocs): + exponent = math.floor(math.log(abs(loc)) / math.log(b)) + coeff = loc / b ** nearest_long(exponent) + if nearest_long(coeff) not in sublabel: + label[i] = False + + return label + def view_limits(self, vmin, vmax): 'Try to choose the view limits intelligently' b = self._base From 56e28716d8a46ae66087351ababe590581dba1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Fri, 9 Oct 2015 15:49:09 +0100 Subject: [PATCH 2/9] manage locators not deriving from LocatorBase --- lib/matplotlib/axis.py | 10 ++++++++-- lib/matplotlib/ticker.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index f29978728860..b47c8bb7535b 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -889,14 +889,20 @@ def iter_ticks(self): Iterate through all of the major and minor ticks. """ majorLocs = self.major.locator() - hasLabel = self.major.locator.show_tick_label(majorLocs) + try: + hasLabel = self.major.locator.show_tick_label(majorLocs) + except AttributeError: + hasLabel = np.ones(np.asarray(majorLocs).size, dtype=np.bool) majorTicks = self.get_major_ticks(len(majorLocs)) self.major.formatter.set_locs(majorLocs) majorLabels = [self.major.formatter(val, i) if hasLabel[i] else '' for i, val in enumerate(majorLocs)] minorLocs = self.minor.locator() - hasLabel = self.major.locator.show_tick_label(minorLocs) + try: + hasLabel = self.major.locator.show_tick_label(minorLocs) + except AttributeError: + hasLabel = np.ones(np.asarray(minorLocs).size, dtype=np.bool) minorTicks = self.get_minor_ticks(len(minorLocs)) self.minor.formatter.set_locs(minorLocs) minorLabels = [self.minor.formatter(val, i) if hasLabel[i] else '' diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 0996f27e2465..860ed74df191 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1932,7 +1932,7 @@ def show_tick_label(self, ticklocs): numdec = abs(vmax - vmin) if numdec > 3: - sublabel = set((1)) + sublabel = set((1,)) elif numdec > 2: sublabel = set((1, 3)) elif numdec > 1: From a6e954d87acd83b6f96cd166b25b8cfb4f487cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Thu, 15 Oct 2015 18:04:58 +0100 Subject: [PATCH 3/9] generalize label location for all bases fix pep8 fix close to decade coefficients --- lib/matplotlib/ticker.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 860ed74df191..cb16f405f60d 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -910,6 +910,7 @@ def pprint_val(self, x, d): s = s.rstrip('0').rstrip('.') return s + class LogFormatterExponent(LogFormatter): """ Format values for log axis using ``exponent = log_base(value)``. @@ -999,6 +1000,7 @@ def __call__(self, x, pos=None): '%s%s^{%d}' % (sign_string, base, nearest_long(fx)))) + class LogFormatterSciNotation(LogFormatterMathtext): """ Format values following scientific notation in a logarithmic axis @@ -1022,6 +1024,7 @@ def _non_decade_format(self, sign_string, base, fx, usetex): return (r'$\mathdefault{%g\times%s^{%d}}$') % \ (coeff, base, exponent) + class LogitFormatter(Formatter): """ Probability formatter (using Math text). @@ -1932,22 +1935,20 @@ def show_tick_label(self, ticklocs): numdec = abs(vmax - vmin) if numdec > 3: + # Label only bases sublabel = set((1,)) - elif numdec > 2: - sublabel = set((1, 3)) - elif numdec > 1: - sublabel = set((1, 2, 5)) else: - sublabel = set((1, 2, 4, 7)) + # Add labels between bases at log-spaced coefficients + c = np.logspace(0, 1, (4 - int(numdec)) + 1, base=b) + sublabel = set(np.round(c)) - label = np.ones(ticklocs.size, dtype=np.bool) - for i, loc in enumerate(ticklocs): - exponent = math.floor(math.log(abs(loc)) / math.log(b)) - coeff = loc / b ** nearest_long(exponent) - if nearest_long(coeff) not in sublabel: - label[i] = False + fx = np.log(abs(ticklocs)) / np.log(b) + exponents = np.array([np.round(x) if is_close_to_int(x) + else np.floor(x) + for x in fx]) + coeffs = np.round(ticklocs / b ** exponents) - return label + return [c in sublabel for c in coeffs] def view_limits(self, vmin, vmax): 'Try to choose the view limits intelligently' From 4ce70e9116df710cfd6175fd2a8148bdc95f90ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Fri, 22 Jan 2016 16:02:20 +0000 Subject: [PATCH 4/9] use _mathdefault in non-usetex mathtext --- lib/matplotlib/ticker.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index cb16f405f60d..62eae3af6b64 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -953,11 +953,11 @@ class LogFormatterMathtext(LogFormatter): def _non_decade_format(self, sign_string, base, fx, usetex): 'Return string for non-decade locations' - if usetex: - return (r'$%s%s^{%.2f}$') % (sign_string, base, fx) - else: - return ('$%s$' % _mathdefault('%s%s^{%.2f}' % - (sign_string, base, fx))) + if usetex: + return (r'$%s%s^{%.2f}$') % (sign_string, base, fx) + else: + return ('$%s$' % _mathdefault('%s%s^{%.2f}' % + (sign_string, base, fx))) def __call__(self, x, pos=None): """ @@ -1021,8 +1021,8 @@ def _non_decade_format(self, sign_string, base, fx, usetex): return (r'$%g\times%s^{%d}$') % \ (coeff, base, exponent) else: - return (r'$\mathdefault{%g\times%s^{%d}}$') % \ - (coeff, base, exponent) + return ('$%s$' % _mathdefault(r'%g\times%s^{%d}' % + (coeff, base, exponent))) class LogitFormatter(Formatter): From d1a5aff5a3676925b1eb81f7b18e58c8dd34f7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Sun, 24 Jul 2016 17:27:40 +0100 Subject: [PATCH 5/9] add tests add cleanup for test --- lib/matplotlib/tests/test_ticker.py | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 59deeaef892a..01a7a9de0196 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -50,6 +50,15 @@ def test_AutoMinorLocator(): assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value) +def _sub_labels(axis, subs=()): + "Test whether locator marks subs to be labeled" + loc = axis.get_minor_locator() + minor_tlocs = axis.get_minorticklocs() + coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs))) + label = [np.round(c) in subs for c in coefs] + assert_equal(loc.show_tick_label(minor_tlocs), label) + +@cleanup def test_LogLocator(): loc = mticker.LogLocator(numticks=5) assert_raises(ValueError, loc.tick_values, 0, 1000) @@ -63,6 +72,31 @@ def test_LogLocator(): test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.]) assert_almost_equal(loc.tick_values(1, 100), test_value) + # test label locator + fig, ax = plt.subplots() + ax.set_xscale('log') + ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[])) + ax.xaxis.set_minor_locator(mticker.LogLocator(base=10, + subs=np.arange(2, 10))) + # axis range above 3 decades, only bases are labeled + ax.set_xlim(1, 1e4) + loc = ax.xaxis.get_major_locator() + show_major_labels = loc.show_tick_label(ax.xaxis.get_majorticklocs()) + assert np.all(show_major_labels) + _sub_labels(ax.xaxis, subs=[]) + + # axis range at 2 to 3 decades, label sub 3 + ax.set_xlim(1, 800) + _sub_labels(ax.xaxis, subs=[3]) + + # axis range at 1 to 2 decades, label subs 2 and 5 + ax.set_xlim(1, 80) + _sub_labels(ax.xaxis, subs=[2, 5]) + + # axis range at 0 to 1 decades, label subs 2, 3, 6 + ax.set_xlim(1, 8) + _sub_labels(ax.xaxis, subs=[2, 3, 6]) + def test_LinearLocator_set_params(): """ @@ -252,6 +286,44 @@ def get_view_interval(self): yield _logfe_helper, formatter, base, locs, i, expected_result +def test_LogFormatterSciNotation(): + test_cases = { + 10: ( + (1e-05, '${10^{-5}}$'), + (1, '${10^{0}}$'), + (100000, '${10^{5}}$'), + (2e-05, '${2\\times10^{-5}}$'), + (2, '${2\\times10^{0}}$'), + (200000, '${2\\times10^{5}}$'), + (3.1415926535897935e-05, '${3.14159\\times10^{-5}}$'), + (3.141592653589793, '${3.14159\\times10^{0}}$'), + (314159.2653589793, '${3.14159\\times10^{5}}$'), + (5e-05, '${5\\times10^{-5}}$'), + (5, '${5\\times10^{0}}$'), + (500000, '${5\\times10^{5}}$'), + (8.5e-05, '${8.5\\times10^{-5}}$'), + (8.5, '${8.5\\times10^{0}}$'), + (850000.0, '${8.5\\times10^{5}}$'), + ), + 2: ( + (0.03125, '${2^{-5}}$'), + (1, '${2^{0}}$'), + (32, '${2^{5}}$'), + (0.0375, '${1.2\\times2^{-5}}$'), + (1.2, '${1.2\\times2^{0}}$'), + (38.4, '${1.2\\times2^{5}}$'), + (0.044194173824159223, '${1.41421\\times2^{-5}}$'), + (1.4142135623730951, '${1.41421\\times2^{0}}$'), + (45.254833995939045, '${1.41421\\times2^{5}}$')), + } + + for base in test_cases.keys(): + formatter = mticker.LogFormatterSciNotation(base=base) + for value, expected in test_cases[base]: + with matplotlib.rc_context({'text.usetex': False}): + nose.tools.assert_equal(formatter(value), expected) + + def _pprint_helper(value, domain, expected): fmt = mticker.LogFormatter() label = fmt.pprint_val(value, domain) From 4e83e9a2e8bab09a8c560b4243d88ac4733d1c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Tue, 2 Aug 2016 22:34:29 +0100 Subject: [PATCH 6/9] move labeling logic into LogFormatter --- lib/matplotlib/axis.py | 12 +--- lib/matplotlib/tests/test_ticker.py | 87 +++++++++++------------ lib/matplotlib/ticker.py | 103 ++++++++++++++-------------- 3 files changed, 96 insertions(+), 106 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index b47c8bb7535b..276c09605a70 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -889,23 +889,15 @@ def iter_ticks(self): Iterate through all of the major and minor ticks. """ majorLocs = self.major.locator() - try: - hasLabel = self.major.locator.show_tick_label(majorLocs) - except AttributeError: - hasLabel = np.ones(np.asarray(majorLocs).size, dtype=np.bool) majorTicks = self.get_major_ticks(len(majorLocs)) self.major.formatter.set_locs(majorLocs) - majorLabels = [self.major.formatter(val, i) if hasLabel[i] else '' + majorLabels = [self.major.formatter(val, i) for i, val in enumerate(majorLocs)] minorLocs = self.minor.locator() - try: - hasLabel = self.major.locator.show_tick_label(minorLocs) - except AttributeError: - hasLabel = np.ones(np.asarray(minorLocs).size, dtype=np.bool) minorTicks = self.get_minor_ticks(len(minorLocs)) self.minor.formatter.set_locs(minorLocs) - minorLabels = [self.minor.formatter(val, i) if hasLabel[i] else '' + minorLabels = [self.minor.formatter(val, i) for i, val in enumerate(minorLocs)] major_minor = [ diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 01a7a9de0196..8a4833d5ee40 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -50,15 +50,6 @@ def test_AutoMinorLocator(): assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value) -def _sub_labels(axis, subs=()): - "Test whether locator marks subs to be labeled" - loc = axis.get_minor_locator() - minor_tlocs = axis.get_minorticklocs() - coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs))) - label = [np.round(c) in subs for c in coefs] - assert_equal(loc.show_tick_label(minor_tlocs), label) - -@cleanup def test_LogLocator(): loc = mticker.LogLocator(numticks=5) assert_raises(ValueError, loc.tick_values, 0, 1000) @@ -72,31 +63,6 @@ def test_LogLocator(): test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.]) assert_almost_equal(loc.tick_values(1, 100), test_value) - # test label locator - fig, ax = plt.subplots() - ax.set_xscale('log') - ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[])) - ax.xaxis.set_minor_locator(mticker.LogLocator(base=10, - subs=np.arange(2, 10))) - # axis range above 3 decades, only bases are labeled - ax.set_xlim(1, 1e4) - loc = ax.xaxis.get_major_locator() - show_major_labels = loc.show_tick_label(ax.xaxis.get_majorticklocs()) - assert np.all(show_major_labels) - _sub_labels(ax.xaxis, subs=[]) - - # axis range at 2 to 3 decades, label sub 3 - ax.set_xlim(1, 800) - _sub_labels(ax.xaxis, subs=[3]) - - # axis range at 1 to 2 decades, label subs 2 and 5 - ax.set_xlim(1, 80) - _sub_labels(ax.xaxis, subs=[2, 5]) - - # axis range at 0 to 1 decades, label subs 2, 3, 6 - ax.set_xlim(1, 8) - _sub_labels(ax.xaxis, subs=[2, 3, 6]) - def test_LinearLocator_set_params(): """ @@ -241,6 +207,48 @@ def check_offset_for(left, right, offset): yield check_offset_for, right, left, offset +def _sub_labels(axis, subs=()): + "Test whether locator marks subs to be labeled" + fmt = axis.get_minor_formatter() + minor_tlocs = axis.get_minorticklocs() + fmt.set_locs(minor_tlocs) + coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs))) + label_expected = [np.round(c) in subs for c in coefs] + label_test = [fmt(x) != '' for x in minor_tlocs] + assert_equal(label_test, label_expected) + + +@cleanup +def test_LogFormatter_sublabel(): + # test label locator + fig, ax = plt.subplots() + ax.set_xscale('log') + ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[])) + ax.xaxis.set_minor_locator(mticker.LogLocator(base=10, + subs=np.arange(2, 10))) + ax.xaxis.set_major_formatter(mticker.LogFormatter()) + ax.xaxis.set_minor_formatter(mticker.LogFormatter(labelOnlyBase=False)) + # axis range above 3 decades, only bases are labeled + ax.set_xlim(1, 1e4) + fmt = ax.xaxis.get_major_formatter() + fmt.set_locs(ax.xaxis.get_majorticklocs()) + show_major_labels = [fmt(x) != '' for x in ax.xaxis.get_majorticklocs()] + assert np.all(show_major_labels) + _sub_labels(ax.xaxis, subs=[]) + + # axis range at 2 to 3 decades, label sub 3 + ax.set_xlim(1, 800) + _sub_labels(ax.xaxis, subs=[3]) + + # axis range at 1 to 2 decades, label subs 2 and 5 + ax.set_xlim(1, 80) + _sub_labels(ax.xaxis, subs=[2, 5]) + + # axis range at 0 to 1 decades, label subs 2, 3, 6 + ax.set_xlim(1, 8) + _sub_labels(ax.xaxis, subs=[2, 3, 6]) + + def _logfe_helper(formatter, base, locs, i, expected_result): vals = base**locs labels = [formatter(x, pos) for (x, pos) in zip(vals, i)] @@ -295,15 +303,9 @@ def test_LogFormatterSciNotation(): (2e-05, '${2\\times10^{-5}}$'), (2, '${2\\times10^{0}}$'), (200000, '${2\\times10^{5}}$'), - (3.1415926535897935e-05, '${3.14159\\times10^{-5}}$'), - (3.141592653589793, '${3.14159\\times10^{0}}$'), - (314159.2653589793, '${3.14159\\times10^{5}}$'), (5e-05, '${5\\times10^{-5}}$'), (5, '${5\\times10^{0}}$'), (500000, '${5\\times10^{5}}$'), - (8.5e-05, '${8.5\\times10^{-5}}$'), - (8.5, '${8.5\\times10^{0}}$'), - (850000.0, '${8.5\\times10^{5}}$'), ), 2: ( (0.03125, '${2^{-5}}$'), @@ -312,13 +314,12 @@ def test_LogFormatterSciNotation(): (0.0375, '${1.2\\times2^{-5}}$'), (1.2, '${1.2\\times2^{0}}$'), (38.4, '${1.2\\times2^{5}}$'), - (0.044194173824159223, '${1.41421\\times2^{-5}}$'), - (1.4142135623730951, '${1.41421\\times2^{0}}$'), - (45.254833995939045, '${1.41421\\times2^{5}}$')), + ) } for base in test_cases.keys(): formatter = mticker.LogFormatterSciNotation(base=base) + formatter.sublabel = set([1, 2, 5, 1.2]) for value, expected in test_cases[base]: with matplotlib.rc_context({'text.usetex': False}): nose.tools.assert_equal(formatter(value), expected) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 62eae3af6b64..961c017b2553 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -820,6 +820,7 @@ def __init__(self, base=10.0, labelOnlyBase=True): """ self._base = base + 0.0 self.labelOnlyBase = labelOnlyBase + self.sublabel = [1, ] def base(self, base): """ @@ -839,12 +840,28 @@ def label_minor(self, labelOnlyBase): """ self.labelOnlyBase = labelOnlyBase + def set_locs(self, locs): + b = self._base + + vmin, vmax = self.axis.get_view_interval() + self.d = abs(vmax - vmin) + vmin = math.log(vmin) / math.log(b) + vmax = math.log(vmax) / math.log(b) + + numdec = abs(vmax - vmin) + + if numdec > 3: + # Label only bases + self.sublabel = set((1,)) + else: + # Add labels between bases at log-spaced coefficients + c = np.logspace(0, 1, (4 - int(numdec)) + 1, base=b) + self.sublabel = set(np.round(c)) + def __call__(self, x, pos=None): """ Return the format for tick val `x` at position `pos`. """ - vmin, vmax = self.axis.get_view_interval() - d = abs(vmax - vmin) b = self._base if x == 0.0: return '0' @@ -852,16 +869,21 @@ def __call__(self, x, pos=None): # only label the decades fx = math.log(abs(x)) / math.log(b) isDecade = is_close_to_int(fx) - if not isDecade and self.labelOnlyBase: - s = '' - elif x > 10000: - s = '%1.0e' % x - elif x < 1: - s = '%1.0e' % x + exponent = np.round(fx) if isDecade else np.floor(fx) + coeff = np.round(x / b ** exponent) + if coeff in self.sublabel: + if not isDecade and self.labelOnlyBase: + s = '' + elif x > 10000: + s = '%1.0e' % x + elif x < 1: + s = '%1.0e' % x + else: + s = self.pprint_val(x, self.d) + if sign == -1: + s = '-%s' % s else: - s = self.pprint_val(x, d) - if sign == -1: - s = '-%s' % s + s = '' return self.fix_minus(s) @@ -977,6 +999,8 @@ def __call__(self, x, pos=None): fx = math.log(abs(x)) / math.log(b) is_decade = is_close_to_int(fx) + exponent = np.round(fx) if is_decade else np.floor(fx) + coeff = np.round(x / b ** exponent) sign_string = '-' if x < 0 else '' @@ -986,19 +1010,22 @@ def __call__(self, x, pos=None): else: base = '%s' % b - if not is_decade and self.labelOnlyBase: - return '' - elif not is_decade: - return self._non_decade_format(sign_string, base, fx, usetex) - else: - if usetex: - return (r'$%s%s^{%d}$') % (sign_string, - base, - nearest_long(fx)) + if coeff in self.sublabel: + if not is_decade and self.labelOnlyBase: + return '' + elif not is_decade: + return self._non_decade_format(sign_string, base, fx, usetex) else: - return ('$%s$' % _mathdefault( - '%s%s^{%d}' % - (sign_string, base, nearest_long(fx)))) + if usetex: + return (r'$%s%s^{%d}$') % (sign_string, + base, + nearest_long(fx)) + else: + return ('$%s$' % _mathdefault( + '%s%s^{%d}' % + (sign_string, base, nearest_long(fx)))) + else: + return '' class LogFormatterSciNotation(LogFormatterMathtext): @@ -1276,11 +1303,6 @@ def __call__(self): # hence there is no *one* interface to call self.tick_values. raise NotImplementedError('Derived must override') - def show_tick_label(self, locs): - """Return boolean array on whether to show a label for the given - locations""" - return np.ones(np.asarray(locs).size, dtype=np.bool) - def raise_if_exceeds(self, locs): """raise a RuntimeError if Locator attempts to create more than MAXTICKS locs""" @@ -1925,31 +1947,6 @@ def tick_values(self, vmin, vmax): return self.raise_if_exceeds(np.asarray(ticklocs)) - def show_tick_label(self, ticklocs): - b = self._base - - vmin, vmax = self.axis.get_view_interval() - vmin = math.log(vmin) / math.log(b) - vmax = math.log(vmax) / math.log(b) - - numdec = abs(vmax - vmin) - - if numdec > 3: - # Label only bases - sublabel = set((1,)) - else: - # Add labels between bases at log-spaced coefficients - c = np.logspace(0, 1, (4 - int(numdec)) + 1, base=b) - sublabel = set(np.round(c)) - - fx = np.log(abs(ticklocs)) / np.log(b) - exponents = np.array([np.round(x) if is_close_to_int(x) - else np.floor(x) - for x in fx]) - coeffs = np.round(ticklocs / b ** exponents) - - return [c in sublabel for c in coeffs] - def view_limits(self, vmin, vmax): 'Try to choose the view limits intelligently' b = self._base From ad0a7518701fd41aa4eb7258c6307f3cace9a66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Wed, 3 Aug 2016 22:43:28 +0100 Subject: [PATCH 7/9] fix numdec computation for symlog axes --- lib/matplotlib/scale.py | 2 +- lib/matplotlib/ticker.py | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index d1b9d4dca597..04dea31e2177 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -465,7 +465,7 @@ def set_default_locators_and_formatters(self, axis): axis.set_major_formatter(LogFormatterSciNotation(self.base)) axis.set_minor_locator(SymmetricalLogLocator(self.get_transform(), self.subs)) - axis.set_minor_formatter(LogFormatterSciNotation(self.base)) + axis.set_minor_formatter(NullFormatter()) def get_transform(self): """ diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 961c017b2553..5e9eecba6030 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -845,10 +845,24 @@ def set_locs(self, locs): vmin, vmax = self.axis.get_view_interval() self.d = abs(vmax - vmin) - vmin = math.log(vmin) / math.log(b) - vmax = math.log(vmax) / math.log(b) - numdec = abs(vmax - vmin) + if hasattr(self.axis.get_transform(), 'linthresh'): + t = self.axis.get_transform() + linthresh = t.linthresh + # Only compute the number of decades in the logarithmic part of the + # axis + numdec = 0 + if vmin < -linthresh: + numdec += math.log(-vmin / linthresh) / math.log(b) + + if vmax > linthresh and vmin < linthresh: + numdec += math.log(vmax / linthresh) / math.log(b) + elif vmin >= linthresh: + numdec += math.log(vmax / vmin) / math.log(b) + else: + vmin = math.log(vmin) / math.log(b) + vmax = math.log(vmax) / math.log(b) + numdec = abs(vmax - vmin) if numdec > 3: # Label only bases From f04bff0f7bf07afab12a1edf5f125055aeea6a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Wed, 3 Aug 2016 22:49:50 +0100 Subject: [PATCH 8/9] update baseline images for log_scales test --- .../baseline_images/test_axes/log_scales.pdf | Bin 6254 -> 7017 bytes .../baseline_images/test_axes/log_scales.png | Bin 13681 -> 16959 bytes .../baseline_images/test_axes/log_scales.svg | 226 ++++++++++++------ 3 files changed, 157 insertions(+), 69 deletions(-) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.pdf b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.pdf index c76b653c33fb119c3454bfa8d18aabf33cb805c8..03284a4e4ee09a2e4028ad0825f5873c8e8eca50 100644 GIT binary patch delta 3220 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 m^^ZuwL#75;1O;Lm7#LA!j~=#IIjEKJk zO$WjW3kYC2umL864y*=1E+R!!BmzVVII5gUU|>cv0)PR0;nXNW#27%I)_6kn025oE;ONu|;j`);TVG(ZFyOvI!8z%`9fh}fcK-p$T)2I^jda$$g=d#uRczQ>Y;-c(+@BLKxJh+f z`N%qu+Y0ZuuJU2F#eQ2&IUQj2oi@Kvw)=$3(WuM6t#P|f)neV!y^wDL?cL9azH!9E z^wco}>tjaYyYAKQY~~D|3TjwvlraBh&0z7$5@nDjGI)>X=i6{HOwvH@ZY~{J7BYKP z>GC7T_g`7;+HW8_b8hV(zulV)^BqMNdjrD#X6d9f*ey9UM2&CkF$J9xeFijr&-=v4 z+A=MUDag&8a#rj>QEPrnbhKxe`V#l$p|iYO-Z1`=KHrM}B%5rsQTugZZs>t0`ZuE> zhr#SOC(2_DgPMgi>N;&QJ@iLKt4cR|tJvKfYu%=9bOchO4V&Zbt`v0T7txRC zS88x=NKd!r>X@YLOh_)`JUHc{xj8bW(l}8gwf*%eZ6Ee~f+dtTZn~!FC(ax*B!$*5;+&)?r)z`Is#m z-xXWH*1B%wxoK@uLkMTwZ0TI6>X8L_)Ha|cR;w^tTv~1c-i@nTs>b`FSZMTflxxFm z&%Vl2yFw?Rx5C3VyBFRv<;bm)0yeHLao12}-!K`Qxj+0Ux#UIb3uHq1i{)D}*1a{z z5R>cXtX`HS>o6D{Upi8Rp6C;Ly@=M7MQi?EcfsfRj}{{jPwFQ=YfKw?fBEQ&bGO9m1};-LP*OdDr`5y({^9!3E&=cdn>I-%&N+8d=>B34I$UTTeF^|EtvM z;@yMgkMHhH^ENB!yN`nYZ&!~#_>kIIw>JLZxW!~;eQ`v;{l0+?%|~++h8Og>;yuUj z%V!h&;h+AZ>uHqhi-D1EhD zYL;$jmX`B-XKd{B;JOlNLtAxaPP--TuFTmb?cjY!&WZK;uH~#$^H7Zq?v!il{_}~5 zDe)iOewj#Kr$3jbv9Q{3Q5*GKjoGOQ-{t8sXYG{7Uf*cjNA_FKHJrnFPfZ`U(UWI= zxaU|n);+7_Lw@a@{ykoX&8Cx&9~8YCkF6;*J9EZ#hBBR^aWERWT{O`wd)=3`<$9XB z;=IAWmIooUrt{W)muOu>&n9!iC!2?_UJnkwBk%91xE(n%@<+F#!o1;SZPf7F!SS&~ zUYf&uh1uGY&OhwVM_!3i#MABnm>5tv-R`X~{53c>PnPohcJ}J>+<%MkYu)wii+3N@ zV${e}HDyLw{kP=_-3PC&dl3<2Tp(X|W7BpKr7W{ono%JiQII1h=I6DvTuq#Dv$y& zYr5-uiUsHtn>afIf04s~MR=1wdl8EPfU%-fgdi0F6pO?N!745;B+v^LV;+c9AQ2^F zs04r+s&D}4C6Y)n)hfgk&mvo@k?A|8HERG1mB&Kdsfr^zlbk6iDEBHwSS9u`u{Gb#bUo6Gl$W{hC3`q(CAQI@#Pxe7X z!Z-}yVPK}M%1GsGM`y6BN(>gy2+TwzC;+pkvzyuoJWa0Cn}Mfj5dh;dlH(9D0CT1d zu{0E*tEiM<%w$9&LSq0rJgq{-2p(qax`a+pgo-^>j|CeKCprYPAtnsNjtmDDWCt;65Ja;j5qTlv7*(mH zOORL+1kjoIY*SFiVzP-EQhk6JAWi*1EV=^$!)G!EL_8TjlQAIzpU-9Vuh=n(O8L|d zg4jecLU@}gTjr=bkWbZ2_{-%OoUh1OM6G`2%Ys>|(8Tw@EfHuCr~Ijm4o{~l`@12X zNtEN~hK`Ic0@69GF9I^?j$h!$_!>qm`rk0eaI4v5#$Q*7i=wKUn7URxQ6@qhld7bo V7+-=-9UP0zVUet?JpH{$e*!$4`8EIm diff --git a/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png b/lib/matplotlib/tests/baseline_images/test_axes/log_scales.png index 876b47caedb6f53a02eb9dd4f84da942810821b7..92d8776d6cc467081dcc12020ebd7919361a69f0 100644 GIT binary patch literal 16959 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 - +" id="m2d4ddba423" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="m4e4fc712fc" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -181,12 +181,12 @@ Q 19.53125 74.21875 31.78125 74.21875 - + - + @@ -215,12 +215,12 @@ z - + - + @@ -261,176 +261,241 @@ Q 31.109375 20.453125 19.1875 8.296875 +" id="m1acb1eab51" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="mb4079269e1" style="stroke:#000000;stroke-width:0.5;"/> - + - + - + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + @@ -441,23 +506,23 @@ L 0 2 +" id="md4f0ea9b24" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="mb109f2c327" style="stroke:#000000;stroke-width:0.5;"/> - + - + - + - + - + @@ -530,15 +595,15 @@ z - + - + - + @@ -553,104 +618,127 @@ z +" id="m6ccf459a85" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="m4cdb1537b0" style="stroke:#000000;stroke-width:0.5;"/> - + + + + + + + + + + + + + - + - + - + - + - + - + - + - + + + + + + + + + + + + - + - + - + - + - + - + @@ -658,7 +746,7 @@ L -2 0 - + From 5c28e95ad5fc60a553ea93e2576d546f84850e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Zabalza?= Date: Wed, 3 Aug 2016 23:40:17 +0100 Subject: [PATCH 9/9] do not fail on _DummyAxes --- lib/matplotlib/ticker.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 5e9eecba6030..4bd1a36148dc 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -846,7 +846,11 @@ def set_locs(self, locs): vmin, vmax = self.axis.get_view_interval() self.d = abs(vmax - vmin) - if hasattr(self.axis.get_transform(), 'linthresh'): + if not hasattr(self.axis, 'get_transform'): + # This might be a colorbar dummy axis, do not attempt to get + # transform + numdec = 10 + elif hasattr(self.axis.get_transform(), 'linthresh'): t = self.axis.get_transform() linthresh = t.linthresh # Only compute the number of decades in the logarithmic part of the