Skip to content

Commit 7ea51e7

Browse files
committed
ENH: Added TransformFormatter to matplotlib.ticker
Tests included. Example code provided with some recipes from previous attempt.
1 parent 70831bd commit 7ea51e7

File tree

6 files changed

+584
-49
lines changed

6 files changed

+584
-49
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Two new Formatters added to `matplotlib.ticker`
2+
-----------------------------------------------
3+
4+
Two new formatters have been added for displaying some specialized
5+
tick labels:
6+
7+
- :class:`matplotlib.ticker.PercentFormatter`
8+
- :class:`matplotlib.ticker.TransformFormatter`
9+
10+
11+
:class:`matplotlib.ticker.PercentFormatter`
12+
```````````````````````````````````````````
13+
14+
This new formatter has some nice features like being able to convert
15+
from arbitrary data scales to percents, a customizable percent symbol
16+
and either automatic or manual control over the decimal points.
17+
18+
19+
:class:`matplotlib.ticker.TransformFormatter`
20+
```````````````````````````````````````````````
21+
22+
A more generic version of :class:`matplotlib.ticker.FuncFormatter` that
23+
allows the tick values to be transformed before being passed to an
24+
underlying formatter. The transformation can yield results of arbitrary
25+
type, so for example, using `int` as the transformation will allow
26+
:class:`matplotlib.ticker.StrMethodFormatter` to use integer format
27+
strings. If the underlying formatter is an instance of
28+
:class:`matplotlib.ticker.Formatter`, it will be configured correctly
29+
through this class.

doc/users/whats_new/percent_formatter.rst

Lines changed: 0 additions & 6 deletions
This file was deleted.

examples/ticks_and_spines/tick-formatters.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,23 @@ def setup(ax):
1919
ax.set_xlim(0, 5)
2020
ax.set_ylim(0, 1)
2121
ax.patch.set_alpha(0.0)
22+
ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00))
23+
ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25))
2224

2325

2426
plt.figure(figsize=(8, 5))
25-
n = 6
27+
n = 7
2628

2729
# Null formatter
2830
ax = plt.subplot(n, 1, 1)
2931
setup(ax)
30-
ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00))
31-
ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25))
3232
ax.xaxis.set_major_formatter(ticker.NullFormatter())
3333
ax.xaxis.set_minor_formatter(ticker.NullFormatter())
3434
ax.text(0.0, 0.1, "NullFormatter()", fontsize=16, transform=ax.transAxes)
3535

3636
# Fixed formatter
3737
ax = plt.subplot(n, 1, 2)
3838
setup(ax)
39-
ax.xaxis.set_major_locator(ticker.MultipleLocator(1.0))
40-
ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25))
4139
majors = ["", "0", "1", "2", "3", "4", "5"]
4240
ax.xaxis.set_major_formatter(ticker.FixedFormatter(majors))
4341
minors = [""] + ["%.2f" % (x-int(x)) if (x-int(x))
@@ -54,8 +52,6 @@ def major_formatter(x, pos):
5452

5553
ax = plt.subplot(n, 1, 3)
5654
setup(ax)
57-
ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00))
58-
ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25))
5955
ax.xaxis.set_major_formatter(ticker.FuncFormatter(major_formatter))
6056
ax.text(0.0, 0.1, 'FuncFormatter(lambda x, pos: "[%.2f]" % x)',
6157
fontsize=15, transform=ax.transAxes)
@@ -64,29 +60,31 @@ def major_formatter(x, pos):
6460
# FormatStr formatter
6561
ax = plt.subplot(n, 1, 4)
6662
setup(ax)
67-
ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00))
68-
ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25))
6963
ax.xaxis.set_major_formatter(ticker.FormatStrFormatter(">%d<"))
7064
ax.text(0.0, 0.1, "FormatStrFormatter('>%d<')",
7165
fontsize=15, transform=ax.transAxes)
7266

7367
# Scalar formatter
7468
ax = plt.subplot(n, 1, 5)
7569
setup(ax)
76-
ax.xaxis.set_major_locator(ticker.AutoLocator())
77-
ax.xaxis.set_minor_locator(ticker.AutoMinorLocator())
7870
ax.xaxis.set_major_formatter(ticker.ScalarFormatter(useMathText=True))
7971
ax.text(0.0, 0.1, "ScalarFormatter()", fontsize=15, transform=ax.transAxes)
8072

8173
# StrMethod formatter
8274
ax = plt.subplot(n, 1, 6)
8375
setup(ax)
84-
ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00))
85-
ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25))
8676
ax.xaxis.set_major_formatter(ticker.StrMethodFormatter("{x}"))
8777
ax.text(0.0, 0.1, "StrMethodFormatter('{x}')",
8878
fontsize=15, transform=ax.transAxes)
8979

80+
# TransformFormatter
81+
ax = plt.subplot(n, 1, 7)
82+
setup(ax)
83+
ax.xaxis.set_major_formatter(ticker.TransformFormatter(lambda x: 7 - 2 * x))
84+
ax.text(0.0, 0.1, "TransformFormatter(lambda x: 7 - 2 * x)",
85+
fontsize=15, transform=ax.transAxes)
86+
87+
9088
# Push the top of the top axes outside the figure because we only show the
9189
# bottom spine.
9290
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=1.05)
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
Demo of the `matplotlib.ticker.TransformFormatter` class.
3+
4+
This code demonstrates two features:
5+
6+
1. A linear transformation of the input values. A callable class for
7+
doing the transformation is presented as a recipe here. The data
8+
type of the inputs does not change.
9+
2. A transformation of the input type. The example here allows
10+
`matplotlib.ticker.StrMethodFormatter` to handle integer formats
11+
('b', 'o', 'd', 'n', 'x', 'X'), which will normally raise an error
12+
if used directly. This transformation is associated with a
13+
`matplotlib.ticker.MaxNLocator` which has `integer` set to True to
14+
ensure that the inputs are indeed integers.
15+
16+
The same histogram is plotted in two sub-plots with a shared x-axis.
17+
Each axis shows a different temperature scale: one in degrees Celsius,
18+
one in degrees Rankine (the Fahrenheit analogue of Kelvins). This is one
19+
of the few examples of recognized scientific units that have both a
20+
scale and an offset relative to each other.
21+
"""
22+
23+
import numpy as np
24+
from matplotlib import pyplot as plt
25+
from matplotlib.axis import Ticker
26+
from matplotlib.ticker import (
27+
TransformFormatter, StrMethodFormatter, MaxNLocator
28+
)
29+
30+
31+
class LinearTransform:
32+
"""
33+
A callable class that transforms input values to output according to
34+
a linear transformation.
35+
"""
36+
37+
def __init__(self, in_start=None, in_end=None, out_start=None, out_end=None):
38+
"""
39+
Sets up the transformation such that `in_start` gets mapped to
40+
`out_start` and `in_end` gets mapped to `out_end`. The following
41+
shortcuts apply when only some of the inputs are specified:
42+
43+
- none: no-op
44+
- in_start: translation to zero
45+
- out_start: translation from zero
46+
- in_end: scaling to one (divide input by in_end)
47+
- out_end: scaling from one (multiply input by in_end)
48+
- in_start, out_start: translation
49+
- in_end, out_end: scaling (in_start and out_start zero)
50+
- in_start, out_end: in_end=out_end, out_start=0
51+
- in_end, out_start: in_start=0, out_end=in_end
52+
53+
Based on the following rules:
54+
55+
- start missing: set start to zero
56+
- both ends are missing: set ranges to 1.0
57+
- one end is missing: set it to the other end
58+
"""
59+
self._in_offset = 0.0 if in_start is None else in_start
60+
self._out_offset = 0.0 if out_start is None else out_start
61+
62+
if in_end is None:
63+
if out_end is None:
64+
self._in_scale = 1.0
65+
else:
66+
self._in_scale = out_end - self._in_offset
67+
else:
68+
self._in_scale = in_end - self._in_offset
69+
70+
if out_end is None:
71+
if in_end is None:
72+
self._out_scale = 1.0
73+
else:
74+
self._out_scale = in_end - self._out_offset
75+
else:
76+
self._out_scale = out_end - self._out_offset
77+
78+
def __call__(self, x):
79+
"""
80+
Transforms the input value `x` according to the rule set up in
81+
`__init__`.
82+
"""
83+
return ((x - self._in_offset) * self._out_scale / self._in_scale +
84+
self._out_offset)
85+
86+
# X-data
87+
temp_C = np.arange(-5.0, 5.1, 0.25)
88+
# Y-data
89+
counts = 15.0 * np.exp(-temp_C**2 / 25)
90+
# Add some noise
91+
counts += np.random.normal(scale=4.0, size=counts.shape)
92+
if counts.min() < 0:
93+
counts += counts.min()
94+
95+
fig = plt.figure()
96+
ax1 = fig.add_subplot(111)
97+
ax2 = fig.add_subplot(111, sharex=ax1, sharey=ax1, frameon=False)
98+
99+
ax1.plot(temp_C, counts, drawstyle='steps-mid')
100+
101+
ax1.xaxis.set_major_formatter(StrMethodFormatter('{x:0.2f}'))
102+
103+
# This step is necessary to allow the shared x-axes to have different
104+
# Formatter and Locator objects.
105+
ax2.xaxis.major = Ticker()
106+
# 0C -> 491.67R (definition), -273.15C (0K)->0R (-491.67F)(definition)
107+
ax2.xaxis.set_major_locator(ax1.xaxis.get_major_locator())
108+
ax2.xaxis.set_major_formatter(
109+
TransformFormatter(LinearTransform(in_start=-273.15, in_end=0,
110+
out_end=491.67),
111+
StrMethodFormatter('{x:0.2f}')))
112+
113+
# The y-axes share their locators and formatters, so only one needs to
114+
# be set
115+
ax1.yaxis.set_major_locator(MaxNLocator(integer=True))
116+
# Setting the transfrom to `int` will only alter the type, not the
117+
# actual value of the ticks
118+
ax1.yaxis.set_major_formatter(
119+
TransformFormatter(int, StrMethodFormatter('{x:02X}')))
120+
121+
ax1.set_xlabel('Temperature (\u00B0C)')
122+
ax1.set_ylabel('Samples (Hex)')
123+
ax2.set_xlabel('Temperature (\u00B0R)')
124+
125+
ax1.xaxis.tick_top()
126+
ax1.xaxis.set_label_position('top')
127+
128+
plt.show()

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