File: api_interfaces.rst

package info (click to toggle)
matplotlib 3.6.3-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 76,280 kB
  • sloc: python: 133,763; cpp: 66,599; objc: 1,699; ansic: 1,367; javascript: 765; makefile: 153; sh: 48
file content (290 lines) | stat: -rw-r--r-- 9,207 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
.. redirect-from:: /gallery/misc/pythonic_matplotlib

.. _api_interfaces:

========================================
Matplotlib Application Interfaces (APIs)
========================================

Matplotlib has two major application interfaces, or styles of using the library:

- An explicit "Axes" interface that uses methods on a Figure or Axes object to
  create other Artists, and build a visualization step by step.  This has also
  been called an "object-oriented" interface.
- An implicit "pyplot" interface that keeps track of the last Figure and Axes
  created, and adds Artists to the object it thinks the user wants.

In addition, a number of downstream libraries (like `pandas` and xarray_) offer
a ``plot`` method implemented directly on their data classes so that users can
call ``data.plot()``.

.. _xarray: https://xarray.pydata.org

The difference between these interfaces can be a bit confusing, particularly
given snippets on the web that use one or the other, or sometimes multiple
interfaces in the same example.  Here we attempt to point out how the "pyplot"
and downstream interfaces relate to the explicit "Axes" interface to help users
better navigate the library.


Native Matplotlib interfaces
----------------------------

The explicit "Axes" interface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The "Axes" interface is how Matplotlib is implemented, and many customizations
and fine-tuning end up being done at this level.

This interface works by instantiating an instance of a
`~.matplotlib.figure.Figure` class (``fig`` below), using a method
`~.Figure.subplots` method (or similar) on that object to create one or more
`~.matplotlib.axes.Axes` objects (``ax`` below), and then calling drawing
methods on the Axes (``plot`` in this example):

.. plot::
   :include-source:
   :align: center

    import matplotlib.pyplot as plt

    fig = plt.figure()
    ax = fig.subplots()
    ax.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])

We call this an "explicit" interface because each object is explicitly
referenced, and used to make the next object.  Keeping references to the objects
is very flexible, and allows us to customize the objects after they are created,
but before they are displayed.


The implicit "pyplot" interface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The `~.matplotlib.pyplot` module shadows most of the
`~.matplotlib.axes.Axes` plotting methods to give the equivalent of
the above, where the creation of the Figure and Axes is done for the user:

.. plot::
   :include-source:
   :align: center

    import matplotlib.pyplot as plt

    plt.plot([1, 2, 3, 4], [0, 0.5, 1, 0.2])

This can be convenient, particularly when doing interactive work or simple
scripts.  A reference to the current Figure can be retrieved using
`~.pyplot.gcf` and to the current Axes by `~.pyplot.gca`.  The `~.pyplot` module
retains a list of Figures, and each Figure retains a list of Axes on the figure
for the user so that the following:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    plt.subplot(1, 2, 1)
    plt.plot([1, 2, 3], [0, 0.5, 0.2])

    plt.subplot(1, 2, 2)
    plt.plot([3, 2, 1], [0, 0.5, 0.2])

is equivalent to:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    plt.subplot(1, 2, 1)
    ax = plt.gca()
    ax.plot([1, 2, 3], [0, 0.5, 0.2])

    plt.subplot(1, 2, 2)
    ax = plt.gca()
    ax.plot([3, 2, 1], [0, 0.5, 0.2])

In the explicit interface, this would be:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    fig, axs = plt.subplots(1, 2)
    axs[0].plot([1, 2, 3], [0, 0.5, 0.2])
    axs[1].plot([3, 2, 1], [0, 0.5, 0.2])

Why be explicit?
^^^^^^^^^^^^^^^^

What happens if you have to backtrack, and operate on an old axes that is not
referenced by ``plt.gca()``?  One simple way is to call ``subplot`` again with
the same arguments.  However, that quickly becomes inelegant.  You can also
inspect the Figure object and get its list of Axes objects, however, that can be
misleading (colorbars are Axes too!). The best solution is probably to save a
handle to every Axes you create, but if you do that, why not simply create the
all the Axes objects at the start?

The first approach is to call ``plt.subplot`` again:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    plt.subplot(1, 2, 1)
    plt.plot([1, 2, 3], [0, 0.5, 0.2])

    plt.subplot(1, 2, 2)
    plt.plot([3, 2, 1], [0, 0.5, 0.2])

    plt.suptitle('Implicit Interface: re-call subplot')

    for i in range(1, 3):
        plt.subplot(1, 2, i)
        plt.xlabel('Boo')

The second is to save a handle:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    axs = []
    ax = plt.subplot(1, 2, 1)
    axs += [ax]
    plt.plot([1, 2, 3], [0, 0.5, 0.2])

    ax = plt.subplot(1, 2, 2)
    axs += [ax]
    plt.plot([3, 2, 1], [0, 0.5, 0.2])

    plt.suptitle('Implicit Interface: save handles')

    for i in range(2):
        plt.sca(axs[i])
        plt.xlabel('Boo')

However, the recommended way would be to be explicit from the outset:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    fig, axs = plt.subplots(1, 2)
    axs[0].plot([1, 2, 3], [0, 0.5, 0.2])
    axs[1].plot([3, 2, 1], [0, 0.5, 0.2])
    fig.suptitle('Explicit Interface')
    for i in range(2):
        axs[i].set_xlabel('Boo')


Third-party library "Data-object" interfaces
--------------------------------------------

Some third party libraries have chosen to implement plotting for their data
objects, e.g. ``data.plot()``, is seen in `pandas`, xarray_, and other
third-party libraries.  For illustrative purposes, a downstream library may
implement a simple data container that has ``x`` and ``y`` data stored together,
and then implements a ``plot`` method:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    # supplied by downstream library:
    class DataContainer:

        def __init__(self, x, y):
            """
            Proper docstring here!
            """
            self._x = x
            self._y = y

        def plot(self, ax=None, **kwargs):
            if ax is None:
                ax = plt.gca()
            ax.plot(self._x, self._y, **kwargs)
            ax.set_title('Plotted from DataClass!')
            return ax


    # what the user usually calls:
    data = DataContainer([0, 1, 2, 3], [0, 0.2, 0.5, 0.3])
    data.plot()

So the library can hide all the nitty-gritty from the user, and can make a
visualization appropriate to the data type, often with good labels, choices of
colormaps, and other convenient features.

In the above, however, we may not have liked the title the library provided.
Thankfully, they pass us back the Axes from the ``plot()`` method, and
understanding the explicit Axes interface, we could call:
``ax.set_title('My preferred title')`` to customize the title.

Many libraries also allow their ``plot`` methods to accept an optional *ax*
argument. This allows us to place the visualization in an Axes that we have
placed and perhaps customized.

Summary
-------

Overall, it is useful to understand the explicit "Axes" interface since it is
the most flexible and underlies the other interfaces.  A user can usually
figure out how to drop down to the explicit interface and operate on the
underlying objects.  While the explicit interface can be a bit more verbose
to setup, complicated plots will often end up simpler than trying to use
the implicit "pyplot" interface.

.. note::

    It is sometimes confusing to people that we import ``pyplot`` for both
    interfaces.  Currently, the ``pyplot`` module implements the "pyplot"
    interface, but it also provides top-level Figure and Axes creation
    methods, and ultimately spins up the graphical user interface, if one
    is being used.  So ``pyplot`` is still needed regardless of the
    interface chosen.

Similarly, the declarative interfaces provided by partner libraries use the
objects accessible by the "Axes" interface, and often accept these as arguments
or pass them back from methods.  It is usually essential to use the explicit
"Axes" interface to perform any customization of the default visualization, or
to unpack the data into NumPy arrays and pass directly to Matplotlib.

Appendix: "Axes" interface with data structures
-----------------------------------------------

Most `~.axes.Axes` methods allow yet another API addressing by passing a
*data* object to the method and specifying the arguments as strings:

.. plot::
    :include-source:
    :align: center

    import matplotlib.pyplot as plt

    data = {'xdat': [0, 1, 2, 3], 'ydat': [0, 0.2, 0.4, 0.1]}
    fig, ax = plt.subplots(figsize=(2, 2))
    ax.plot('xdat', 'ydat', data=data)


Appendix: "pylab" interface
---------------------------

There is one further interface that is highly discouraged, and that is to
basically do ``from matplotlib.pyplot import *``.  This allows users to simply
call ``plot(x, y)``.  While convenient, this can lead to obvious problems if the
user unwittingly names a variable the same name as a pyplot method.