Skip to content

Commit 43a6bd6

Browse files
committed
ENH: Fixed PercentFormatter usage with latex
Added an example to the big formatter example Added test for latex correctness Modified existing pylab example
1 parent 1f999f4 commit 43a6bd6

File tree

4 files changed

+101
-60
lines changed

4 files changed

+101
-60
lines changed
Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
11
import matplotlib
22
from numpy.random import randn
33
import matplotlib.pyplot as plt
4-
from matplotlib.ticker import FuncFormatter
4+
from matplotlib.ticker import PercentFormatter
55

66

7-
def to_percent(y, position):
8-
# Ignore the passed in position. This has the effect of scaling the default
9-
# tick locations.
10-
s = str(100 * y)
11-
12-
# The percent symbol needs escaping in latex
13-
if matplotlib.rcParams['text.usetex'] is True:
14-
return s + r'$\%$'
15-
else:
16-
return s + '%'
17-
187
x = randn(5000)
198

20-
# Make a normed histogram. It'll be multiplied by 100 later.
21-
plt.hist(x, bins=50, normed=True)
9+
# Create a figure with some axes. This makes it easier to set the
10+
# formatter later, since that is only available through the OO API.
11+
fig, ax = plt.subplots()
2212

23-
# Create the formatter using the function to_percent. This multiplies all the
24-
# default labels by 100, making them all percentages
25-
formatter = FuncFormatter(to_percent)
13+
# Make a normed histogram. It'll be multiplied by 100 later.
14+
plt.hist(x, bins=50, normed=True, axes=ax)
2615

27-
# Set the formatter
28-
plt.gca().yaxis.set_major_formatter(formatter)
16+
# Set the formatter. `xmax` sets the value that maps to 100%.
17+
ax.yaxis.set_major_formatter(PercentFormatter(xmax=1))
2918

3019
plt.show()

examples/ticks_and_spines/tick-formatters.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def setup(ax):
2121
ax.patch.set_alpha(0.0)
2222

2323

24-
plt.figure(figsize=(8, 5))
25-
n = 6
24+
plt.figure(figsize=(8, 6))
25+
n = 7
2626

2727
# Null formatter
2828
ax = plt.subplot(n, 1, 1)
@@ -87,6 +87,15 @@ def major_formatter(x, pos):
8787
ax.text(0.0, 0.1, "StrMethodFormatter('{x}')",
8888
fontsize=15, transform=ax.transAxes)
8989

90+
# Percent formatter
91+
ax = plt.subplot(n, 1, 7)
92+
setup(ax)
93+
ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00))
94+
ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25))
95+
ax.xaxis.set_major_formatter(ticker.PercentFormatter(xmax=5))
96+
ax.text(0.0, 0.1, "PercentFormatter(xmax=5)",
97+
fontsize=15, transform=ax.transAxes)
98+
9099
# Push the top of the top axes outside the figure because we only show the
91100
# bottom spine.
92101
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=1.05)

lib/matplotlib/tests/test_ticker.py

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -510,13 +510,40 @@ def test_basic(self):
510510
tmp_form = mticker.FormatStrFormatter('%05d')
511511
assert '00002' == tmp_form(2)
512512

513-
# test str.format() style formatter
514-
tmp_form = mticker.StrMethodFormatter('{x:05d}')
515-
assert '00002' == tmp_form(2)
516513

517-
# test str.format() style formatter with `pos`
518-
tmp_form = mticker.StrMethodFormatter('{x:03d}-{pos:02d}')
519-
assert '002-01' == tmp_form(2, 1)
514+
class TestStrMethodFormatter(object):
515+
test_data = [
516+
('{x:05d}', (2,), '00002'),
517+
('{x:03d}-{pos:02d}', (2, 1), '002-01'),
518+
]
519+
520+
@pytest.mark.parametrize('format, input, expected', test_data)
521+
def test_basic(self, format, input, expected):
522+
fmt = mticker.StrMethodFormatter(format)
523+
assert fmt(*input) == expected
524+
525+
526+
class TestEngFormatter(object):
527+
format_data = [
528+
('', 0.1, u'100 m'),
529+
('', 1, u'1'),
530+
('', 999.9, u'999.9'),
531+
('', 1001, u'1.001 k'),
532+
(u's', 0.1, u'100 ms'),
533+
(u's', 1, u'1 s'),
534+
(u's', 999.9, u'999.9 s'),
535+
(u's', 1001, u'1.001 ks'),
536+
]
537+
538+
@pytest.mark.parametrize('unit, input, expected', format_data)
539+
def test_formatting(self, unit, input, expected):
540+
"""
541+
Test the formatting of EngFormatter with some inputs, against
542+
instances with and without units. Cases focus on when no SI
543+
prefix is present, for values in [1, 1000).
544+
"""
545+
fmt = mticker.EngFormatter(unit)
546+
assert fmt(input) == expected
520547

521548

522549
class TestPercentFormatter(object):
@@ -525,12 +552,12 @@ class TestPercentFormatter(object):
525552
(100, 0, '%', 120, 100, '120%'),
526553
(100, 0, '%', 100, 90, '100%'),
527554
(100, 0, '%', 90, 50, '90%'),
528-
(100, 0, '%', 1.7, 40, '2%'),
555+
(100, 0, '%', -1.7, 40, '-2%'),
529556
(100, 1, '%', 90.0, 100, '90.0%'),
530557
(100, 1, '%', 80.1, 90, '80.1%'),
531558
(100, 1, '%', 70.23, 50, '70.2%'),
532559
# 60.554 instead of 60.55: see https://bugs.python.org/issue5118
533-
(100, 1, '%', 60.554, 40, '60.6%'),
560+
(100, 1, '%', -60.554, 40, '-60.6%'),
534561
# Check auto decimals over different intervals and values
535562
(100, None, '%', 95, 1, '95.00%'),
536563
(1.0, None, '%', 3, 6, '300%'),
@@ -565,33 +592,24 @@ class TestPercentFormatter(object):
565592
'Custom percent symbol',
566593
]
567594

595+
latex_data = [
596+
(False, False, r'50\{t}%'),
597+
(False, True, r'50\\\{t\}\%'),
598+
(True, False, r'50\{t}%'),
599+
(True, True, r'50\{t}%'),
600+
]
601+
568602
@pytest.mark.parametrize(
569603
'xmax, decimals, symbol, x, display_range, expected',
570604
percent_data, ids=percent_ids)
571-
def test_percentformatter(self, xmax, decimals, symbol,
605+
def test_basic(self, xmax, decimals, symbol,
572606
x, display_range, expected):
573607
formatter = mticker.PercentFormatter(xmax, decimals, symbol)
574-
assert formatter.format_pct(x, display_range) == expected
575-
576-
577-
class TestEngFormatter(object):
578-
format_data = [
579-
('', 0.1, u'100 m'),
580-
('', 1, u'1'),
581-
('', 999.9, u'999.9'),
582-
('', 1001, u'1.001 k'),
583-
(u's', 0.1, u'100 ms'),
584-
(u's', 1, u'1 s'),
585-
(u's', 999.9, u'999.9 s'),
586-
(u's', 1001, u'1.001 ks'),
587-
]
588-
589-
@pytest.mark.parametrize('unit, input, expected', format_data)
590-
def test_formatting(self, unit, input, expected):
591-
"""
592-
Test the formatting of EngFormatter with some inputs, against
593-
instances with and without units. Cases focus on when no SI
594-
prefix is present, for values in [1, 1000).
595-
"""
596-
fmt = mticker.EngFormatter(unit)
597-
assert fmt(input) == expected
608+
with matplotlib.rc_context(rc={'text.usetex': False}):
609+
assert formatter.format_pct(x, display_range) == expected
610+
611+
@pytest.mark.parametrize('is_latex, usetex, expected', latex_data)
612+
def test_latex(self, is_latex, usetex, expected):
613+
fmt = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex)
614+
with matplotlib.rc_context(rc={'text.usetex': usetex}):
615+
assert fmt.format_pct(50, 100) == expected

lib/matplotlib/ticker.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,16 +1285,19 @@ class PercentFormatter(Formatter):
12851285
situation is where `xmax` is 1.0.
12861286
12871287
`symbol` is a string which will be appended to the label. It may be
1288-
`None` or empty to indicate that no symbol should be used.
1288+
`None` or empty to indicate that no symbol should be used. LaTeX
1289+
special characters are escaped in `symbol` whenever latex mode is
1290+
enabled, unless `is_latex` is `True`.
12891291
12901292
`decimals` is the number of decimal places to place after the point.
12911293
If it is set to `None` (the default), the number will be computed
12921294
automatically.
12931295
"""
1294-
def __init__(self, xmax=100, decimals=None, symbol='%'):
1296+
def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False):
12951297
self.xmax = xmax + 0.0
12961298
self.decimals = decimals
1297-
self.symbol = symbol
1299+
self._symbol = symbol
1300+
self._is_latex = is_latex
12981301

12991302
def __call__(self, x, pos=None):
13001303
"""
@@ -1350,13 +1353,35 @@ def format_pct(self, x, display_range):
13501353
decimals = self.decimals
13511354
s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals))
13521355

1353-
if self.symbol:
1354-
return s + self.symbol
1355-
return s
1356+
return s + self.symbol
13561357

13571358
def convert_to_pct(self, x):
13581359
return 100.0 * (x / self.xmax)
13591360

1361+
@property
1362+
def symbol(self):
1363+
"""
1364+
The configured percent symbol as a string.
1365+
1366+
If LaTeX is enabled via ``rcParams['text.usetex']``, the special
1367+
characters `{'#', '$', '%', '&', '~', '_', '^', '\\', '{', '}'}`
1368+
are automatically escaped in the string.
1369+
"""
1370+
symbol = self._symbol
1371+
if not symbol:
1372+
symbol = ''
1373+
elif rcParams['text.usetex'] and not self._is_latex:
1374+
# Source: http://www.personal.ceu.hu/tex/specchar.htm
1375+
# Backslash must be first for this to work correctly since
1376+
# it keeps getting added in
1377+
for spec in r'\#$%&~_^{}':
1378+
symbol = symbol.replace(spec, '\\' + spec)
1379+
return symbol
1380+
1381+
@symbol.setter
1382+
def symbol(self):
1383+
self._symbol = symbol
1384+
13601385

13611386
class Locator(TickHelper):
13621387
"""

0 commit comments

Comments
 (0)
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