I'm trying to do a simple button to "reset" widgets to certain default values. I'm using the @interact
decorator in Jupyter Lab environment. The problem is that the widgets identifiers have their values copied to the same identifiers used as float variables inside the function and therefore I cannot access them anymore within this new scope. Here is a short example (not working):
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, Button
@interact(starts_at=(0, np.pi*0.9, np.pi*0.1), ends_at=(np.pi, 2*np.pi, np.pi*0.1))
def plot_graph(starts_at=0, ends_at=2*np.pi):
def on_button_clicked(_):
# instructions when clicking the button (this cannot work)
starts_at = 0
ends_at = 2*np.pi
button = Button(description="Reset")
button.on_click(on_button_clicked)
display(button)
f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
x = np.linspace(0, 2*np.pi, 1000)
plt.plot(x, f(x))
plt.xlim([starts_at, ends_at])
Does anybody know how to send to the scope of the decorated function a reference to the original widget objects? I'll be accepting also simple ways of implementing a button to reset those sliders.
:-D
Edit: corrected text flow
To accomplish this you'll have to use the more manual interactive_output
function. That function allows you to pre-create the widgets and then pass them in:
import ipywidgets as widgets
import numpy as np
import matplotlib.pyplot as plt
start_slider = widgets.FloatSlider(
val = 0,
min = 0,
max = np.pi*0.9,
step = np.pi*0.1,
description = 'Starts at'
)
end_slider = widgets.FloatSlider(
val = np.pi,
min = np.pi,
max = 2*np.pi,
step = np.pi*0.1,
description = 'Ends at'
)
def on_button_clicked(_):
start_slider.value = 0
end_slider.value = 2*np.pi
button = Button(description="Reset")
button.on_click(on_button_clicked)
def plot_graph(starts_at=0, ends_at=2*np.pi):
f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
x = np.linspace(0, 2*np.pi, 1000)
plt.plot(x, f(x))
plt.xlim([starts_at, ends_at])
display(widgets.VBox([start_slider, end_slider, button]))
widgets.interactive_output(plot_graph, {'starts_at': start_slider, 'ends_at':end_slider})
However, this will regenerate the plot entirely everytime you update it which can lead to a choppy experience. So you can also re-write this to use the matplotlib methods like .set_data
if you use an interactive matplotlib backend in the notebook. So if you were to use ipympl you could follow the examples in this example notebook.
I wrote a library mpl-interactions to make it easier to control matplotlib plots using ipywidgets sliders. It provides a function analogous to ipywidgets.interact
in that it handles creating the widgets for you, but it has the advantage of being matplotlib focused so all you need to provide is the data. More about the differences to ipywidgets here
%matplotlib ipympl
import mpl_interactions.ipyplot as iplt
import matplotlib.pyplot as plt
import numpy as np
import ipywidgets as widgets
def plot_graph(starts_at=0, ends_at=2*np.pi):
x = np.linspace(starts_at, ends_at, 1000)
f = lambda x : sum(1/a*np.sin(a*x + np.pi/a) for a in range(1,6))
return np.array([x, f(x)]).T
fig, ax = plt.subplots()
button = widgets.Button(description = 'reset')
display(button)
controls = iplt.plot(plot_graph, starts_at = (0, np.pi), ends_at = (np.pi, 2*np.pi), xlim='auto', parametric=True)
def on_click(event):
for hbox in controls.controls.values():
slider = hbox.children[0]
slider.value = slider.min
button.on_click(on_click)