8000 Merge branch 'main' into feature/asinh-scale · matplotlib/matplotlib@3e487a6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3e487a6

Browse files
committed
Merge branch 'main' into feature/asinh-scale
2 parents de30538 + 447160e commit 3e487a6

File tree

17 files changed

+374
-28
lines changed

17 files changed

+374
-28
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
``CallbackRegistry`` raises on unknown signals
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
When Matplotlib instantiates a `.CallbackRegistry`, it now limits callbacks
4+
to the signals that the registry knows about. In practice, this means that
5+
calling `~.FigureCanvasBase.mpl_connect` with an invalid signal name now raises
6+
a `ValueError`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Pending deprecation of layout methods
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
The methods `~.Figure.set_tight_layout`, `~.Figure.set_constrained_layout`,
4+
are discouraged, and now emit a ``PendingDeprecationWarning`` in favor of
5+
explicitly referencing the layout engine via
6+
``figure.set_layout_engine('tight')`` and
7+
``figure.set_layout_engine('constrained')``. End users should not see the
8+
warning, but library authors should adjust.
9+
10+
The methods `~.Figure.set_constrained_layout_pads` and
11+
`~.Figure.get_constrained_layout_pads` are will be deprecated in favor of
12+
``figure.get_layout_engine().set()`` and
13+
``figure.get_layout_engine().get()``, and currently emit a
14+
``PendingDeprecationWarning``.

doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ def _check_dependencies():
162162
'python': ('https://docs.python.org/3/', None),
163163
'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None),
164164
'tornado': ('https://www.tornadoweb.org/en/stable/', None),
165+
'xarray': ('https://xarray.pydata.org/en/stable/', None),
165166
}
166167

167168

doc/users/explain/api_interfaces.rst

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
.. _api_interfaces:
2+
3+
========================================
4+
Matplotlib Application Interfaces (APIs)
5+
========================================
6+
7+
Matplotlib has two major application interfaces, or styles of using the library:
8+
9+
- An explicit "Axes" interface that uses methods on a Figure or Axes object to
10+
create other Artists, and build a visualization step by step. This has also
11+
been called an "object-oriented" interface.
12+
- An implicit "pyplot" interface that keeps track of the last Figure and Axes
13+
created, and adds Artists to the object it thinks the user wants.
14+
15+
In addition, a number of downstream libraries (like `pandas` and xarray_) offer
16+
a ``plot`` method implemented directly on their data classes so that users can
17+
call ``data.plot()``.
18+
19+
.. _xarray: https://xarray.pydata.org
20+
21+
The difference between these interfaces can be a bit confusing, particularly
22+
given snippets on the web that use one or the other, or sometimes multiple
23+
interfaces in the same example. Here we attempt to point out how the "pyplot"
24+
and downstream interfaces relate to the explicit "Axes" interface to help users
25+
better navigate the library.
26+
27+
28+
Native Matplotlib interfaces
29+
----------------------------
30+
31+
The explicit "Axes" interface
32+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33+
34+
The "Axes" interface is how Matplotlib is implemented, and many customizations
35+
and fine-tuning end up being done at this level.
36+
37+
This interface works by instantiating an instance of a
38+
`~.matplotlib.figure.Figure` class (``fig`` below), using a method
39+
`~.Figure.subplots` method (or similar) on that object to create one or more
40+
`~.matplotlib.axes.Axes` objects (``ax`` below), and then calling drawing
41+
methods on the Axes (``plot`` in this example):
42+
43+
.. plot::
44+
:include-source:
45+
:align: center
46+
47+
import matplotlib.pyplot as plt
48+
49+
fig = plt.figure()
50+
ax = fig.subplots()
51+
ax.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])
52+
53+
We call this an "explicit" interface because each object is explicitly
54+
referenced, and used to make the next object. Keeping references to the objects
55+
is very flexible, and allows us to customize the objects after they are created,
56+
but before they are displayed.
57+
58+
59+
The implicit "pyplot" interface
60+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61+
62+
The `~.matplotlib.pyplot` module shadows most of the
63+
`~.matplotlib.axes.Axes` plotting methods to give the equivalent of
64+
the above, where the creation of the Figure and Axes is done for the user:
65+
66+
.. plot::
67+
:include-source:
68+
:align: center
69+
70+
import matplotlib.pyplot as plt
71+
72+
plt.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])
73+
74+
This can be convenient, particularly when doing interactive work or simple
75+
scripts. A reference to the current Figure can be retrieved using
76+
`~.pyplot.gcf` and to the current Axes by `~.pyplot.gca`. The `~.pyplot` module
77+
retains a list of Figures, and each Figure retains a list of Axes on the figure
78+
for the user so that the following:
79+
80+
.. plot::
81+
:include-source:
82+
:align: center
83+
84+
import matplotlib.pyplot as plt
85+
86+
plt.subplot(1, 2, 1)
87+
plt.plot([1, 2, 3], [0, 0.5, 0.2])
88+
89+
plt.subplot(1, 2, 2)
90+
plt.plot([3, 2, 1], [0, 0.5, 0.2])
91+
92+
is equivalent to:
93+
94+
.. plot::
95+
:include-source:
96+
:align: center
97+
98+
import matplotlib.pyplot as plt
99+
100+
plt.subplot(1, 2, 1)
101+
ax = plt.gca()
102+
ax.plot([1, 2, 3], [0, 0.5, 0.2])
103+
104+
plt.subplot(1, 2, 2)
105+
ax = plt.gca()
106+
ax.plot([3, 2, 1], [0, 0.5, 0.2])
107+
108+
In the explicit interface, this would be:
109+
110+
.. plot::
111+
:include-source:
112+
:align: center
113+
114+
import matplotlib.pyplot as plt
115+
116+
fig, axs = plt.subplots(1, 2)
117+
axs[0].plot([1, 2, 3], [0, 0.5, 0.2])
118+
axs[1].plot([3, 2, 1], [0, 0.5, 0.2])
119+
120+
Why be explicit?
121+
^^^^^^^^^^^^^^^^
122+
123+
What happens if you have to backtrack, and operate on an old axes that is not
124+
referenced by ``plt.gca()``? One simple way is to call ``subplot`` again with
125+
the same arguments. However, that quickly becomes inelegant. You can also
126+
inspect the Figure object and get its list of Axes objects, however, that can be
127+
misleading (colorbars are Axes too!). The best solution is probably to save a
128+
handle to every Axes you create, but if you do that, why not simply create the
129+
all the Axes objects at the start?
130+
131+
The first approach is to call ``plt.subplot`` again:
132+
133+
.. plot::
134+
:include-source:
135+
:align: center
136+
137+
import matplotlib.pyplot as plt
138+
139+
plt.subplot(1, 2, 1)
140+
plt.plot([1, 2, 3], [0, 0.5, 0.2])
141+
142+
plt.subplot(1, 2, 2)
143+
plt.plot([3, 2, 1], [0, 0.5, 0.2])
144+
145+
plt.suptitle('Implicit Interface: re-call subplot')
146+
147+
for i in range(1, 3):
148+
plt.subplot(1, 2, i)
149+
plt.xlabel('Boo')
150+
151+
The second is to save a handle:
152+
153+
.. plot::
154+
:include-source:
155+
:align: center
156+
157+
import matplotlib.pyplot as plt
158+
159+
axs = []
160+
ax = plt.subplot(1, 2, 1)
161+
axs += [ax]
162+
plt.plot([1, 2, 3], [0, 0.5, 0.2])
163+
164+
ax = plt.subplot(1, 2, 2)
165+
axs += [ax]
166+
plt.plot([3, 2, 1], [0, 0.5, 0.2])
167+
168+
plt.suptitle('Implicit Interface: save handles')
169+
170+
for i in range(2):
171+
plt.sca(axs[i])
172+
plt.xlabel('Boo')
173+
174+
However, the recommended way would be to be explicit from the outset:
175+
176+
.. plot::
177+
:include-source:
178+
:align: center
179+
180+
import matplotlib.pyplot as plt
181+
182+
fig, axs = plt.subplots(1, 2)
183+
axs[0].plot([1, 2, 3], [0, 0.5, 0.2])
184+
axs[1].plot([3, 2, 1], [0, 0.5, 0.2])
185+
fig.suptitle('Explicit Interface')
186+
for i in range(2):
187+
axs[i].set_xlabel('Boo')
188+
189+
190+
Third-party library "Data-object" interfaces
191+
--------------------------------------------
192+
193+
Some third party libraries have chosen to implement plotting for their data
194+
objects, e.g. ``data.plot()``, is seen in `pandas`, xarray_, and other
195+
third-party libraries. For illustrative purposes, a downstream library may
196+
implement a simple data container that has ``x`` and ``y`` data stored together,
197+
and then implements a ``plot`` method:
198+
199+
.. plot::
200+
:include-source:
201+
:align: center
202+
203+
import matplotlib.pyplot as plt
204+
205+
# supplied by downstream library:
206+
class DataContainer:
207+
208+
def __init__(self, x, y):
209+
"""
210+
Proper docstring here!
211+
"""
212+
self._x = x
213+
self._y = y
214+
215+
def plot(self, ax=None, **kwargs):
216+
if ax is None:
217+
ax = plt.gca()
218+
ax.plot(self._x, self._y, **kwargs)
219+
ax.set_title('Plotted from DataClass!')
220+
return ax
221+
222+
223+
# what the user usually calls:
224+
data = DataContainer([0, 1, 2, 3], [0, 0.2, 0.5, 0.3])
225+
data.plot()
226+
227+
So the library can hide all the nitty-gritty from the user, and can make a
228+
visualization appropriate to the data type, often with good labels, choices of
229+
colormaps, and other convenient features.
230+
231+
In the above, however, we may not have liked the title the library provided.
232+
Thankfully, they pass us back the Axes from the ``plot()`` method, and
233+
understanding the explicit Axes interface, we could call:
234+
``ax.set_title('My preferred title')`` to customize the title.
235+
236+
Many libraries also allow their ``plot`` methods to accept an optional *ax*
237+
argument. This allows us to place the visualization in an Axes that we have
238+
placed and perhaps customized.
239+
240+
Summary
241+
-------
242+
243+
Overall, it is useful to understand the explicit "Axes" interface since it is
244+
the most flexible and underlies the other interfaces. A user can usually
245+
figure out how to drop down to the explicit interface and operate on the
246+
underlying objects. While the explicit interface can be a bit more verbose
247+
to setup, complicated plots will often end up simpler than trying to use
248+
the implicit "pyplot" interface.
249+
250+
.. note::
251+
252+
It is sometimes confusing to people that we import ``pyplot`` for both
253+
interfaces. Currently, the ``pyplot`` module implements the "pyplot"
254+
interface, but it also provides top-level Figure and Axes creation
255+
methods, and ultimately spins up the graphical user interface, if one
256+
is being used. So ``pyplot`` is still needed regardless of the
257+
interface chosen.
258+
259+
Similarly, the declarative interfaces provided by partner libraries use the
260+
objects accessible by the "Axes" interface, and often accept these as arguments
261+
or pass them back from methods. It is usually essential to use the explicit
262+
"Axes" interface to perform any customization of the default visualization, or
263+
to unpack the data into NumPy arrays and pass directly to Matplotlib.
264+
265+
Appendix: "Axes" interface with data structures
266+
-----------------------------------------------
267+
268+
Most `~.axes.Axes` methods allow yet another API addressing by passing a
269+
*data* object to the method and specifying the arguments as strings:
270+
271+
.. plot::
272+
:include-source:
273+
:align: center
274+
275+
import matplotlib.pyplot as plt
276+
277+
data = {'xdat': [0, 1, 2, 3], 'ydat': [0, 0.2, 0.4, 0.1]}
278+
fig, ax = plt.subplots(figsize=(2, 2))
279+
ax.plot('xdat', 'ydat', data=data)
280+
281+
282+
Appendix: "pylab" interface
283+
---------------------------
284+
285+
There is one further interface that is highly discouraged, and that is to
286+
basically do ``from matplotlib.pyplot import *``. This allows users to simply
287+
call ``plot(x, y)``. While convenient, this can lead to obvious problems if the
288+
user unwittingly names a variable the same name as a pyplot method.

doc/users/explain/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Explanations
88
.. toctree::
99
:maxdepth: 2
1010

11+
api_interfaces.rst
1112
backends.rst
1213
interactive.rst
1314
fonts.rst

lib/matplotlib/artist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def __init__(self):
167167
# Normally, artist classes need to be queried for mouseover info if and
168168
# only if they override get_cursor_data.
169169
self._mouseover = type(self).get_cursor_data != Artist.get_cursor_data
170-
self._callbacks = cbook.CallbackRegistry()
170+
self._callbacks = cbook.CallbackRegistry(signals=["pchanged"])
171171
try:
172172
self.axes = None
173173
except AttributeError:

lib/matplotlib/axes/_base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1201,7 +1201,8 @@ def cla(self):
12011201
spine.clear()
12021202

12031203
self.ignore_existing_data_limits = True
1204-
self.callbacks = cbook.CallbackRegistry()
1204+
self.callbacks = cbook.CallbackRegistry(
1205+
signals=["xlim_changed", "ylim_changed", "zlim_changed"])
12051206

12061207
if self._sharex is not None:
12071208
self.sharex(self._sharex)

lib/matplotlib/axis.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,8 @@ def __init__(self, axes, pickradius=15):
655655
self.axes = axes
656656
self.major = Ticker()
657657
self.minor = Ticker()
658-
self.callbacks = cbook.CallbackRegistry()
658+
self.callbacks = cbook.CallbackRegistry(
659+
signals=["units", "units finalize"])
659660

660661
self._autolabelpos = True
661662

@@ -806,7 +807,8 @@ def clear(self):
806807
self._set_scale('linear')
807808

808809
# Clear the callback registry for this axis, or it may "leak"
809-
self.callbacks = cbook.CallbackRegistry()
810+
self.callbacks = cbook.CallbackRegistry(
811+
signals=["units", "units finalize"])
810812

811813
# whether the grids are on
812814
self._major_tick_kw['gridOn'] = (

0 commit comments

Comments
 (0)
0