11

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?

9
  • But does that really make sense? plt.plot just uses the currently active axis and plots. If you want to avoid a third party code to plot to your figure, you can e.g. close it. Using sca to unset your created axis as the currently active axis should work as well. In my opinion a default behavior that forces plt.plot to close all open plots is probably hard to motivate :) Commented Feb 20, 2015 at 15:05
  • 1
    I totally understand you concerns here, but I think python code is a little different from other OO-Languages, like e.g. Java/C#. In python information-hiding is a convention between caller and callee. E.g. there are no private member attributes as in many other languages but a convention that attributes starting with an underscore should be treated as private. But this is not enforced. I think your example is a similar situation. I would consider unconditionally calling plt.plot bad style, but not forbidden in any way. Commented Feb 20, 2015 at 15:25
  • 1
    Fine. This isn't really helping me answer my question, you're basically telling me I shouldn't worry about it :) Commented Feb 20, 2015 at 15:41
  • 1
    No, I am saying: I don't think there is an easy, straightforward solution to this. But of course there are legitimate reasons where this can be a problem. But for these cases there is probably a simple workaround: If you make sure, that plt.gca() does not return your axis then other functions should not be able to manipulate your plot anymore. And there is sca()... Commented Feb 20, 2015 at 15:47
  • 1
    github.com/matplotlib/matplotlib/pull/2624 <- dormant PR starting to pull this apart. Commented Feb 20, 2015 at 18:19

1 Answer 1

20

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:

  1. Creates the figure object that's returned
  2. Creates a FigureManager for that figure using the appropriate backend
  3. The figure manager creates a FigureCanvas, gui window (as needed), and NavigationToolbar2 (zoom buttons, etc)
  4. The figure manager instance is then added to _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

Sign up to request clarification or add additional context in comments.

4 Comments

Excellent! Is there any way I can use this approach in something like IPython Notebook? How would I display fig?
It's a touch more complicated and backend-specific to use interactive figures. I'm working on those examples now. I think it might not be possible with an ipython notebook, though. (I almost never use notebooks, so I'm not terribly familiar with them.) I'm assuming IPython uses pyplot's figure manager to find the figure you create, and I don't know offhand how to tell IPython to use a figure manager that's been manually created.
@nicolaskruchten - Ignore that part about it not working with IPython notebooks. It actually works exactly the same as the other backends!
Thanks @JoeKington! I was in the process of starting to type this up, got distracted and came back to find it done!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.