Skip to content

Commit c33266b

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 87885e7 commit c33266b

File tree

4 files changed

+107
-53
lines changed

4 files changed

+107
-53
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=(9, 5))
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: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -511,35 +511,66 @@ def test_formatstrformatter():
511511
assert '002-01' == tmp_form(2, 1)
512512

513513

514-
percentformatter_test_cases = (
515-
# Check explicitly set decimals over different intervals and values
516-
(100, 0, '%', 120, 100, '120%'),
517-
(100, 0, '%', 100, 90, '100%'),
518-
(100, 0, '%', 90, 50, '90%'),
519-
(100, 0, '%', 1.7, 40, '2%'),
520-
(100, 1, '%', 90.0, 100, '90.0%'),
521-
(100, 1, '%', 80.1, 90, '80.1%'),
522-
(100, 1, '%', 70.23, 50, '70.2%'),
523-
# 60.554 instead of 60.55: see https://bugs.python.org/issue5118
524-
(100, 1, '%', 60.554, 40, '60.6%'),
525-
# Check auto decimals over different intervals and values
526-
(100, None, '%', 95, 1, '95.00%'),
527-
(1.0, None, '%', 3, 6, '300%'),
528-
(17.0, None, '%', 1, 8.5, '6%'),
529-
(17.0, None, '%', 1, 8.4, '5.9%'),
530-
(5, None, '%', -100, 0.000001, '-2000.00000%'),
531-
# Check percent symbol
532-
(1.0, 2, None, 1.2, 100, '120.00'),
533-
(75, 3, '', 50, 100, '66.667'),
534-
(42, None, '^^Foobar$$', 21, 12, '50.0^^Foobar$$'),
535-
)
536-
537-
538514
@pytest.mark.parametrize('xmax, decimals, symbol, x, display_range, expected',
539-
percentformatter_test_cases)
515+
[
516+
# Check explicitly set decimals over different intervals and values
517+
(100, 0, '%', 120, 100, '120%'),
518+
(100, 0, '%', 100, 90, '100%'),
519+
(100, 0, '%', 90, 50, '90%'),
520+
(100, 0, '%', -1.7, 40, '-2%'),
521+
(100, 1, '%', 90.0, 100, '90.0%'),
522+
(100, 1, '%', 80.1, 90, '80.1%'),
523+
(100, 1, '%', 70.23, 50, '70.2%'),
524+
# 60.554 instead of 60.55: see https://bugs.python.org/issue5118
525+
(100, 1, '%', -60.554, 40, '-60.6%'),
526+
# Check auto decimals over different intervals and values
527+
(100, None, '%', 95, 1, '95.00%'),
528+
(1.0, None, '%', 3, 6, '300%'),
529+
(17.0, None, '%', 1, 8.5, '6%'),
530+
(17.0, None, '%', 1, 8.4, '5.9%'),
531+
(5, None, '%', -100, 0.000001, '-2000.00000%'),
532+
# Check percent symbol
533+
(1.0, 2, None, 1.2, 100, '120.00'),
534+
(75, 3, '', 50, 100, '66.667'),
535+
(42, None, '^^Foobar$$', 21, 12, '50.0^^Foobar$$'),
536+
], ids=[
537+
# Check explicitly set decimals over different intervals and values
538+
'decimals=0, x>100%',
539+
'decimals=0, x=100%',
540+
'decimals=0, x<100%',
541+
'decimals=0, x<0%',
542+
'decimals=1, x>100%',
543+
'decimals=1, x=100%',
544+
'decimals=1, x<100%',
545+
'decimals=1, x<0%',
546+
# Check auto decimals over different intervals and values
547+
'autodecimal, x<100%, display_range=1',
548+
'autodecimal, x>100%, display_range=6 (custom xmax test)',
549+
'autodecimal, x<100%, display_range=8.5 (autodecimal test 1)',
550+
'autodecimal, x<100%, display_range=8.4 (autodecimal test 2)',
551+
'autodecimal, x<-100%, display_range=1e-6 (tiny display range)',
552+
# Check percent symbol
553+
'None as percent symbol',
554+
'Empty percent symbol',
555+
'Custom percent symbol',
556+
])
540557
def test_percentformatter(xmax, decimals, symbol, x, display_range, expected):
541558
formatter = mticker.PercentFormatter(xmax, decimals, symbol)
542-
assert formatter.format_pct(x, display_range) == expected
559+
with matplotlib.rc_context(rc={'text.usetex': False}):
560+
assert formatter.format_pct(x, display_range) == expected
561+
562+
563+
@pytest.mark.parametrize('is_latex, usetex, expected',
564+
[
565+
(False, False, r'50\{t}%'),
566+
(False, True, r'50\\\{t\}\%'),
567+
(True, False, r'50\{t}%'),
568+
(True, True, r'50\{t}%'),
569+
])
570+
def test_percentformatter_latex(is_latex, usetex, expected):
571+
formatter = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex)
572+
with matplotlib.rc_context(rc={'text.usetex': usetex}):
573+
assert formatter.format_pct(50, 100) == expected
543574

544575

545576
def test_EngFormatter_formatting():

lib/matplotlib/ticker.py

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

13021305
def __call__(self, x, pos=None):
13031306
"""
@@ -1353,13 +1356,35 @@ def format_pct(self, x, display_range):
13531356
decimals = self.decimals
13541357
s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals))
13551358

1356-
if self.symbol:
1357-
return s + self.symbol
1358-
return s
1359+
return s + self.symbol
13591360

13601361
def convert_to_pct(self, x):
13611362
return 100.0 * (x / self.xmax)
13621363

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

13641389
class Locator(TickHelper):
13651390
"""

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