How to change Matplotlib backend temporarily (locally) for a function in Jupyter notebook, wihout affecting the backend globally?

Viewed 502
  • The goal:

I am writing a function that uses Qt5Agg backend within a Jupyter notebook. The rest of the notebook mostly uses inline. The function generates an interactive plot with matplotlib widgets, and I want to make it work in a separate window. The rest of the notebook produces a bunch of figures, sometimes hundreds in a single cell that have to be generated inline, for efficiency.

I have been running %matplotlib qt just before running the function and %matplotlib inline immediately after running the function. However, I don't want to bother with these two extra lines, especially because %matplotlib inline has to be run in a separate cell. It is prone to user error - the backend will get stuck in Qt5Agg if the user forgets to run the cell after.

I would like to change the backend temporarily, by adding some code within the function. This would have the added advantage of making this function more compatible. I could transfer it to other notebooks and it would work as expected, using Qt5Agg, and won't mess with whatever backend the rest of that notebook uses (inline or something else).

Please note that I am coding on Windows, but I have to make sure that the notebook works on all operating systems.

  • What I have tried so far:

The most obvious solution I could come up with was setting a temporary rc context with a decorator...

import matplotlib.pyplot as plt

@plt.rc_context({'backend': 'Qt5Agg'})
def function(foo):
    ...
    return None

This sets a context for the backend, as expected, but Matplotlib doesn't use it! If I print the backend from within the function, it will tell me that it is Qt5Agg, while I'm staring at an inline plot below it. Other parameters can be effectively changed using the rc_context. I can easily change stuff like 'lines.linestyle' and the plot will reflect those changes. Only when it comes to backend, Matplotlib refuses to use the value I set.

Next I tried changing the 'backend' value in the rcParams dictionary (this would change the backend globally, not locally but I tried it anyway). It leads to a similar situation where the value is changed in the dictionary, but Matplotlib continues using the inline backend. I also tried using switch_backend and matplotlib.use and neither of them worked reliably. Besides, they are also supposed to change the backend globally, so not ideal for my use. Of all the methods I discovered so far, magic is the only way to properly change backend.

  • Summary:
  1. I can't get Matplotlib to reliably change backends without using magic.
  2. Magic sets the backend globally, not locally within the function.
  3. I need to change the backend locally, for only one function.
  4. Matplotlib won't use the backend from rc_context.

P.S. - This is my first post, so apologies if I haven't provided all the useful information. Let me know if you need more info.

1 Answers

I found the best way to switch to PyQt and back to inline is through the 'close_event' in mpl_connect. Here is an outline of what I did:

def function(foo):
    #Switches to qt interactive backend
    %matplotlib qt
    ...
    fig = plt.figure(...)
    ...
    def on_close(event):
        %matplotlib inline
    fig.canvas.mpl_connect('close_event', on_close) #Switches backend to inline when the figure closes
    ...
    return None

It works perfectly on my Windows machine. I can close the figure window using the default close button, or by passing plt.close() using a Matplotlib widget. The backend switches back to inline in both cases.

Related