I’ve been experimenting with matplotlib recently, both interactively in an ipython shell as well as non-interactively as a chart image generator to be served through the web.
This post shares some tips that took some searching on how matplotlib
operates in different interaction contexts.
General Concept: Backends
matplotlib
has the concept of backends — essentially
the target canvas or surface it can render a figure to.
A backend implementation takes matplotlib
’s internal representations of
high-level drawing objects such as lines and axes and converts these to a
form suitable for that backend.
For example, an image backend — effectively what we use when we save a figure to a file or stream — will know about different image formats, and how to convert drawing objects into pixels in that format.
The key point here is that interactive and non-interactive modes require a backend implementation suitable specifically to each of them.
The following ipython
sample session illustrates common backend API calls
which we will need in later sections:
In [1]: import matplotlib as mpl
In [2]: mpl.get_backend()
Out[2]: 'Qt5Agg'
In [3]:
In [3]: %matplotlib --list
Available matplotlib backends: ['tk', 'gtk', 'gtk3', 'wx', 'qt4', 'qt5', 'qt',
'osx', 'nbagg', 'notebook', 'agg', 'svg', 'pdf', 'ps', 'inline', 'ipympl',
'widget']
In [7]: mpl.use('pdf')
In [8]: mpl.get_backend()
Out[8]: 'pdf'
Note that simply doing mpl.use('qt5')
fails. The suffix agg
needs to
be added in some cases — but obviously not all, as per the pdf
example
above (go figure) — like so:
mpl.use('qt5agg')
It is not case-sensitive, so Qt5Agg
and qt5agg
work just the same. The
exception that shows actually lists the proper names:
ValueError: Unrecognized backend string 'gtk': valid strings are ['GTK3Agg',
'GTK3Cairo', 'MacOSX', 'nbAgg', 'Qt4Agg', 'Qt4Cairo', 'Qt5Agg', 'Qt5Cairo',
'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', 'WXCairo', 'agg', 'cairo',
'pdf', 'pgf', 'ps', 'svg', 'template']
Furthermore, even though ipython
lists those various backends, it may
still fail on calling use(...)
, and require you to install additional
components.
Basically, ipython
is aware of a bunch of possible backends, and
attempts to dynamically load the one requested by matplotlib.use(...)
.
Switching backends can happen prior to any plotting command, e.g.
matplotlib.pyplot.plot(..)
.
In [61]: mpl.use('agg')
In [62]: plt.bar(x, y)
Out[62]: <BarContainer object of 25 artists>
In [63]: plt.show()
/home/xxx/apps/anaconda3/lib/python3.6/site-packages/matplotlib/figure.py:445
: UserWarning: Matplotlib is currently using agg, which is a non-GUI
backend, so cannot show the figure.
% get_backend())
In [64]: mpl.use('qt5agg')
In [66]: plt.bar(x, y)
Out[66]: <BarContainer object of 25 artists>
In [67]: plt.show()
Interactive Mode
In an interactive shell such as ipython
, we want an interactive backend
such as Qt5Agg
or TkAgg
.
TkAgg
in particular, was what worked for me on Mac with an
Anaconda distribution without installing anything further.
These are image renderer and viewer implementations with controls for interactive use.
When exploring in interactive mode, I was confused why
matplotlib.pyplot.show()
was only taking effect once: calling show()
then closing the chart window, and calling show()
again does nothing.
Essentially, one needs to reissue a plotting command such as
pyplot.bar(...)
or pyplot.plot(...)
to force a draw, and then call
show()
again.
Of course, simply starting afresh with a new figure also works, since a new figure is a fresh draw.
plt.figure()
plt.show()
plt.figure()
plt.show()
Non-Interactive Mode
A common use case for non-interactive mode is to produce and serve graphics over the web. The server code will run completely headless and not require any GUI toolkits to be installed where it is deployed.
We will often get user inputs, plot the figure in-memory, then stream it through, encoded as an image to be rendered on the browser, or saved to disk, etc.
The correct backend to use for this is agg
.
Essentially, as part of bootstrapping our application, webapp or otherwise, we need to run the following prior to any plotting API calls being made:
import matplotlib
matplotlib.use('Agg') # case-insensitive
In most frameworks, such as Flask, this can be done in the
web module’s __init__.py
where we create the application object.
Of course, this configuration appropriately stops show()
from working,
issuing a UserWarning
:
In [61]: mpl.use('agg')
In [62]: plt.bar(x, y)
Out[62]: <BarContainer object of 25 artists>
In [63]: plt.show()
/home/xxx/apps/anaconda3/lib/python3.6/site-packages/matplotlib/figure.py:445
: UserWarning: Matplotlib is currently using agg, which is a non-GUI
backend, so cannot show the figure.
% get_backend())
Happy Hacking!