From 5a550f2f07d603b5cc744fd6d25326eefe397cdb Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Mon, 6 Jun 2016 12:37:13 +0200 Subject: [PATCH 01/25] ENH: Add the space_sep option to EngFormatter, and docstring updates --- lib/matplotlib/ticker.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 65a23537b58a..bda1a88107d1 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1192,6 +1192,9 @@ class EngFormatter(Formatter): `places` is the precision with which to display the number, specified in digits after the decimal point (there will be between one and three digits before the decimal point). + + `space_sep` controls if there is a space between the number and the + prefix/unit. For example, '3.14 mV' if True and '3.14mV' if False. """ # The SI engineering prefixes ENG_PREFIXES = { @@ -1214,9 +1217,27 @@ class EngFormatter(Formatter): 24: "Y" } - def __init__(self, unit="", places=None): + def __init__(self, unit="", places=None, space_sep=True): + """ Parameters + ---------- + unit: str (default: u"") + Unit symbol to use. + + places: int (default: None) + Precision, i.e. number of digits after the decimal point. + If it is None, falls back to the floating point format '%g'. + + space_sep: boolean (default: True) + If True, a (single) space is used between the value and the + prefix/unit, else the prefix/unit is directly appended to the + value. + """ self.unit = unit self.places = places + if space_sep is True: + self.sep = u" " # 1 space + else: + self.sep = u"" # no space def __call__(self, x, pos=None): s = "%s%s" % (self.format_eng(x), self.unit) @@ -1260,18 +1281,20 @@ def format_eng(self, num): mant = sign * dnum / (10 ** pow10) + # TODO: shouldn't we raise a warning if self.places < 0? if self.places is None: - format_str = "%g %s" + format_str = "%g{sep:s}%s".format(sep=self.sep) elif self.places == 0: - format_str = "%i %s" + format_str = "%d{sep:s}%s".format(sep=self.sep) elif self.places > 0: - format_str = ("%%.%if %%s" % self.places) + format_str = "%.{p:d}f{sep:s}%s".format(p=self.places, + sep=self.sep) formatted = format_str % (mant, prefix) formatted = formatted.strip() if (self.unit != "") and (prefix == self.ENG_PREFIXES[0]): - formatted = formatted + " " + formatted = formatted + self.sep return formatted From 722f5d615f26bf4fc9dbc41570ba6b1768e39810 Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Mon, 6 Jun 2016 13:19:37 +0200 Subject: [PATCH 02/25] FIX: force format_eng(-0.0) to be consistent with format_eng(0) --- lib/matplotlib/ticker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index bda1a88107d1..320bd88876c4 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1273,6 +1273,10 @@ def format_eng(self, num): pow10 = decimal.Decimal(int(math.floor(dnum.log10() / 3) * 3)) else: pow10 = decimal.Decimal(0) + # Force dnum to zero, to avoid inconsistencies like + # format_eng(-0) = "0" and format_eng(0.0) = "0" + # but format_eng(-0.0) = "-0.0" + dnum = decimal.Decimal(0) pow10 = pow10.min(max(self.ENG_PREFIXES)) pow10 = pow10.max(min(self.ENG_PREFIXES)) From 3c221ffd5f89e342b4cae973630d45df6083903d Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Tue, 7 Jun 2016 09:36:21 +0200 Subject: [PATCH 03/25] Fix docstrings and comments (remove leading spaces) --- lib/matplotlib/ticker.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 320bd88876c4..c6ed53f3b6a8 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1218,19 +1218,20 @@ class EngFormatter(Formatter): } def __init__(self, unit="", places=None, space_sep=True): - """ Parameters - ---------- - unit: str (default: u"") - Unit symbol to use. - - places: int (default: None) - Precision, i.e. number of digits after the decimal point. - If it is None, falls back to the floating point format '%g'. - - space_sep: boolean (default: True) - If True, a (single) space is used between the value and the - prefix/unit, else the prefix/unit is directly appended to the - value. + """ + Parameters + ---------- + unit: str (default: u"") + Unit symbol to use. + + places: int (default: None) + Precision, i.e. number of digits after the decimal point. + If it is None, falls back to the floating point format '%g'. + + space_sep: boolean (default: True) + If True, a (single) space is used between the value and the + prefix/unit, else the prefix/unit is directly appended to the + value. """ self.unit = unit self.places = places From 122cfa202a52bd86b149714e35a35dc8b4010e78 Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Tue, 7 Jun 2016 09:50:36 +0200 Subject: [PATCH 04/25] Simplify and move cleaning strip op. from 'format_eng' to '__call__' --- lib/matplotlib/ticker.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index c6ed53f3b6a8..d1924f5b6f21 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1242,6 +1242,7 @@ def __init__(self, unit="", places=None, space_sep=True): def __call__(self, x, pos=None): s = "%s%s" % (self.format_eng(x), self.unit) + s = s.strip() # Remove separator when there is neither prefix nor unit return self.fix_minus(s) def format_eng(self, num): @@ -1297,10 +1298,6 @@ def format_eng(self, num): formatted = format_str % (mant, prefix) - formatted = formatted.strip() - if (self.unit != "") and (prefix == self.ENG_PREFIXES[0]): - formatted = formatted + self.sep - return formatted From 07f022d4bebba016c02d5bf8a025171c144452dc Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sun, 19 Jun 2016 12:57:10 +0200 Subject: [PATCH 05/25] DOC: update the api example engineering_formatter.py --- examples/api/engineering_formatter.py | 30 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/api/engineering_formatter.py b/examples/api/engineering_formatter.py index 743ee819cae9..d3f3f45f4e28 100644 --- a/examples/api/engineering_formatter.py +++ b/examples/api/engineering_formatter.py @@ -14,13 +14,29 @@ # Fixing random state for reproducibility prng = np.random.RandomState(19680801) -fig, ax = plt.subplots() -ax.set_xscale('log') -formatter = EngFormatter(unit='Hz') -ax.xaxis.set_major_formatter(formatter) - +# Create artificial data to plot. +# The x data span over several decades to demonstrate several SI prefixes. xs = np.logspace(1, 9, 100) -ys = (0.8 + 0.4 * prng.uniform(size=100)) * np.log10(xs)**2 -ax.plot(xs, ys) +ys = (0.8 + 0.4*prng.uniform(size=100))*np.log10(xs)**2 + +# Figure width is doubled (2*6.4) to display nicely 2 subplots side by side. +fig, (ax0, ax1) = plt.subplots(ncols=2, figsize=(12.8, 4.8)) +for ax in (ax0, ax1): + ax.set_xscale('log') + +# Demo of the default settings, with a user-defined unit label. +ax0.set_title('Full unit ticklabels, w/ default precision & space sep.') +formatter0 = EngFormatter(unit='Hz') +ax0.xaxis.set_major_formatter(formatter0) +ax0.plot(xs, ys) +ax0.set_xlabel('Frequency') + +# Demo of the options `places` (number of digit after decimal point) and +# `space_sep` (presence of a space between the number and the prefix/unit). +ax1.set_title('SI-prefix only ticklabels, 1-digit precision & w/o space sep.') +formatter1 = EngFormatter(places=1, space_sep=False) +ax1.xaxis.set_major_formatter(formatter1) +ax1.plot(xs, ys) +ax1.set_xlabel('Frequency [Hz]') plt.show() From e371e5a586269e44bb8dc38cb89678d626cb988a Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sat, 11 Feb 2017 18:04:21 +0100 Subject: [PATCH 06/25] More extensive testing of EngFormatter (including 'space_sep' param) --- lib/matplotlib/tests/test_ticker.py | 94 ++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 865cc085c376..26eee489b92c 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -549,26 +549,88 @@ def test_basic(self, format, input, expected): class TestEngFormatter(object): - format_data = [ - ('', 0.1, u'100 m'), - ('', 1, u'1'), - ('', 999.9, u'999.9'), - ('', 1001, u'1.001 k'), - (u's', 0.1, u'100 ms'), - (u's', 1, u'1 s'), - (u's', 999.9, u'999.9 s'), - (u's', 1001, u'1.001 ks'), + # (input, expected) where ''expected'' corresponds to the outputs + # respectively returned when (places=None, places=0, places=2) + raw_format_data = [ + (-1234.56789, (u'-1.23457 k', u'-1 k', u'-1.23 k')), + (-1.23456789, (u'-1.23457', u'-1', u'-1.23')), + (-0.123456789, (u'-123.457 m', u'-123 m', u'-123.46 m')), + (-0.00123456789, (u'-1.23457 m', u'-1 m', u'-1.23 m')), + (-0.0, (u'0', u'0', u'0.00')), + (-0, (u'0', u'0', u'0.00')), + (0, (u'0', u'0', u'0.00')), + (1.23456789e-6, (u'1.23457 \u03bc', u'1 \u03bc', u'1.23 \u03bc')), + (0.123456789, (u'123.457 m', u'123 m', u'123.46 m')), + (0.1, (u'100 m', u'100 m', u'100.00 m')), + (1, (u'1', u'1', u'1.00')), + (1.23456789, (u'1.23457', u'1', u'1.23')), + (999.9, (u'999.9', u'999', u'999.90')), + (1000, (u'1 k', u'1 k', u'1.00 k')), + (1001, (u'1.001 k', u'1 k', u'1.00 k')), + (100001, (u'100.001 k', u'100 k', u'100.00 k')), + (987654.321, (u'987.654 k', u'987 k', u'987.65 k')) ] - @pytest.mark.parametrize('unit, input, expected', format_data) - def test_formatting(self, unit, input, expected): + @pytest.mark.parametrize('input, expected', raw_format_data) + def test_params(self, input, expected): """ - Test the formatting of EngFormatter with some inputs, against - instances with and without units. Cases focus on when no SI - prefix is present, for values in [1, 1000). + Test the formatting of EngFormatter for various values of the 'places' + argument, in several cases: + 0. without unit but with a space separator; + 1. with both a unit and a space separator; + 2. with a unit but no space separator; + 3. with neihter a unit nor a space separator. """ - fmt = mticker.EngFormatter(unit) - assert fmt(input) == expected + + UNIT = u's' # seconds + DIGITS = u'0123456789' # %timeit showed 10-20% faster search than set + + # Case 0: unit='' (default) and space_sep=True (default). + # 'expected' already corresponds to this reference case. + exp_outputs = (_s for _s in expected) # simple copy of 'expected' + formatters = ( + mticker.EngFormatter(), # places=None (default) + mticker.EngFormatter(places=0), + mticker.EngFormatter(places=2) + ) + for _formatter, _exp_output in zip(formatters, exp_outputs): + assert _formatter(input) == _exp_output + + # Case 1: unit=UNIT and space_sep=True (default). + # Append a unit symbol to the reference case. + # Beware of the values in [1, 1000), where there is no prefix! + exp_outputs = (_s + u" " + UNIT if _s[-1] in DIGITS # case w/o prefix + else _s + UNIT for _s in expected) + formatters = ( + mticker.EngFormatter(unit=UNIT), # places=None (default) + mticker.EngFormatter(unit=UNIT, places=0), + mticker.EngFormatter(unit=UNIT, places=2) + ) + for _formatter, _exp_output in zip(formatters, exp_outputs): + assert _formatter(input) == _exp_output + + # Case 2: unit=UNIT and space_sep=False. + # Remove the space separator from the reference case. + exp_outputs = (_s.replace(" ", "") + UNIT for _s in expected) + formatters = ( + mticker.EngFormatter(unit=UNIT, space_sep=False), # places=None + mticker.EngFormatter(unit=UNIT, places=0, space_sep=False), + mticker.EngFormatter(unit=UNIT, places=2, space_sep=False) + ) + for _formatter, _exp_output in zip(formatters, exp_outputs): + assert _formatter(input) == _exp_output + + # Case 3: unit='' (default) and space_sep=False. + # Remove the space separator from the reference case and append + # a unit symbol to it. + exp_outputs = (_s.replace(" ", "") for _s in expected) + formatters = ( + mticker.EngFormatter(space_sep=False), # places=None (default) + mticker.EngFormatter(places=0, space_sep=False), + mticker.EngFormatter(places=2, space_sep=False) + ) + for _formatter, _exp_output in zip(formatters, exp_outputs): + assert _formatter(input) == _exp_output class TestPercentFormatter(object): From 3049a2d90b00da495af1f92f14f267e83e83d50f Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sun, 12 Feb 2017 13:06:43 +0100 Subject: [PATCH 07/25] space_sep=bool <- sep=string, and adapt tests --- lib/matplotlib/tests/test_ticker.py | 54 ++++++++++++++++------------- lib/matplotlib/ticker.py | 27 ++++++++------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 26eee489b92c..20fa4ed11940 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -585,7 +585,7 @@ def test_params(self, input, expected): UNIT = u's' # seconds DIGITS = u'0123456789' # %timeit showed 10-20% faster search than set - # Case 0: unit='' (default) and space_sep=True (default). + # Case 0: unit='' (default) and sep=' ' (default). # 'expected' already corresponds to this reference case. exp_outputs = (_s for _s in expected) # simple copy of 'expected' formatters = ( @@ -596,7 +596,7 @@ def test_params(self, input, expected): for _formatter, _exp_output in zip(formatters, exp_outputs): assert _formatter(input) == _exp_output - # Case 1: unit=UNIT and space_sep=True (default). + # Case 1: unit=UNIT and sep=' ' (default). # Append a unit symbol to the reference case. # Beware of the values in [1, 1000), where there is no prefix! exp_outputs = (_s + u" " + UNIT if _s[-1] in DIGITS # case w/o prefix @@ -609,28 +609,34 @@ def test_params(self, input, expected): for _formatter, _exp_output in zip(formatters, exp_outputs): assert _formatter(input) == _exp_output - # Case 2: unit=UNIT and space_sep=False. - # Remove the space separator from the reference case. - exp_outputs = (_s.replace(" ", "") + UNIT for _s in expected) - formatters = ( - mticker.EngFormatter(unit=UNIT, space_sep=False), # places=None - mticker.EngFormatter(unit=UNIT, places=0, space_sep=False), - mticker.EngFormatter(unit=UNIT, places=2, space_sep=False) - ) - for _formatter, _exp_output in zip(formatters, exp_outputs): - assert _formatter(input) == _exp_output - - # Case 3: unit='' (default) and space_sep=False. - # Remove the space separator from the reference case and append - # a unit symbol to it. - exp_outputs = (_s.replace(" ", "") for _s in expected) - formatters = ( - mticker.EngFormatter(space_sep=False), # places=None (default) - mticker.EngFormatter(places=0, space_sep=False), - mticker.EngFormatter(places=2, space_sep=False) - ) - for _formatter, _exp_output in zip(formatters, exp_outputs): - assert _formatter(input) == _exp_output + # Test several non default separators: no separator, a narrow + # no-break space (unicode character) and an extravagant string. + for _sep in ("","\u202f", "@_@"): + # Case 2x: unit=UNIT and sep=_sep. + # Remove the space separator from the reference case. + exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix + else _s.replace(" ", _sep) + UNIT + for _s in expected) + #exp_outputs = list(_s.replace(" ", _sep) + UNIT for _s in expected) + formatters = ( + mticker.EngFormatter(unit=UNIT, sep=_sep), # places=None + mticker.EngFormatter(unit=UNIT, places=0, sep=_sep), + mticker.EngFormatter(unit=UNIT, places=2, sep=_sep) + ) + for _formatter, _exp_output in zip(formatters, exp_outputs): + assert _formatter(input) == _exp_output + + # Case 3x: unit='' (default) and sep=_sep. + # Remove the space separator from the reference case and append + # a unit symbol to it. + exp_outputs = (_s.replace(" ", _sep) for _s in expected) + formatters = ( + mticker.EngFormatter(sep=_sep), # places=None (default) + mticker.EngFormatter(places=0, sep=_sep), + mticker.EngFormatter(places=2, sep=_sep) + ) + for _formatter, _exp_output in zip(formatters, exp_outputs): + assert _formatter(input) == _exp_output class TestPercentFormatter(object): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index d1924f5b6f21..4c4232007f30 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1193,8 +1193,9 @@ class EngFormatter(Formatter): specified in digits after the decimal point (there will be between one and three digits before the decimal point). - `space_sep` controls if there is a space between the number and the - prefix/unit. For example, '3.14 mV' if True and '3.14mV' if False. + `sep` is the separator (a string) that is used between the number + and the prefix/unit. For example, '3.14 mV' if `sep` is " " (default) + and '3.14mV' if `sep` is "". """ # The SI engineering prefixes ENG_PREFIXES = { @@ -1217,32 +1218,32 @@ class EngFormatter(Formatter): 24: "Y" } - def __init__(self, unit="", places=None, space_sep=True): + def __init__(self, unit="", places=None, sep=" "): """ Parameters ---------- - unit: str (default: u"") + unit: string (default: "") Unit symbol to use. places: int (default: None) Precision, i.e. number of digits after the decimal point. If it is None, falls back to the floating point format '%g'. - space_sep: boolean (default: True) - If True, a (single) space is used between the value and the - prefix/unit, else the prefix/unit is directly appended to the - value. + sep: string (default: " ") + String used between the value and the prefix/unit. Beside the + default behavior, some other useful use cases may be: + * sep="" to append directly the prefix/unit to the value; + * sep="\u00a0" to use a no-break space; + * sep="\u202f" to use a narrow no-break space. """ self.unit = unit self.places = places - if space_sep is True: - self.sep = u" " # 1 space - else: - self.sep = u"" # no space + self.sep = sep def __call__(self, x, pos=None): s = "%s%s" % (self.format_eng(x), self.unit) - s = s.strip() # Remove separator when there is neither prefix nor unit + # Remove the trailing separator when there is neither prefix nor unit + s = s.strip(self.sep) return self.fix_minus(s) def format_eng(self, num): From e0a2ec87b59e432c638d87dc43a8d2bef2484c2c Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sun, 12 Feb 2017 13:08:04 +0100 Subject: [PATCH 08/25] remove unnecessary trailing 'u' characters in strings --- lib/matplotlib/tests/test_ticker.py | 40 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 20fa4ed11940..32e91813d0d4 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -552,23 +552,23 @@ class TestEngFormatter(object): # (input, expected) where ''expected'' corresponds to the outputs # respectively returned when (places=None, places=0, places=2) raw_format_data = [ - (-1234.56789, (u'-1.23457 k', u'-1 k', u'-1.23 k')), - (-1.23456789, (u'-1.23457', u'-1', u'-1.23')), - (-0.123456789, (u'-123.457 m', u'-123 m', u'-123.46 m')), - (-0.00123456789, (u'-1.23457 m', u'-1 m', u'-1.23 m')), - (-0.0, (u'0', u'0', u'0.00')), - (-0, (u'0', u'0', u'0.00')), - (0, (u'0', u'0', u'0.00')), - (1.23456789e-6, (u'1.23457 \u03bc', u'1 \u03bc', u'1.23 \u03bc')), - (0.123456789, (u'123.457 m', u'123 m', u'123.46 m')), - (0.1, (u'100 m', u'100 m', u'100.00 m')), - (1, (u'1', u'1', u'1.00')), - (1.23456789, (u'1.23457', u'1', u'1.23')), - (999.9, (u'999.9', u'999', u'999.90')), - (1000, (u'1 k', u'1 k', u'1.00 k')), - (1001, (u'1.001 k', u'1 k', u'1.00 k')), - (100001, (u'100.001 k', u'100 k', u'100.00 k')), - (987654.321, (u'987.654 k', u'987 k', u'987.65 k')) + (-1234.56789, ('-1.23457 k', '-1 k', '-1.23 k')), + (-1.23456789, ('-1.23457', '-1', '-1.23')), + (-0.123456789, ('-123.457 m', '-123 m', '-123.46 m')), + (-0.00123456789, ('-1.23457 m', '-1 m', '-1.23 m')), + (-0.0, ('0', '0', '0.00')), + (-0, ('0', '0', '0.00')), + (0, ('0', '0', '0.00')), + (1.23456789e-6, ('1.23457 \u03bc', '1 \u03bc', '1.23 \u03bc')), + (0.123456789, ('123.457 m', '123 m', '123.46 m')), + (0.1, ('100 m', '100 m', '100.00 m')), + (1, ('1', '1', '1.00')), + (1.23456789, ('1.23457', '1', '1.23')), + (999.9, ('999.9', '999', '999.90')), + (1000, ('1 k', '1 k', '1.00 k')), + (1001, ('1.001 k', '1 k', '1.00 k')), + (100001, ('100.001 k', '100 k', '100.00 k')), + (987654.321, ('987.654 k', '987 k', '987.65 k')) ] @pytest.mark.parametrize('input, expected', raw_format_data) @@ -582,8 +582,8 @@ def test_params(self, input, expected): 3. with neihter a unit nor a space separator. """ - UNIT = u's' # seconds - DIGITS = u'0123456789' # %timeit showed 10-20% faster search than set + UNIT = 's' # seconds + DIGITS = '0123456789' # %timeit showed 10-20% faster search than set # Case 0: unit='' (default) and sep=' ' (default). # 'expected' already corresponds to this reference case. @@ -599,7 +599,7 @@ def test_params(self, input, expected): # Case 1: unit=UNIT and sep=' ' (default). # Append a unit symbol to the reference case. # Beware of the values in [1, 1000), where there is no prefix! - exp_outputs = (_s + u" " + UNIT if _s[-1] in DIGITS # case w/o prefix + exp_outputs = (_s + " " + UNIT if _s[-1] in DIGITS # case w/o prefix else _s + UNIT for _s in expected) formatters = ( mticker.EngFormatter(unit=UNIT), # places=None (default) From 1621a2daa7e3cf53c4379788cfed4ff44a80c23e Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sun, 12 Feb 2017 13:44:26 +0100 Subject: [PATCH 09/25] fix EngFormatter docstring: escape Unicode character codes --- lib/matplotlib/ticker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 4c4232007f30..1406f150f19f 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1233,8 +1233,8 @@ def __init__(self, unit="", places=None, sep=" "): String used between the value and the prefix/unit. Beside the default behavior, some other useful use cases may be: * sep="" to append directly the prefix/unit to the value; - * sep="\u00a0" to use a no-break space; - * sep="\u202f" to use a narrow no-break space. + * sep="\\u00a0" to use a no-break space; + * sep="\\u202f" to use a narrow no-break space. """ self.unit = unit self.places = places From c82dcff1a70ed20a03132fd7dd4fd7799ced9f41 Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sun, 12 Feb 2017 18:00:54 +0100 Subject: [PATCH 10/25] 'fix' docstring: unindent and reorder bullet list + mention regular thin space --- lib/matplotlib/ticker.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 1406f150f19f..7043d84ce6af 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1232,9 +1232,10 @@ def __init__(self, unit="", places=None, sep=" "): sep: string (default: " ") String used between the value and the prefix/unit. Beside the default behavior, some other useful use cases may be: - * sep="" to append directly the prefix/unit to the value; - * sep="\\u00a0" to use a no-break space; - * sep="\\u202f" to use a narrow no-break space. + * sep="" to append directly the prefix/unit to the value; + * sep="\\u2009" to use a thin space; + * sep="\\u202f" to use a narrow no-break space; + * sep="\\u00a0" to use a no-break space. """ self.unit = unit self.places = places From f09a1b40d4325e8de0ad3a17ba52d9a079ab956a Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sun, 12 Feb 2017 13:18:29 +0100 Subject: [PATCH 11/25] Update the example --- examples/api/engineering_formatter.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/api/engineering_formatter.py b/examples/api/engineering_formatter.py index d3f3f45f4e28..5f58b74b9bc7 100644 --- a/examples/api/engineering_formatter.py +++ b/examples/api/engineering_formatter.py @@ -25,16 +25,17 @@ ax.set_xscale('log') # Demo of the default settings, with a user-defined unit label. -ax0.set_title('Full unit ticklabels, w/ default precision & space sep.') +ax0.set_title('Full unit ticklabels, w/ default precision & space separator') formatter0 = EngFormatter(unit='Hz') ax0.xaxis.set_major_formatter(formatter0) ax0.plot(xs, ys) ax0.set_xlabel('Frequency') # Demo of the options `places` (number of digit after decimal point) and -# `space_sep` (presence of a space between the number and the prefix/unit). -ax1.set_title('SI-prefix only ticklabels, 1-digit precision & w/o space sep.') -formatter1 = EngFormatter(places=1, space_sep=False) +# `sep` (separator between the number and the prefix/unit). +ax1.set_title('SI-prefix only ticklabels, 1-digit precision & ' + + 'hair space separator') +formatter1 = EngFormatter(places=1, sep=u"\u200a") # U+200A is hair space ax1.xaxis.set_major_formatter(formatter1) ax1.plot(xs, ys) ax1.set_xlabel('Frequency [Hz]') From dc9409b6a96a313376bc96a3005a0f5463f9f872 Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Sun, 12 Feb 2017 13:43:26 +0100 Subject: [PATCH 12/25] fix test docstring PEP8 errors in test_ticker.py --- lib/matplotlib/tests/test_ticker.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 32e91813d0d4..46bef7bb980d 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -576,10 +576,10 @@ def test_params(self, input, expected): """ Test the formatting of EngFormatter for various values of the 'places' argument, in several cases: - 0. without unit but with a space separator; - 1. with both a unit and a space separator; - 2. with a unit but no space separator; - 3. with neihter a unit nor a space separator. + 0. without a unit symbol but with a (default) space separator; + 1. with both a unit symbol and a (default) space separator; + 2x. with both a unit symbol and some non default separators; + 3x. without a unit symbol but with some non default separators. """ UNIT = 's' # seconds @@ -611,13 +611,12 @@ def test_params(self, input, expected): # Test several non default separators: no separator, a narrow # no-break space (unicode character) and an extravagant string. - for _sep in ("","\u202f", "@_@"): + for _sep in ("", "\u202f", "@_@"): # Case 2x: unit=UNIT and sep=_sep. # Remove the space separator from the reference case. exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix else _s.replace(" ", _sep) + UNIT for _s in expected) - #exp_outputs = list(_s.replace(" ", _sep) + UNIT for _s in expected) formatters = ( mticker.EngFormatter(unit=UNIT, sep=_sep), # places=None mticker.EngFormatter(unit=UNIT, places=0, sep=_sep), From 0195beb3b00a5f07cbe0ed8d61fe848cd94b45c4 Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Mon, 13 Feb 2017 00:26:19 +0100 Subject: [PATCH 13/25] Add a 'whats_new' entry --- doc/users/whats_new/EngFormatter_new_kwarg_sep.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 doc/users/whats_new/EngFormatter_new_kwarg_sep.rst diff --git a/doc/users/whats_new/EngFormatter_new_kwarg_sep.rst b/doc/users/whats_new/EngFormatter_new_kwarg_sep.rst new file mode 100644 index 000000000000..9e4bb2840e64 --- /dev/null +++ b/doc/users/whats_new/EngFormatter_new_kwarg_sep.rst @@ -0,0 +1,10 @@ +New keyword argument 'sep' for EngFormatter +------------------------------------------- + +A new "sep" keyword argument has been added to +:class:`~matplotlib.ticker.EngFormatter` and provides a means to define +the string that will be used between the value and its unit. The default +string is " ", which preserves the former behavior. Besides, the separator is +now present between the value and its unit even in the absence of SI prefix. +There was formerly a bug that was causing strings like "3.14V" to be returned +instead of the expected "3.14 V" (with the default behavior). From 3ec1dbd3f2ba8e255243807eebe651ad982f10eb Mon Sep 17 00:00:00 2001 From: Adrien F Vincent Date: Mon, 13 Feb 2017 01:16:49 +0100 Subject: [PATCH 14/25] docstring overhaul: fix bullet list and merge duplicated infos between class and init docstrings --- examples/api/engineering_formatter.py | 2 +- lib/matplotlib/tests/test_ticker.py | 22 ++++++------ lib/matplotlib/ticker.py | 50 ++++++++++++--------------- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/examples/api/engineering_formatter.py b/examples/api/engineering_formatter.py index 5f58b74b9bc7..2994465752c9 100644 --- a/examples/api/engineering_formatter.py +++ b/examples/api/engineering_formatter.py @@ -17,7 +17,7 @@ # Create artificial data to plot. # The x data span over several decades to demonstrate several SI prefixes. xs = np.logspace(1, 9, 100) -ys = (0.8 + 0.4*prng.uniform(size=100))*np.log10(xs)**2 +ys = (0.8 + 0.4 * prng.uniform(size=100)) * np.log10(xs)**2 # Figure width is doubled (2*6.4) to display nicely 2 subplots side by side. fig, (ax0, ax1) = plt.subplots(ncols=2, figsize=(12.8, 4.8)) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 46bef7bb980d..b68641c392a6 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -576,10 +576,11 @@ def test_params(self, input, expected): """ Test the formatting of EngFormatter for various values of the 'places' argument, in several cases: - 0. without a unit symbol but with a (default) space separator; - 1. with both a unit symbol and a (default) space separator; - 2x. with both a unit symbol and some non default separators; - 3x. without a unit symbol but with some non default separators. + 0. without a unit symbol but with a (default) space separator; + 1. with both a unit symbol and a (default) space separator; + 2. with both a unit symbol and some non default separators; + 3. without a unit symbol but with some non default separators. + Note that cases 2. and 3. are looped over several separator strings. """ UNIT = 's' # seconds @@ -587,7 +588,7 @@ def test_params(self, input, expected): # Case 0: unit='' (default) and sep=' ' (default). # 'expected' already corresponds to this reference case. - exp_outputs = (_s for _s in expected) # simple copy of 'expected' + exp_outputs = expected formatters = ( mticker.EngFormatter(), # places=None (default) mticker.EngFormatter(places=0), @@ -612,8 +613,9 @@ def test_params(self, input, expected): # Test several non default separators: no separator, a narrow # no-break space (unicode character) and an extravagant string. for _sep in ("", "\u202f", "@_@"): - # Case 2x: unit=UNIT and sep=_sep. - # Remove the space separator from the reference case. + # Case 2: unit=UNIT and sep=_sep. + # Replace the default space separator from the reference case + # with the tested one `_sep` and append a unit symbol to it. exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix else _s.replace(" ", _sep) + UNIT for _s in expected) @@ -625,9 +627,9 @@ def test_params(self, input, expected): for _formatter, _exp_output in zip(formatters, exp_outputs): assert _formatter(input) == _exp_output - # Case 3x: unit='' (default) and sep=_sep. - # Remove the space separator from the reference case and append - # a unit symbol to it. + # Case 3: unit='' (default) and sep=_sep. + # Replace the default space separator from the reference case + # with the tested one `_sep`. Reference case is already unitless. exp_outputs = (_s.replace(" ", _sep) for _s in expected) formatters = ( mticker.EngFormatter(sep=_sep), # places=None (default) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 7043d84ce6af..0bc0f1af3cb3 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1184,19 +1184,8 @@ class EngFormatter(Formatter): """ Formats axis values using engineering prefixes to represent powers of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7. - - `unit` is a string containing the abbreviated name of the unit, - suitable for use with single-letter representations of powers of - 1000. For example, 'Hz' or 'm'. - - `places` is the precision with which to display the number, - specified in digits after the decimal point (there will be between - one and three digits before the decimal point). - - `sep` is the separator (a string) that is used between the number - and the prefix/unit. For example, '3.14 mV' if `sep` is " " (default) - and '3.14mV' if `sep` is "". """ + # The SI engineering prefixes ENG_PREFIXES = { -24: "y", @@ -1222,20 +1211,26 @@ def __init__(self, unit="", places=None, sep=" "): """ Parameters ---------- - unit: string (default: "") - Unit symbol to use. - - places: int (default: None) - Precision, i.e. number of digits after the decimal point. - If it is None, falls back to the floating point format '%g'. - - sep: string (default: " ") - String used between the value and the prefix/unit. Beside the - default behavior, some other useful use cases may be: - * sep="" to append directly the prefix/unit to the value; - * sep="\\u2009" to use a thin space; - * sep="\\u202f" to use a narrow no-break space; - * sep="\\u00a0" to use a no-break space. + unit : str (default: "") + Unit symbol to use, suitable for use with single-letter + representations of powers of 1000. For example, 'Hz' or 'm'. + + places : int (default: None) + Precision with which to display the number, specified in + digits after the decimal point (there will be between one + and three digits before the decimal point). If it is None, + falls back to the floating point format '%g'. + + sep : str (default: " ") + Separator used between the value and the prefix/unit. For + example, one get '3.14 mV' if ``sep`` is " " (default) and + '3.14mV' if ``sep`` is "". Besides the default behavior, some + other useful options may be: + + * ``sep=""`` to append directly the prefix/unit to the value; + * ``sep="\\u2009"`` to use a thin space; + * ``sep="\\u202f"`` to use a narrow no-break space; + * ``sep="\\u00a0"`` to use a no-break space. """ self.unit = unit self.places = places @@ -1244,7 +1239,8 @@ def __init__(self, unit="", places=None, sep=" "): def __call__(self, x, pos=None): s = "%s%s" % (self.format_eng(x), self.unit) # Remove the trailing separator when there is neither prefix nor unit - s = s.strip(self.sep) + if len(self.sep) > 0 and s.endswith(self.sep): + s = s[:-len(self.sep)] return self.fix_minus(s) def format_eng(self, num): From cd2ac8850e0d1f6cca6d7a9587ceea78525512b6 Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Thu, 17 Aug 2017 08:22:10 -0700 Subject: [PATCH 15/25] use named entities instead of raw unicode codes --- examples/api/engineering_formatter.py | 4 ++-- lib/matplotlib/ticker.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/api/engineering_formatter.py b/examples/api/engineering_formatter.py index 2994465752c9..b49852b507aa 100644 --- a/examples/api/engineering_formatter.py +++ b/examples/api/engineering_formatter.py @@ -34,8 +34,8 @@ # Demo of the options `places` (number of digit after decimal point) and # `sep` (separator between the number and the prefix/unit). ax1.set_title('SI-prefix only ticklabels, 1-digit precision & ' + - 'hair space separator') -formatter1 = EngFormatter(places=1, sep=u"\u200a") # U+200A is hair space + 'thin space separator') +formatter1 = EngFormatter(places=1, sep=u"\N{THIN SPACE}") # U+2009 ax1.xaxis.set_major_formatter(formatter1) ax1.plot(xs, ys) ax1.set_xlabel('Frequency [Hz]') diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 0bc0f1af3cb3..8e86f485b8c7 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1228,9 +1228,9 @@ def __init__(self, unit="", places=None, sep=" "): other useful options may be: * ``sep=""`` to append directly the prefix/unit to the value; - * ``sep="\\u2009"`` to use a thin space; - * ``sep="\\u202f"`` to use a narrow no-break space; - * ``sep="\\u00a0"`` to use a no-break space. + * ``sep="\\N{THIN SPACE}"`` (``U+2009``); + * ``sep="\\N{NARROW NO-BREAK SPACE}"`` (``U+202F``); + * ``sep="\\N{NO-BREAK SPACE}"`` (``U+00A0``). """ self.unit = unit self.places = places From 7f1422ca3da3fc5fff27ea148f1d7dce07a35548 Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Thu, 17 Aug 2017 08:32:11 -0700 Subject: [PATCH 16/25] stop mixing C-style and format-based formatting --- 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 8e86f485b8c7..587d1d630c7e 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1285,16 +1285,16 @@ def format_eng(self, num): mant = sign * dnum / (10 ** pow10) - # TODO: shouldn't we raise a warning if self.places < 0? if self.places is None: - format_str = "%g{sep:s}%s".format(sep=self.sep) + fmt = "g" elif self.places == 0: - format_str = "%d{sep:s}%s".format(sep=self.sep) - elif self.places > 0: - format_str = "%.{p:d}f{sep:s}%s".format(p=self.places, - sep=self.sep) + fmt = "d" + else: + fmt = ".{}f".format(self.places) + + formatted = "{mant:{fmt}}{sep}{prefix}".format( + mant=mant, fmt=fmt, sep=self.sep, prefix=prefix) - formatted = format_str % (mant, prefix) return formatted From aba7a7fe51cd8ba415aa697612c6673dae6db282 Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Thu, 17 Aug 2017 08:45:43 -0700 Subject: [PATCH 17/25] Small example tweaking to avoid label cluttering --- examples/api/engineering_formatter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/api/engineering_formatter.py b/examples/api/engineering_formatter.py index b49852b507aa..cb90416320dd 100644 --- a/examples/api/engineering_formatter.py +++ b/examples/api/engineering_formatter.py @@ -20,7 +20,7 @@ ys = (0.8 + 0.4 * prng.uniform(size=100)) * np.log10(xs)**2 # Figure width is doubled (2*6.4) to display nicely 2 subplots side by side. -fig, (ax0, ax1) = plt.subplots(ncols=2, figsize=(12.8, 4.8)) +fig, (ax0, ax1) = plt.subplots(nrows=2, figsize=(7, 9.6)) for ax in (ax0, ax1): ax.set_xscale('log') @@ -40,4 +40,5 @@ ax1.plot(xs, ys) ax1.set_xlabel('Frequency [Hz]') +plt.tight_layout() plt.show() From b75f20d8fbe015e6f6c3161c46baff59f85ffaa2 Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Thu, 17 Aug 2017 16:35:12 -0700 Subject: [PATCH 18/25] fix an issue with {:g} and str format --- lib/matplotlib/ticker.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 587d1d630c7e..74d90c7bea5d 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1285,16 +1285,15 @@ def format_eng(self, num): mant = sign * dnum / (10 ** pow10) - if self.places is None: - fmt = "g" - elif self.places == 0: - fmt = "d" - else: - fmt = ".{}f".format(self.places) - + # NB: one has to cast *mant* to a float because it is actually + # an instance of decimal.Decimal. Combined for `str.format`, this + # may produce strings with more than 6 digits in the case of the + # "%g" format, which breaks the former behavior that one got with + # C-style formatting. Another option would be to rely on the + # `decimal.localcontext()` context manager. formatted = "{mant:{fmt}}{sep}{prefix}".format( - mant=mant, fmt=fmt, sep=self.sep, prefix=prefix) - + mant=float(mant), sep=self.sep, prefix=prefix, + fmt="g" if self.places is None else ".{:d}f".format(self.places)) return formatted From 41a69da42c459cae2547277475626f71cf63e1ac Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Sat, 19 Aug 2017 10:56:19 -0700 Subject: [PATCH 19/25] get rid of decimal.Decimal + fix rounding special cases like 999.9... --- lib/matplotlib/ticker.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 74d90c7bea5d..0e402b725aa1 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -173,7 +173,6 @@ import six -import decimal import itertools import locale import math @@ -1259,10 +1258,9 @@ def format_eng(self, num): u'-1.00 \N{GREEK SMALL LETTER MU}' `num` may be a numeric value or a string that can be converted - to a numeric value with the `decimal.Decimal` constructor. + to a numeric value with `float(num)`. """ - dnum = decimal.Decimal(str(num)) - + dnum = float(num) sign = 1 if dnum < 0: @@ -1270,29 +1268,28 @@ def format_eng(self, num): dnum = -dnum if dnum != 0: - pow10 = decimal.Decimal(int(math.floor(dnum.log10() / 3) * 3)) + pow10 = int(math.floor(math.log10(dnum) / 3) * 3) else: - pow10 = decimal.Decimal(0) + pow10 = 0 # Force dnum to zero, to avoid inconsistencies like # format_eng(-0) = "0" and format_eng(0.0) = "0" # but format_eng(-0.0) = "-0.0" - dnum = decimal.Decimal(0) - - pow10 = pow10.min(max(self.ENG_PREFIXES)) - pow10 = pow10.max(min(self.ENG_PREFIXES)) + dnum = 0.0 - prefix = self.ENG_PREFIXES[int(pow10)] + pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES)) mant = sign * dnum / (10 ** pow10) + # Taking care of the cases like 999.9..., which + # may be rounded to 1000 instead of 1 k. + if (self.places is not None and + round(mant, self.places) >= 1000): + mant /= 1000 + pow10 += 3 + + prefix = self.ENG_PREFIXES[int(pow10)] - # NB: one has to cast *mant* to a float because it is actually - # an instance of decimal.Decimal. Combined for `str.format`, this - # may produce strings with more than 6 digits in the case of the - # "%g" format, which breaks the former behavior that one got with - # C-style formatting. Another option would be to rely on the - # `decimal.localcontext()` context manager. formatted = "{mant:{fmt}}{sep}{prefix}".format( - mant=float(mant), sep=self.sep, prefix=prefix, + mant=mant, sep=self.sep, prefix=prefix, fmt="g" if self.places is None else ".{:d}f".format(self.places)) return formatted From 556ec20b4f81da4b59d8075e2ac1b3784afd609b Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Sat, 19 Aug 2017 10:58:25 -0700 Subject: [PATCH 20/25] Fix the related rounding discrepancies in test_ticker --- lib/matplotlib/tests/test_ticker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index b68641c392a6..d34844e5959a 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -564,11 +564,11 @@ class TestEngFormatter(object): (0.1, ('100 m', '100 m', '100.00 m')), (1, ('1', '1', '1.00')), (1.23456789, ('1.23457', '1', '1.23')), - (999.9, ('999.9', '999', '999.90')), + (999.9, ('999.9', '1 k', '999.90')), (1000, ('1 k', '1 k', '1.00 k')), (1001, ('1.001 k', '1 k', '1.00 k')), (100001, ('100.001 k', '100 k', '100.00 k')), - (987654.321, ('987.654 k', '987 k', '987.65 k')) + (987654.321, ('987.654 k', '988 k', '987.65 k')) ] @pytest.mark.parametrize('input, expected', raw_format_data) From a9af4310202b234372d142de9f8e1ad460b04c33 Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Sat, 19 Aug 2017 11:25:06 -0700 Subject: [PATCH 21/25] (try) fix(ing) an exception about raising an int to a negative power --- lib/matplotlib/ticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 0e402b725aa1..320da41c37ec 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1278,7 +1278,7 @@ def format_eng(self, num): pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES)) - mant = sign * dnum / (10 ** pow10) + mant = sign * dnum / (10.0 ** pow10) # Taking care of the cases like 999.9..., which # may be rounded to 1000 instead of 1 k. if (self.places is not None and From 0bde03a9f5516ac3f6ccaf4cfcd887191a7e116f Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Sun, 20 Aug 2017 12:34:58 -0700 Subject: [PATCH 22/25] fix some anntzer's comments --- lib/matplotlib/tests/test_ticker.py | 2 +- lib/matplotlib/ticker.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index d34844e5959a..a4d213905edb 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -612,7 +612,7 @@ def test_params(self, input, expected): # Test several non default separators: no separator, a narrow # no-break space (unicode character) and an extravagant string. - for _sep in ("", "\u202f", "@_@"): + for _sep in ("", "\N{NARROW NO-BREAK SPACE}", "@_@"): # Case 2: unit=UNIT and sep=_sep. # Replace the default space separator from the reference case # with the tested one `_sep` and append a unit symbol to it. diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 320da41c37ec..3ec093d71298 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1218,7 +1218,10 @@ def __init__(self, unit="", places=None, sep=" "): Precision with which to display the number, specified in digits after the decimal point (there will be between one and three digits before the decimal point). If it is None, - falls back to the floating point format '%g'. + the formatting falls back to the floating point format '%g', + which displays up to 6 *significant* digits (i.e. the + equivalent value for *places* varies between 0 and 5, both + values included). sep : str (default: " ") Separator used between the value and the prefix/unit. For @@ -1258,7 +1261,7 @@ def format_eng(self, num): u'-1.00 \N{GREEK SMALL LETTER MU}' `num` may be a numeric value or a string that can be converted - to a numeric value with `float(num)`. + to a numeric value with ``float(num)``. """ dnum = float(num) sign = 1 From c98ba91dbe01e08cbfcab5b84c06b27096e8e31e Mon Sep 17 00:00:00 2001 From: "Adrien F. VINCENT" Date: Sun, 20 Aug 2017 12:58:25 -0700 Subject: [PATCH 23/25] more complete handling of corner-case roundings + add proper tests --- lib/matplotlib/tests/test_ticker.py | 6 ++++-- lib/matplotlib/ticker.py | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index a4d213905edb..921d40cae3f7 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -564,11 +564,13 @@ class TestEngFormatter(object): (0.1, ('100 m', '100 m', '100.00 m')), (1, ('1', '1', '1.00')), (1.23456789, ('1.23457', '1', '1.23')), - (999.9, ('999.9', '1 k', '999.90')), + (999.9, ('999.9', '1 k', '999.90')), # places=0: corner-case rounding + (999.9999, ('1 k', '1 k', '1.00 k')), # corner-case roudning for all (1000, ('1 k', '1 k', '1.00 k')), (1001, ('1.001 k', '1 k', '1.00 k')), (100001, ('100.001 k', '100 k', '100.00 k')), - (987654.321, ('987.654 k', '988 k', '987.65 k')) + (987654.321, ('987.654 k', '988 k', '987.65 k')), + (1.23e27, ('1230 Y', '1230 Y', '1230.00 Y')) # OoR value (> 1000 Y) ] @pytest.mark.parametrize('input, expected', raw_format_data) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 3ec093d71298..9f3e6df5bdb5 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1265,6 +1265,7 @@ def format_eng(self, num): """ dnum = float(num) sign = 1 + fmt = "g" if self.places is None else ".{:d}f".format(self.places) if dnum < 0: sign = -1 @@ -1283,17 +1284,18 @@ def format_eng(self, num): mant = sign * dnum / (10.0 ** pow10) # Taking care of the cases like 999.9..., which - # may be rounded to 1000 instead of 1 k. - if (self.places is not None and - round(mant, self.places) >= 1000): + # may be rounded to 1000 instead of 1 k. Beware + # of the corner case of values that are beyond + # the range of SI prefixes (i.e. > 'Y'). + _fmant = float("{mant:{fmt}}".format(mant=mant, fmt=fmt)) + if _fmant >= 1000 and pow10 != max(self.ENG_PREFIXES): mant /= 1000 pow10 += 3 prefix = self.ENG_PREFIXES[int(pow10)] formatted = "{mant:{fmt}}{sep}{prefix}".format( - mant=mant, sep=self.sep, prefix=prefix, - fmt="g" if self.places is None else ".{:d}f".format(self.places)) + mant=mant, sep=self.sep, prefix=prefix, fmt=fmt) return formatted From 60b95abfb4d4e11f3f629f8fece63c94c35cb059 Mon Sep 17 00:00:00 2001 From: "Adrien F. Vincent" Date: Mon, 21 Aug 2017 09:52:06 -0700 Subject: [PATCH 24/25] Fix some additional remarks made by anntzer --- examples/api/engineering_formatter.py | 2 +- lib/matplotlib/ticker.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/api/engineering_formatter.py b/examples/api/engineering_formatter.py index cb90416320dd..4d9f2dfdec90 100644 --- a/examples/api/engineering_formatter.py +++ b/examples/api/engineering_formatter.py @@ -33,7 +33,7 @@ # Demo of the options `places` (number of digit after decimal point) and # `sep` (separator between the number and the prefix/unit). -ax1.set_title('SI-prefix only ticklabels, 1-digit precision & ' + +ax1.set_title('SI-prefix only ticklabels, 1-digit precision & ' 'thin space separator') formatter1 = EngFormatter(places=1, sep=u"\N{THIN SPACE}") # U+2009 ax1.xaxis.set_major_formatter(formatter1) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 9f3e6df5bdb5..80c79df758cd 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1219,9 +1219,8 @@ def __init__(self, unit="", places=None, sep=" "): digits after the decimal point (there will be between one and three digits before the decimal point). If it is None, the formatting falls back to the floating point format '%g', - which displays up to 6 *significant* digits (i.e. the - equivalent value for *places* varies between 0 and 5, both - values included). + which displays up to 6 *significant* digits, i.e. the equivalent + value for *places* varies between 0 and 5 (inclusive). sep : str (default: " ") Separator used between the value and the prefix/unit. For From ac42b94e990e878a2b831cf3e4382cf6cb40a1b5 Mon Sep 17 00:00:00 2001 From: "Adrien F. Vincent" Date: Thu, 24 Aug 2017 14:41:44 -0700 Subject: [PATCH 25/25] Deprecate passing a string as *num* argument --- .../api_changes/2017-08-24-deprecation-in-engformatter.rst | 5 +++++ lib/matplotlib/ticker.py | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 doc/api/api_changes/2017-08-24-deprecation-in-engformatter.rst diff --git a/doc/api/api_changes/2017-08-24-deprecation-in-engformatter.rst b/doc/api/api_changes/2017-08-24-deprecation-in-engformatter.rst new file mode 100644 index 000000000000..630bab605668 --- /dev/null +++ b/doc/api/api_changes/2017-08-24-deprecation-in-engformatter.rst @@ -0,0 +1,5 @@ +Deprecation in EngFormatter +``````````````````````````` + +Passing a string as *num* argument when calling an instance of +`matplotlib.ticker.EngFormatter` is deprecated and will be removed in 2.3. diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 80c79df758cd..84933aeb0e35 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1262,6 +1262,12 @@ def format_eng(self, num): `num` may be a numeric value or a string that can be converted to a numeric value with ``float(num)``. """ + if isinstance(num, six.string_types): + warnings.warn( + "Passing a string as *num* argument is deprecated since" + "Matplotlib 2.1, and is expected to be removed in 2.3.", + mplDeprecation) + dnum = float(num) sign = 1 fmt = "g" if self.places is None else ".{:d}f".format(self.places)