Reputation: 1233
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
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?
Upvotes: 3
Views: 1384
Reputation: 46
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)
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))
Upvotes: 0
Reputation: 3152
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.
Upvotes: 0
Reputation: 3199
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)
Upvotes: 1