Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 91261e7

Browse files
authored
Merge pull request #7965 from madphysicist/pct-formatter-updates
ENH: Fixed PercentFormatter usage with latex
2 parents c4b8fe6 + 6add6ce commit 91261e7

File tree

4 files changed

+102
-61
lines changed

4 files changed

+102
-61
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+
ax.hist(x, bins=50, normed=True)
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: 52 additions & 34 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,
572-
x, display_range, expected):
605+
def test_basic(self, xmax, decimals, symbol,
606+
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)