From 6add6ce677fea5c72e4921d972ab1dbf44f0a71e Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Fri, 27 Jan 2017 17:02:25 -0500 Subject: [PATCH] ENH: Fixed PercentFormatter usage with latex Added an example to the big formatter example Added test for latex correctness Modified existing pylab example --- .../pylab_examples/histogram_percent_demo.py | 27 ++---- examples/ticks_and_spines/tick-formatters.py | 13 ++- lib/matplotlib/tests/test_ticker.py | 86 +++++++++++-------- lib/matplotlib/ticker.py | 37 ++++++-- 4 files changed, 102 insertions(+), 61 deletions(-) diff --git a/examples/pylab_examples/histogram_percent_demo.py b/examples/pylab_examples/histogram_percent_demo.py index 9d7b5d34423f..ce62b0a16017 100644 --- a/examples/pylab_examples/histogram_percent_demo.py +++ b/examples/pylab_examples/histogram_percent_demo.py @@ -1,30 +1,19 @@ import matplotlib from numpy.random import randn import matplotlib.pyplot as plt -from matplotlib.ticker import FuncFormatter +from matplotlib.ticker import PercentFormatter -def to_percent(y, position): - # Ignore the passed in position. This has the effect of scaling the default - # tick locations. - s = str(100 * y) - - # The percent symbol needs escaping in latex - if matplotlib.rcParams['text.usetex'] is True: - return s + r'$\%$' - else: - return s + '%' - x = randn(5000) -# Make a normed histogram. It'll be multiplied by 100 later. -plt.hist(x, bins=50, normed=True) +# Create a figure with some axes. This makes it easier to set the +# formatter later, since that is only available through the OO API. +fig, ax = plt.subplots() -# Create the formatter using the function to_percent. This multiplies all the -# default labels by 100, making them all percentages -formatter = FuncFormatter(to_percent) +# Make a normed histogram. It'll be multiplied by 100 later. +ax.hist(x, bins=50, normed=True) -# Set the formatter -plt.gca().yaxis.set_major_formatter(formatter) +# Set the formatter. `xmax` sets the value that maps to 100%. +ax.yaxis.set_major_formatter(PercentFormatter(xmax=1)) plt.show() diff --git a/examples/ticks_and_spines/tick-formatters.py b/examples/ticks_and_spines/tick-formatters.py index 19b486de69e9..13f17ab1ac02 100644 --- a/examples/ticks_and_spines/tick-formatters.py +++ b/examples/ticks_and_spines/tick-formatters.py @@ -21,8 +21,8 @@ def setup(ax): ax.patch.set_alpha(0.0) -plt.figure(figsize=(8, 5)) -n = 6 +plt.figure(figsize=(8, 6)) +n = 7 # Null formatter ax = plt.subplot(n, 1, 1) @@ -87,6 +87,15 @@ def major_formatter(x, pos): ax.text(0.0, 0.1, "StrMethodFormatter('{x}')", fontsize=15, transform=ax.transAxes) +# Percent formatter +ax = plt.subplot(n, 1, 7) +setup(ax) +ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00)) +ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25)) +ax.xaxis.set_major_formatter(ticker.PercentFormatter(xmax=5)) +ax.text(0.0, 0.1, "PercentFormatter(xmax=5)", + fontsize=15, transform=ax.transAxes) + # Push the top of the top axes outside the figure because we only show the # bottom spine. plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=1.05) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 443fa05d8dcf..d6eee0d6d7bd 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -510,13 +510,40 @@ def test_basic(self): tmp_form = mticker.FormatStrFormatter('%05d') assert '00002' == tmp_form(2) - # test str.format() style formatter - tmp_form = mticker.StrMethodFormatter('{x:05d}') - assert '00002' == tmp_form(2) - # test str.format() style formatter with `pos` - tmp_form = mticker.StrMethodFormatter('{x:03d}-{pos:02d}') - assert '002-01' == tmp_form(2, 1) +class TestStrMethodFormatter(object): + test_data = [ + ('{x:05d}', (2,), '00002'), + ('{x:03d}-{pos:02d}', (2, 1), '002-01'), + ] + + @pytest.mark.parametrize('format, input, expected', test_data) + def test_basic(self, format, input, expected): + fmt = mticker.StrMethodFormatter(format) + assert fmt(*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'), + ] + + @pytest.mark.parametrize('unit, input, expected', format_data) + def test_formatting(self, unit, 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). + """ + fmt = mticker.EngFormatter(unit) + assert fmt(input) == expected class TestPercentFormatter(object): @@ -525,12 +552,12 @@ class TestPercentFormatter(object): (100, 0, '%', 120, 100, '120%'), (100, 0, '%', 100, 90, '100%'), (100, 0, '%', 90, 50, '90%'), - (100, 0, '%', 1.7, 40, '2%'), + (100, 0, '%', -1.7, 40, '-2%'), (100, 1, '%', 90.0, 100, '90.0%'), (100, 1, '%', 80.1, 90, '80.1%'), (100, 1, '%', 70.23, 50, '70.2%'), # 60.554 instead of 60.55: see https://bugs.python.org/issue5118 - (100, 1, '%', 60.554, 40, '60.6%'), + (100, 1, '%', -60.554, 40, '-60.6%'), # Check auto decimals over different intervals and values (100, None, '%', 95, 1, '95.00%'), (1.0, None, '%', 3, 6, '300%'), @@ -565,33 +592,24 @@ class TestPercentFormatter(object): 'Custom percent symbol', ] + latex_data = [ + (False, False, r'50\{t}%'), + (False, True, r'50\\\{t\}\%'), + (True, False, r'50\{t}%'), + (True, True, r'50\{t}%'), + ] + @pytest.mark.parametrize( 'xmax, decimals, symbol, x, display_range, expected', percent_data, ids=percent_ids) - def test_percentformatter(self, xmax, decimals, symbol, - x, display_range, expected): + def test_basic(self, xmax, decimals, symbol, + x, display_range, expected): formatter = mticker.PercentFormatter(xmax, decimals, symbol) - assert formatter.format_pct(x, display_range) == 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'), - ] - - @pytest.mark.parametrize('unit, input, expected', format_data) - def test_formatting(self, unit, 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). - """ - fmt = mticker.EngFormatter(unit) - assert fmt(input) == expected + with matplotlib.rc_context(rc={'text.usetex': False}): + assert formatter.format_pct(x, display_range) == expected + + @pytest.mark.parametrize('is_latex, usetex, expected', latex_data) + def test_latex(self, is_latex, usetex, expected): + fmt = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex) + with matplotlib.rc_context(rc={'text.usetex': usetex}): + assert fmt.format_pct(50, 100) == expected diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 5b5f0de93974..ddfae88619a5 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1285,16 +1285,19 @@ class PercentFormatter(Formatter): situation is where `xmax` is 1.0. `symbol` is a string which will be appended to the label. It may be - `None` or empty to indicate that no symbol should be used. + `None` or empty to indicate that no symbol should be used. LaTeX + special characters are escaped in `symbol` whenever latex mode is + enabled, unless `is_latex` is `True`. `decimals` is the number of decimal places to place after the point. If it is set to `None` (the default), the number will be computed automatically. """ - def __init__(self, xmax=100, decimals=None, symbol='%'): + def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False): self.xmax = xmax + 0.0 self.decimals = decimals - self.symbol = symbol + self._symbol = symbol + self._is_latex = is_latex def __call__(self, x, pos=None): """ @@ -1350,13 +1353,35 @@ def format_pct(self, x, display_range): decimals = self.decimals s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals)) - if self.symbol: - return s + self.symbol - return s + return s + self.symbol def convert_to_pct(self, x): return 100.0 * (x / self.xmax) + @property + def symbol(self): + """ + The configured percent symbol as a string. + + If LaTeX is enabled via ``rcParams['text.usetex']``, the special + characters `{'#', '$', '%', '&', '~', '_', '^', '\\', '{', '}'}` + are automatically escaped in the string. + """ + symbol = self._symbol + if not symbol: + symbol = '' + elif rcParams['text.usetex'] and not self._is_latex: + # Source: http://www.personal.ceu.hu/tex/specchar.htm + # Backslash must be first for this to work correctly since + # it keeps getting added in + for spec in r'\#$%&~_^{}': + symbol = symbol.replace(spec, '\\' + spec) + return symbol + + @symbol.setter + def symbol(self): + self._symbol = symbol + class Locator(TickHelper): """ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy