3

I am trying to create a ipywidget interface with a matplotlib figure that updates upon changing a slider. It works in principle, but it always creates an extra figure.

Here's the code:

import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import widgets
from IPython.display import display, clear_output

# make a grid

x = np.linspace(0, 5, 100)
X, Y = np.meshgrid(x, x)

# create the layout with slider

out = widgets.Output(layout=widgets.Layout(height='300px', width = '400px', border='solid'))
slider = widgets.IntSlider(value=1, min=1, max=5)
w = widgets.VBox(children=(out, slider))

# axes to plot into

ax = plt.axes()

display(w)

def update(value):
    i = slider.value
    Z = np.exp(-(X / i)**2 - (Y / i)**2)
    ax.pcolormesh(x, x, Z, vmin=0, vmax=1, shading='auto')
    with out:
        clear_output(wait=True)
        display(ax.figure)

slider.observe(update)
update(None)

And here's the undesired output

output

The widget works, and only the upper output is updated, but I do not understand why the lower output also exists or how to get rid of it. Am I missing something obvious?

3 Answers 3

1

You can use the widget backend, %matplotlib widget, which I think is designed for this. You'll need to put %matplotlib widget at the top (or before matplotlib stuff is brought in).

Update: Also some guidance form matplotlib here and below, emphasis added.

Note

To get the interactive functionality described here, you must be using an interactive backend. The default backend in notebooks, the inline backend, is not. backend_inline renders the figure once and inserts a static image into the notebook when the cell is executed. Because the images are static, they can not be panned / zoomed, take user input, or be updated from other cells.

Your example can be reduced to:

%matplotlib widget
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import widgets
from IPython.display import display, clear_output

x = np.linspace(0, 5, 100)
X, Y = np.meshgrid(x, x)

slider = widgets.IntSlider(value=1, min=1, max=5)

ax = plt.axes()
display(slider)
def update(value):
    i = slider.value
    Z = np.exp(-(X / i)**2 - (Y / i)**2)
    ax.pcolormesh(x, x, Z, vmin=0, vmax=1, shading='auto')

slider.observe(update)
update(None)

enter image description here

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

3 Comments

Thanks, that indeed always just shows one figure, but it does seem to depend on the widget backend. Without it it is not updating. The way I did it in the original post always shows two figures, but it seems to work in both classic notebook and Jupyter lab even without calling the widget backend (I have installed an the ipywidgets extension, however).
Can you clarify "Without it it is not updating"? I'm hoping with %matplotlib widget you are getting a single figure AND it's updating with the slider.
yes, with the widget backend it is working fine, without it (e.g. inline backend) it does not work but also does not raise an exception. I was hoping for something to work under all backends in both the notebook and jupyter lab. I suppose checking for the right backend and issuing a warning otherwise is the way to go then.
0

The answer by jayveesa is a really good way to avoid this output. I'd like to add an answer to the why of your question.

The widget works, and only the upper output is updated, but I do not understand why the lower output also exists or how to get rid of it. Am I missing something obvious?

An instructive thing to do here is to comment out the lines in update that display the figure


def update(value):
    i = slider.value
    Z = np.exp(-(X / i)**2 - (Y / i)**2)
    ax.pcolormesh(x, x, Z, vmin=0, vmax=1, shading='auto')
    # with out:
        # clear_output(wait=True)
        # display(ax.figure)
update(None)

if you do this you will see only one plot output. This is because when you use the the %matplotlib inline backend for jupyter notebooks then any figure that was created in a cell will be closed and displayed when the cell finishes running. (see explanation from a core matplotlib dev here: https://github.com/matplotlib/matplotlib/issues/18248#issuecomment-675177108). So the figure surrounded by a black outline is from the display(ax.figure) and the lower figure is from the figure that was created earlier.

Comments

0

I had exactly same problem and used %matplotlib notebook Not only I had single figure but I could also interact (have x,y position, rotate and change the angle of 3d plot) enter image description here

import ipywidgets as widgets
%matplotlib notebook
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.set_xlim(0, xij.max())
ax.set_ylim(0, yij.max())
ax.set_zlim(min_value, max_value)

def update_plot(frame=0):
    ax.clear()
    ax.set_xlim(0, xij.max())
    ax.set_ylim(0, yij.max())
    ax.set_zlim(min_value, max_value)
    surf = ax.plot_surface(xij, yij, data[frame], cmap=cm.coolwarm,
                   linewidth=0, antialiased=False)
 
widgets.interact(update_plot, frame=widgets.IntSlider(min=0, max=100, step=1, value=0))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.