ipympl icon indicating copy to clipboard operation
ipympl copied to clipboard

Displaying a previously created Matplotlib figure in a widget

Open thomasaarholt opened this issue 5 years ago • 8 comments

I'm trying to find a way to take a figure previously created, and show it inside a widget, without losing interactivity. Here's a short version of my approach (which may not be the right one):

fig = plt.figure() # previously created figure
out = widgets.Output()
with out:
    display(fig)
# other things happening
display(widgets.HBox([fig.canvas, some_other_fig]))

If I first create a figure in the notebook outside of an Output widget, and then wish to include it later inside something like an HBox next to a second figure, I seem to lose interactivity with the figure (including the nice ipympl zoom interface).

Here is a nbviewer view of a MWE notebook I created, download it top right on the window. It's best if one downloads it and runs it since the first figure appears buggy in the nbviewer.

Additionally, and I might create a new issue for this - if I call fig1 at the end of the notebook, I get an unexpected error. Here I'd expect the regular figure to be shown. The error is AttributeError: 'NoneType' object has no attribute '_send_event'.

thomasaarholt avatar Mar 16 '20 15:03 thomasaarholt

As @martinRenou pointed out in jupyter-widgets/ipywidgets#2821, it is fig.canvas, not fig, that I want to display.

thomasaarholt avatar Mar 16 '20 15:03 thomasaarholt

The following is my own solution. I turn off matplotlib interactive mode, which prevents the figure from being shown immediately after calling fig = plt.figure(). Then I place the fig.canvas inside whatever widget I want to use (Like an HBox). Here is an example with two figures:

%matplotlib widget
from IPython.display import display # this is automatically imported in the notebook anyway
from ipywidgets.widgets import HBox
import matplotlib.pyplot as plt
from scipy.misc import face

plt.ioff() # turn off interactive mode so figure doesn't show
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
plt.ion() # figure still doesn't show

ax1.imshow(face())
ax2.imshow(face(True))

display(HBox([fig1.canvas, fig2.canvas]))

image

thomasaarholt avatar Mar 18 '20 08:03 thomasaarholt

Nice! Note that you can hide the header/footer/toolbar now with recent versions.

fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.footer_visible = False

You can also disable the resize handle:

fig.canvas.resizable = False

martinRenou avatar Mar 18 '20 09:03 martinRenou

Brilliant! Does anything exist by default in the footer?

I also just noticed that I can do fig.canvas.layout = Layout(...), that's very useful!

thomasaarholt avatar Mar 18 '20 09:03 thomasaarholt

Does anything exist by default in the footer?

By default, the footer contains an empty string. It should contain the mouse position if you hover the plot.

I also just noticed that I can do fig.canvas.layout = Layout(...), that's very useful!

Yes! And you know you don't only have HBox and VBox for making layouts. You can use the AppLayout, the TwoByTwoLayout, and the great GridSpecLayout, see https://ipywidgets.readthedocs.io/en/stable/examples/Layout%20Templates.html

martinRenou avatar Mar 18 '20 09:03 martinRenou

We need more examples using those high-level layouts in the repository. To promote those layouts which are way nicer than boxes.

martinRenou avatar Mar 18 '20 09:03 martinRenou

Hmmm it looks like maybe the example doesn't work anymore because HBox only accepts Widget inherited classes, thanks @thomasaarholt for the fix though

alexrockhill avatar Jun 30 '22 20:06 alexrockhill

@alexrockhill i think there was a typo in the first post's example. It should be fig.canvas in the HBox (i've just edited the first post). The canvas inherits from widget while figure does not

ianhi avatar Jul 01 '22 01:07 ianhi