Reputation: 27430
If I create an Axes
object in matplotlib
and mutate it (i.e. by plotting some data) and then I call a function without passing my Axes
object to that function then that function can still mutate my Axes
. For example:
import matplotlib.pyplot as plt
import numpy as np
def innocent_looking_function():
#let's draw a red line on some unsuspecting Axes!
plt.plot(100*np.random.rand(20), color='r')
fig, ax = plt.subplots()
ax.plot(100*np.random.rand(20), color='b') #draw blue line on ax
#ax now has a blue line, as expected
innocent_looking_function()
#ax now unexpectedly has a blue line and a red line!
My question is: can I prevent this global-variable behaviour in general? I know I can call plt.close()
before calling any innocent_looking_function()
but is there some way to make this the default?
Upvotes: 11
Views: 1399
Reputation: 284890
Sure! What you need to do is bypass the pyplot
state machine entirely when you make your figure.
It's more verbose, as you can't just call fig = plt.figure()
.
First off, let me explain how plt.gca()
or plt.gcf()
works. When using the pyplot
interface, matplotlib stores all created-but-not-displayed figure managers. Figure managers are basically the gui wrapper for a figure.
plt._pylab_helpers.Gcf
is the singleton object that stores the figure managers and keeps track of which one is currently active. plt.gcf()
returns the active figure from _pylab_helpers.Gcf
. Each Figure
object keeps track of it's own axes, so plt.gca()
is just plt.gcf().gca()
.
Normally, when you call plt.figure()
, it:
FigureManager
for that figure using the appropriate backendFigureCanvas
, gui window (as needed), and NavigationToolbar2
(zoom buttons, etc)_pylab_helpers.Gcf
's list of figures.It's this last step that we want to bypass.
Here's a quick example using a non-interactive backend. Note that because we're not worried about interacting with the plot, we can skip the entire figure manager and just create a Figure
and FigureCanvas
instance. (Technically we could skip the FigureCanvas
, but it will be needed as soon as we want to save the plot to an image, etc.)
import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure
# The pylab figure manager will be bypassed in this instance. `plt.gca()`
# can't access the axes created here.
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)
Just to prove that gca
can't see this axes:
import matplotlib.pyplot as plt
import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure
# Independent figure/axes
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)
ax.plot(range(10))
# gca() is completely unaware of this axes and will create a new one instead:
ax2 = plt.gca()
print 'Same axes?:', id(ax) == id(ax2)
# And `plt.show()` would show the blank axes of `ax2`
With an interactive backed, it's a touch more complicated. You can't call plt.show()
, so you need to start the gui's mainloop yourself. You can do it all "from scratch" (see any of the "embedding matplotlib" examples), but the FigureManager
abstracts the backed-specific parts away:
As an example using the TkAgg backend:
import matplotlib.backends.backend_tkagg as backend
from matplotlib.figure import Figure
fig = Figure()
ax = fig.add_subplot(111)
manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()
To use one of the other backends, just change the backend import. For example, for Qt4:
import matplotlib.backends.backend_qt4agg as backend
from matplotlib.figure import Figure
fig = Figure()
ax = fig.add_subplot(111)
manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()
This actually even works with the nbagg
backend used in IPython notebooks. Just change the backend import to import matplotlib.backends.backend_nbagg as backend
Upvotes: 20