From 7e3ef7dc2095852fbd3a668c4a0d3285a63e6ee3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 30 Nov 2015 23:22:15 -0500 Subject: [PATCH 1/3] FIX: formatting in LogFormatterExponent The scale passed to the `pprint_val` method needs to also be scaled by the log so that the range used to format the tick labels matches the actual range of the tick labels (not the underlying values). reported via http://stackoverflow.com/questions/33975758/matplotlib-logformatterexponent-e-in-the-exponent-labels-of-cbar --- lib/matplotlib/tests/test_ticker.py | 33 ++++++++++++++++++++--------- lib/matplotlib/ticker.py | 3 ++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 342cad03e2f6..21965543bb05 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -159,20 +159,28 @@ def test_SymmetricalLogLocator_set_params(): nose.tools.assert_equal(sym.numticks, 8) +def _logfe_helper(formatter, base, locs, i, expected_result): + vals = base**locs + labels = [formatter(x, pos) for (x, pos) in zip(vals, i)] + nose.tools.assert_equal(labels, expected_result) + + def test_LogFormatterExponent(): class FakeAxis(object): """Allow Formatter to be called without having a "full" plot set up.""" + def __init__(self, vmin=1, vmax=10): + self.vmin = vmin + self.vmax = vmax + def get_view_interval(self): - return 1, 10 + return self.vmin, self.vmax i = np.arange(-3, 4, dtype=float) expected_result = ['-3', '-2', '-1', '0', '1', '2', '3'] - for base in [2, 5, 10, np.pi, np.e]: + for base in [2, 5.0, 10.0, np.pi, np.e]: formatter = mticker.LogFormatterExponent(base=base) - formatter.axis = FakeAxis() - vals = base**i - labels = [formatter(x, pos) for (x, pos) in zip(vals, i)] - nose.tools.assert_equal(labels, expected_result) + formatter.axis = FakeAxis(1, base**4) + yield _logfe_helper, formatter, base, i, i, expected_result # Should be a blank string for non-integer powers if labelOnlyBase=True formatter = mticker.LogFormatterExponent(base=10, labelOnlyBase=True) @@ -185,10 +193,15 @@ def get_view_interval(self): expected_result = ['0.1', '1e-05', '3.14', '0.2', '-0.2', '-1e-05'] for base in [2, 5, 10, np.pi, np.e]: formatter = mticker.LogFormatterExponent(base, labelOnlyBase=False) - formatter.axis = FakeAxis() - vals = base**locs - labels = [formatter(x, pos) for (x, pos) in zip(vals, i)] - nose.tools.assert_equal(labels, expected_result) + formatter.axis = FakeAxis(1, base**10) + yield _logfe_helper, formatter, base, locs, i, expected_result + + expected_result = ['3', '5', '12', '42'] + locs = np.array([3, 5, 12, 42], dtype='float') + for base in [2, 5.0, 10.0, np.pi, np.e]: + formatter = mticker.LogFormatterExponent(base, labelOnlyBase=False) + formatter.axis = FakeAxis(1, base**50) + yield _logfe_helper, formatter, base, locs, i, expected_result def test_use_offset(): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 450869878c14..a1809f9d381e 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -768,7 +768,8 @@ def __call__(self, x, pos=None): elif abs(fx) < 1: s = '%1.0g' % fx else: - s = self.pprint_val(fx, d) + fd = math.log(abs(d)) / math.log(b) + s = self.pprint_val(fx, fd) if sign == -1: s = '-%s' % s From 7566447ca969181e18c9990a2137af4bfb4c4163 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 1 Dec 2015 01:28:06 -0500 Subject: [PATCH 2/3] MNT: clean up exponents in simpler way Makes sure that there are never trailing 'e' with no exponent. --- lib/matplotlib/ticker.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index a1809f9d381e..3ec4ba318361 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -731,13 +731,15 @@ def pprint_val(self, x, d): else: fmt = '%1.3f' s = fmt % x - #print d, x, fmt, s + tup = s.split('e') if len(tup) == 2: mantissa = tup[0].rstrip('0').rstrip('.') - sign = tup[1][0].replace('+', '') - exponent = tup[1][1:].lstrip('0') - s = '%se%s%s' % (mantissa, sign, exponent) + exponent = int(tup[1]) + if exponent: + s = '%se%d' % (mantissa, exponent) + else: + s = mantissa else: s = s.rstrip('0').rstrip('.') return s From f9c8aa18f80eb2a7c6c9fbb189e72fa8d464f9aa Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 1 Dec 2015 01:28:44 -0500 Subject: [PATCH 3/3] TST: shotgun testing of LogFormatter.pprint_val Code used to generate the test data to establish bench mark. First print out is human-readable, second is for testing import itertools import matplotlib.ticker as mticker import numpy as np domains = sorted([1e-3, 1.5e-2, 1e6, 100, 5, .5]) float_values = sorted([np.pi * (10**i) for i in range(-5, 6)]) int_values = sorted([1 * (10 ** i) for i in range(-5, 6)]) fmt = mticker.LogFormatter() print() for d in domains: print('Domain ', d) for f in float_values: print(' {: >10.7f}: {: <10}'.format(f, fmt.pprint_val(f, d))) for f in int_values: print(' {: >10g}: {: <10}'.format(f, fmt.pprint_val(f, d))) print() print() print(',\n'.join([' ({v:.10g}, {d!r}, {res!r})'.format( v=f, d=d, res=fmt.pprint_val(f, d)) for d, f in itertools.product( domains, float_values + int_values)])) --- lib/matplotlib/tests/test_ticker.py | 146 ++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 21965543bb05..438be20aa7ce 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -204,6 +204,152 @@ def get_view_interval(self): yield _logfe_helper, formatter, base, locs, i, expected_result +def _pprint_helper(value, domain, expected): + fmt = mticker.LogFormatter() + label = fmt.pprint_val(value, domain) + nose.tools.assert_equal(label, expected) + + +def test_logformatter_pprint(): + test_cases = ( + (3.141592654e-05, 0.001, '3.142e-5'), + (0.0003141592654, 0.001, '3.142e-4'), + (0.003141592654, 0.001, '3.142e-3'), + (0.03141592654, 0.001, '3.142e-2'), + (0.3141592654, 0.001, '3.142e-1'), + (3.141592654, 0.001, '3.142'), + (31.41592654, 0.001, '3.142e1'), + (314.1592654, 0.001, '3.142e2'), + (3141.592654, 0.001, '3.142e3'), + (31415.92654, 0.001, '3.142e4'), + (314159.2654, 0.001, '3.142e5'), + (1e-05, 0.001, '1e-5'), + (0.0001, 0.001, '1e-4'), + (0.001, 0.001, '1e-3'), + (0.01, 0.001, '1e-2'), + (0.1, 0.001, '1e-1'), + (1, 0.001, '1'), + (10, 0.001, '10'), + (100, 0.001, '100'), + (1000, 0.001, '1000'), + (10000, 0.001, '1e4'), + (100000, 0.001, '1e5'), + (3.141592654e-05, 0.015, '0'), + (0.0003141592654, 0.015, '0'), + (0.003141592654, 0.015, '0.003'), + (0.03141592654, 0.015, '0.031'), + (0.3141592654, 0.015, '0.314'), + (3.141592654, 0.015, '3.142'), + (31.41592654, 0.015, '31.416'), + (314.1592654, 0.015, '314.159'), + (3141.592654, 0.015, '3141.593'), + (31415.92654, 0.015, '31415.927'), + (314159.2654, 0.015, '314159.265'), + (1e-05, 0.015, '0'), + (0.0001, 0.015, '0'), + (0.001, 0.015, '0.001'), + (0.01, 0.015, '0.01'), + (0.1, 0.015, '0.1'), + (1, 0.015, '1'), + (10, 0.015, '10'), + (100, 0.015, '100'), + (1000, 0.015, '1000'), + (10000, 0.015, '10000'), + (100000, 0.015, '100000'), + (3.141592654e-05, 0.5, '0'), + (0.0003141592654, 0.5, '0'), + (0.003141592654, 0.5, '0.003'), + (0.03141592654, 0.5, '0.031'), + (0.3141592654, 0.5, '0.314'), + (3.141592654, 0.5, '3.142'), + (31.41592654, 0.5, '31.416'), + (314.1592654, 0.5, '314.159'), + (3141.592654, 0.5, '3141.593'), + (31415.92654, 0.5, '31415.927'), + (314159.2654, 0.5, '314159.265'), + (1e-05, 0.5, '0'), + (0.0001, 0.5, '0'), + (0.001, 0.5, '0.001'), + (0.01, 0.5, '0.01'), + (0.1, 0.5, '0.1'), + (1, 0.5, '1'), + (10, 0.5, '10'), + (100, 0.5, '100'), + (1000, 0.5, '1000'), + (10000, 0.5, '10000'), + (100000, 0.5, '100000'), + (3.141592654e-05, 5, '0'), + (0.0003141592654, 5, '0'), + (0.003141592654, 5, '0'), + (0.03141592654, 5, '0.03'), + (0.3141592654, 5, '0.31'), + (3.141592654, 5, '3.14'), + (31.41592654, 5, '31.42'), + (314.1592654, 5, '314.16'), + (3141.592654, 5, '3141.59'), + (31415.92654, 5, '31415.93'), + (314159.2654, 5, '314159.27'), + (1e-05, 5, '0'), + (0.0001, 5, '0'), + (0.001, 5, '0'), + (0.01, 5, '0.01'), + (0.1, 5, '0.1'), + (1, 5, '1'), + (10, 5, '10'), + (100, 5, '100'), + (1000, 5, '1000'), + (10000, 5, '10000'), + (100000, 5, '100000'), + (3.141592654e-05, 100, '0'), + (0.0003141592654, 100, '0'), + (0.003141592654, 100, '0'), + (0.03141592654, 100, '0'), + (0.3141592654, 100, '0.3'), + (3.141592654, 100, '3.1'), + (31.41592654, 100, '31.4'), + (314.1592654, 100, '314.2'), + (3141.592654, 100, '3141.6'), + (31415.92654, 100, '31415.9'), + (314159.2654, 100, '314159.3'), + (1e-05, 100, '0'), + (0.0001, 100, '0'), + (0.001, 100, '0'), + (0.01, 100, '0'), + (0.1, 100, '0.1'), + (1, 100, '1'), + (10, 100, '10'), + (100, 100, '100'), + (1000, 100, '1000'), + (10000, 100, '10000'), + (100000, 100, '100000'), + (3.141592654e-05, 1000000.0, '3.1e-5'), + (0.0003141592654, 1000000.0, '3.1e-4'), + (0.003141592654, 1000000.0, '3.1e-3'), + (0.03141592654, 1000000.0, '3.1e-2'), + (0.3141592654, 1000000.0, '3.1e-1'), + (3.141592654, 1000000.0, '3.1'), + (31.41592654, 1000000.0, '3.1e1'), + (314.1592654, 1000000.0, '3.1e2'), + (3141.592654, 1000000.0, '3.1e3'), + (31415.92654, 1000000.0, '3.1e4'), + (314159.2654, 1000000.0, '3.1e5'), + (1e-05, 1000000.0, '1e-5'), + (0.0001, 1000000.0, '1e-4'), + (0.001, 1000000.0, '1e-3'), + (0.01, 1000000.0, '1e-2'), + (0.1, 1000000.0, '1e-1'), + (1, 1000000.0, '1'), + (10, 1000000.0, '10'), + (100, 1000000.0, '100'), + (1000, 1000000.0, '1000'), + (10000, 1000000.0, '1e4'), + (100000, 1000000.0, '1e5') + ) + + for value, domain, expected in test_cases: + yield _pprint_helper, value, domain, expected + + def test_use_offset(): for use_offset in [True, False]: with matplotlib.rc_context({'axes.formatter.useoffset': use_offset}):