Figure and Axes

The two objects everything else hangs off, and the two APIs that confuse every example you will read.

Overview

Two objects

matplotlib is Python's most widely used plotting library. It produces static, animated and interactive figures. It runs in scripts, notebooks, web applications and GUI programs.

The library has two layers that share one name, and that is the source of most confusion around it. Understanding the two objects and the two APIs on this page removes most of the confusion you will meet in other people's code.

matplotlib gives you full control over every part of a figure. Ticks, spines, colours, fonts, layout, legends, annotations and export format are all adjustable. That control is the library's strength and the reason it has lasted. It is also what makes the learning curve steep. There are many methods, and they live on different objects.

The approach this track takes is to use the object-oriented API from the start. It is more explicit, works the same way with one subplot or twenty, and is what the library's own documentation recommends. The other API, the stateful plt.* style, is covered here so you can read other people's code, but this track does not use it.

Every figure you build starts with two objects: a Figure and an Axes. The Figure is the container. The Axes is the plotting area. Nearly everything you draw, label or configure belongs to the Axes.

A Figure is the whole image: the canvas, its size, its background. The file it's eventually saved to.

An Axes is one plotting area inside it. The box with the ticks, the data, and the labels.

The name is unfortunate. "Axes" is singular here. It doesn't mean "the x and y axes". Those are ax.xaxis and ax.yaxis. This Are objects on the Axes. One figure can hold many Axes. This Is what a grid of subplots is.

Nearly all you want to do belongs to the Axes. Plotting, labelling, limits, ticks, legends and annotation are all ax. methods. The Figure handles size, the total title, layout between subplots, and saving.

fig, ax = plt.subplots() creates one of each and returns both. It's the standard first line of almost each matplotlib program. It's worth using even for a single plot. It puts you in the API that keeps working when the plot grows.

Worth knowing

A Figure is the canvas; an Axes is one plotting area on it. Almost every method you want belongs to the Axes.
fig, ax = plt.subplots() is the standard opening line — it makes both and hands them to you.
The stateful plt.* API draws into a hidden current figure; the object-oriented one names the axes explicitly.
plt.title() becomes ax.set_title(). Drawing methods like plot keep their names.
Draw calls return the artists they created — line, = ax.plot(...) unpacks the one-element list.
matplotlib keeps a reference to every figure, so they must be closed; a drawing loop without plt.close leaks memory and warns at 20.

Figure and Axes

The two objects all hangs off, and the two APIs that confuse each example.

A figure holds axes; axes hold the plot

Two objects, and almost every method you will use belongs to the second.

example_01.pymatplotlib
Output

The same plot, two ways to write it

The stateful API draws into whatever figure is current; the object-oriented one asks a specific axes.

example_02.pymatplotlib
Output

Why the stateful API causes trouble

It has a hidden current figure, and that is a global.

example_03.pymatplotlib
Output

set_ methods, and the plt equivalents

The object-oriented names are the plt names with set_ in front, mostly.

example_04.pymatplotlib
Output

Every draw call returns something

Usually a list of the artists it created, which you keep when you want to change them later.

example_05.pymatplotlib
Output

Figures have to be closed

Each one holds memory until it is, and in a loop that adds up.

example_06.pymatplotlib
Output

Two APIs

each matplotlib example you find online is written in one of two styles. Mixing them is why so many of them are hard to follow.

Stateful (pyplot): plt.plot(...), plt.title(...), plt.xlabel(...). These act on "the current figure and current axes", which matplotlib tracks internally. It is compact and reads well for a single quick plot.

Object-oriented: ax.plot(...), ax.set_title(...). You hold the objects and say which one you mean.

The stateful API is a convenience layer over the object-oriented one. plt.title finds the current axes and calls set_title on it.

The translation is rote: most plt.foo() calls become ax.set_foo(). The exceptions are the drawing methods. plot, scatter, bar. Which keep their names because they're actions rather than properties.

Why the object-oriented form

The current figure is a global var, with the problems globals always have.

In a script that draws one plot, nothing goes wrong. In a script that draws a few, plt.plot after creating a second figure lands on the second, whether or not that was the intent. In a notebook. Here Cells run again and again and out of order, "the current figure" is what ran last. Which is why plots at times appear on the wrong chart or an earlier figure gains a line no one added.

The object-oriented form has no such ambiguity: ax1.plot draws on ax1.

It's also the only form that works comfortably with subplots. Here There are a few axes and no sensible notion of a current one.

This track uses fig, ax = plt.subplots() throughout. Mentions the plt equal where you're likely to meet it in other people's code.

Artists

all drawn is an Artist: lines, text, patches, the axes themselves.

Draw calls return the artists they create. ax.plot returns a list of Line2D objects. A list. One call can draw a few lines. Which is why the idiom is:

line, = ax.plot(x, y)

The trailing comma unpacks the single element. Without it, line is a list and line.set_color fails.

Keeping the artist matters when you want to change it later: line.set_color("crimson"), line.set_label("..."). For a static plot you can skip the return value fully. This Is what most code does.

Closing figures

matplotlib keeps an inner link to each figure created through pyplot. They're never garbage collected while that link exists.

For one plot that doesn't matter. In a loop. Making a chart per group, per file, per day. The figures build up. Matplotlib warns after twenty that "more than 20 figures have been opened".

plt.close(fig) closes one. plt.close("all") closes all. Any loop that draws should close.

On these pages the runner collects open figures after each run and closes them. This Is why an editor that draws shows its plot. That is also why a plt.close() at the end of an editor would leave you with no image. The figure has to still be open when the program finishes.

Where matplotlib sits

matplotlib is the oldest and most widely used plotting library in Python. Nearly all else is built on it or interoperates with it. pandas' .plot, seaborn. The plotting in scikit-learn and statsmodels all give matplotlib objects you can adjust later.

That is the hands-on arg for learning it even if you mostly use a thing higher-level. When the convenience wrapper doesn't do quite what you need, the escape hatch is matplotlib, and it's always on hand.

Its age also shows its awkward parts. The pyplot interface was designed to feel like MATLAB, the object-oriented API came later. Both are supported forever. Most confusing examples online are simply mixing the two.

The parts of a figure

Worth naming, because the docs uses these terms constantly:

Figure — the whole canvas.

Axes — one plotting area. A figure holds one or many.

Axis — the x or y scale on an Axes, with its ticks and label. ax.xaxis.

Artist — anything drawn: lines, text, patches, the axes themselves.

Spines — the four lines bounding the plotting area.

Ticks — the marks on an axis, with major and minor variants.

The tree is Figure → Axes → Axis → ticks and labels. Almost each method lives on the Axes.

Showing a figure

In a notebook, figures display on its own when a cell finishes. %matplotlib inline is the default in Jupyter and rarely needs stating.

In a script, nothing is displayed unless you ask. plt.show() opens a window and blocks. fig.savefig(path) writes a file. A script that draws and does neither makes nothing. This Is a common first surprise.

On these pages, the runner collects any figure still open when your program finishes, renders it to a PNG. Shows it under the printed output. That is why the editors here never call show or savefig. And why calling plt.close() at the end of an editor would leave you with no picture.

Backends

A backend is what matplotlib draws with. Interactive ones open windows. Non-interactive ones write files.

AGG is the non-interactive raster backend. It's what these pages use. A Web Worker has no window to draw into. matplotlib.use("AGG") selects it, and that call must come before pyplot is imported. Which is why it's in the page setup rather than in the examples.

You'll meet this in two places: on a server with no display. Here AGG is needed. In a script that hangs on plt.show() because it picked an interactive backend it can't really use.

What this track covers

The first modules are the drawing types: lines, scatters, bars, histograms, boxes, images.

Then the parts that make a chart readable: labels, legends, limits, ticks, scales, colour, annotation.

Then layout: subplots, spacing, twin axes, saving.

Then judgement: choosing a chart, common mistakes, and speed when the data is large.

each module is six short programs. Each one draws. Changing a number and re-running is the fastest way to find out what an arg really does. Which is worth more here than in most libraries. Matplotlib's arg names aren't always guessable.

Questions people ask about Figure and Axes

Why are there two ways to do everything? History. pyplot was written to feel like MATLAB: one hidden figure, commands act on it. The object-oriented API came later and is what pyplot calls underneath. Both are supported permanently, and examples mix them, which is the main reason matplotlib feels harder than it is.

Do I need plt.show()? In a script, yes, unless you save to a file. In a notebook, no. On these pages, no — the runner collects what is open.

Why does my figure look different on someone else's machine? A different style, a different matplotlibrc, a missing font, or a different backend. The first two are the usual causes and both are silent.

Should I use seaborn instead? For statistical charts, often yes. Seaborn makes matplotlib objects, so nothing is lost, and knowing matplotlib is what lets you adjust what seaborn gives you.

Why is my chart slow? Almost always too many artists or too many points to be visible. The performance module covers both cases.

How to read the documentation

matplotlib's docs is large and organised around the object model. This Makes it hard to search until you know the vocab.

Three habits help.

Search for the Axes method, not the pyplot function. Axes.set_xlabel rather than plt.xlabel. Because that page lists the full signature and each keyword.

Read the Artist page for the thing you're styling. Most keyword args to a drawing call are properties of the artist it creates. Line2D docs all plot accepts beyond its own args.

Use the examples gallery as a search index. Finding a picture that resembles what you want and reading its source is often faster than working out what the step is called.

The API is wide but shallow: a few hundred methods, most of which take the same handful of styling args. Once the vocab is familiar the docs turns navigable.

Figure and Axes, in summary

Two objects: a Figure holding one or more Axes. Almost each method you want belongs to the Axes.

Two APIs: the stateful plt.* one that draws into a hidden current figure. The object-oriented one that names the axes. The second is what this track uses. The first goes wrong the moment there's more than one figure. Which in a notebook is at once.

fig, ax = plt.subplots() is the standard opening line. The translation from any pyplot example is rote: plt.title turns ax.set_title. The drawing methods keep their names.

Draw calls return the artists they create. This You keep when a thing needs changing later and skip else.

And figures must be closed. Matplotlib holds a link to each one until you do.

all after this module is either a kind of drawing or a way of making the result readable. All of it hangs off those two objects.

A closing note on Figure and Axes

matplotlib has a reputation for being hard. Most of that comes from two things this module handles.

The first is the two APIs. Examples that mix them without saying so. Once you know that plt.title and ax.set_title are the same step reached differently, most confusing code turns readable.

The second is that it's a drawing library rather than a charting one. It has no view about what a good chart is. It won't stop you. All is possible at the cost of nothing being auto.

That is the right trade for a foundation library. It's why each higher-level tool in Python either sits on matplotlib or has to reimplement it. Learning it well means the ceiling is never the library.

Reading the code back

each chart in this track starts the same way and ends the same way. Create a figure and axes, draw with ax methods, label, adjust. Let the runner show it. What changes in the middle is the drawing call and the handful of args it takes. Recognising that shape makes the rest of the library a matter of looking up which method and which arg, rather than learning a new pattern each time.

Check yourself

0 of 4

Answer without scrolling back up.

  1. What does 'Axes' refer to in matplotlib?

  2. Why prefer `ax.plot()` over `plt.plot()`?

  3. Why is there a comma in `line, = ax.plot(x, y)`?

  4. Why must figures be closed?

Cheat sheet

Figure and Axes

Learn the difference between Figure and Axes in matplotlib. Understand the two APIs, pyplot and object-oriented, and when to use each one.

MATPLOTLIB · vizlearn.in/matplotlib/what_is_matplotlib.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.