ipywidgets events produces many graphs

Created on 22 Jan 2018  路  7Comments  路  Source: jupyter-widgets/ipywidgets

I am trying to use widget events to make an interactive graph.

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

import ipywidgets as widgets

def myplot(n):
    x = np.linspace(-5, 5, 30)
    y = x**n

    fig, ax = plt.subplots(nrows=1, ncols=1);
    ax.plot(x, y)
    ax.set_xlabel('x')
    ax.set_ylabel('y')

    plt.show()

Interact works as expected (it changes the figure interactively):

widgets.interact(myplot, n=(0,5));

However the following snippet creates several figures that appear below as you interact with the slider.

n_widget = widgets.IntSlider(
                value=2,
                min=0,
                max=5)

def on_value_change(change):
    myplot(n=n_widget.value)

n_widget.observe(on_value_change)
display(n_widget)

Can I update the plot as if I were using widgets.interact()?


My current installation is with conda and Python 3.6 (windows machine).

ipywidgets                7.1.0                     
jupyter                   1.0.0              
jupyter_client            5.2.1                  
jupyter_console           5.2.0             
jupyter_core              4.4.0              
matplotlib                2.1.1             
notebook                  5.3.1               
numpy                     1.14.0

Most helpful comment

Great questions. The recommended way now to display and clear output inside a widget is to use an Output widget. We should add a note in the changelog.

Here's a brief example:

from ipywidgets import Output, IntSlider, VBox
from IPython.display import clear_output
out = Output()

slider = IntSlider()

def square(change):
    with out:
        clear_output()
        print(change.new*change.new)

slider.observe(square, 'value')
slider.value = 50
display(VBox([slider, out]))

All 7 comments

Using IPython.display.display() and .clear_output() the slider disappears after the first interaction.
The following code clears the old results but also the widget.

EDIT: This is expected after ipywidgets v.> 7.0 (see #1274)

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

fig, ax = plt.subplots(nrows=1, ncols=1);
x = np.linspace(-5, 5, 30)
y = x**0

line, = ax.plot(x, y)
ax.set_xlabel('x')
ax.set_ylabel('y')

def myplot(n):
    line.set_ydata(x**n)
    ax.relim()
    ax.autoscale()
    display(fig)
    clear_output(wait=True)


#cell3
n_widget = widgets.IntSlider(
                value=2,
                min=0,
                max=5)

def on_value_change(change):
    myplot(n=n_widget.value)

n_widget.observe(on_value_change)
display(n_widget)

The actual issue/problem description may be a bit lost in the above bunches of code. So to summarize:

With ipywidgets 6.0 it was possible to diplay a widget and a resulting output from one cell. Then using clear_output the output could be removed or updated by subsequently calling display again.

This behaviour has changed in version 7.0. As the changelog tells us, clear_output will now clear everything, including the widget. This makes the previous solution unusable. However, it is not clear what alternative one should use to get the same behaviour back with ipywidgets 7.0.

So this is either a question for the recommended way of adding a persistent widget which can clear the output, or a request for adding back such solution in a future version.

If I understood your issue correctly I managed to solve it by using the following approach (https://github.com/kmader/Quantitative-Big-Imaging-2018/blob/127fd3497cbf37ac2ab89f7076fc1c81dcfb9f17/Exercises/ImageEnhancementPlayground.ipynb)

display(ipw.VBox([ipw.VBox([ipw.HBox([ipw.Label(value = 'Image Name:'), image_name]), 
                    ipw.HBox([ipw.Label(value = 'Noise Type:'), noise_func]),
                    ipw.HBox([ipw.Label(value = 'Noise Level:'), noise_level]),
                    ipw.HBox([ipw.Label(value = 'Filter Name:'), filter_func]),
                    ipw.HBox([ipw.Label(value = 'Filter Size:'), filter_size])        
                   ])]))
display(fig, display_id = 'nice_figure') # need to create figure before callbacks

def update_image(*args):
    show_results(m_axs, 
                 sample_images[image_name.value],
                 noise_func.value, 
                 noise_level.value, 
                 filter_func.value, 
                 {'size': filter_size.value
                 })
    update_display(display_id = 'nice_figure', obj = fig)
image_name.observe(update_image, names='value')

Great questions. The recommended way now to display and clear output inside a widget is to use an Output widget. We should add a note in the changelog.

Here's a brief example:

from ipywidgets import Output, IntSlider, VBox
from IPython.display import clear_output
out = Output()

slider = IntSlider()

def square(change):
    with out:
        clear_output()
        print(change.new*change.new)

slider.observe(square, 'value')
slider.value = 50
display(VBox([slider, out]))

(this will be even easier soon, when the output widget will have a decorator: https://github.com/jupyter-widgets/ipywidgets/pull/1934)

From the comments above, it's not clear to me what's the recommended way of updating a matplotlib plot inside a trait callback.

This works:

@interact(x=(0, 10))
def plot(x):
    plt.plot([1, 2, 3], [4, 5, 6])

But the equivalent using observe has the problem @franktoffel (隆hola! :wave:) described 2 years ago of many plots appearing:

# Many plots
def plot(change):
    plt.plot([1, 2, 3], [5, 4, 6])

slider = widgets.IntSlider(min=0, max=10, value=5)
slider.observe(plot, names="value")
display(slider)

and then using clear_output at the beginning, clears the slider itself:

# Slider disappears
def plot(change):
    clear_output()
    plt.plot([1, 2, 3], [5, 4, 6])

slider = widgets.IntSlider(min=0, max=10, value=5)
slider.observe(plot, names="value")
display(slider)

The "Flickering and jumping output" section of the documentation explains how to combine matplotlib with interactive, but observe is not mentioned.

The three examples that mention matplotlib, "Exploring the Lorenz System of Differential Equations", "Exploring Beat Frequencies using the Audio Object" and "Explore Random Graphs Using NetworkX" use interact or interactive, but observe is not explained.

There is some discussion at https://github.com/jupyter-widgets/ipywidgets/issues/1940 that uses Output as a context manager, but still it's not clear to me how to use it. I tried the code below, but the code widget is empty:

out = widgets.Output()
with out:
    fig, ax = plt.subplots()
ax.plot([1, 2], [1, -1])
# ---
# Another cell, output is empty
out

Is there a chance someone posts here a super short example on how to use a callback + observe + a matplotlib figure? I would be willing to open a pull request to add it to the docs... When I understand how to do it.

(_Edit: Add one extra example_)

Sort of incomplete self-answer: I adapted some code from this SO answer, however it still has a couple of weird things I can't explain about the effect of including (or not) plt.show() and/or display(fig):

import ipywidgets as widgets 
import matplotlib.pyplot as plt

out = widgets.Output(layout=widgets.Layout(height='300px'))

def f(change):
    with out:
        fig, ax = plt.subplots()
        ax.plot([0, 1],[0, 10])
        ax.set_title(change['new'])
        out.clear_output()  # Required, otherwise output area stays the same but plots get added
        # display(fig)  # Doesn't work as a replacement of plt.show()
        plt.show()  # If not here, multiple plots are shown!

w = widgets.IntSlider(min=0, max=10, value=5)
w.observe(f, names="value")
display(w, out)
Was this page helpful?
0 / 5 - 0 ratings