|
| 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. |
0 commit comments