Search code examples
pythonipywidgets

ipywidgets: clear_output() has no effect. Why?


I want to provide a GUI for the user to easily remove outliers from a dataset and plot the data. Therefore, I provide input fields for the user to specify data value limits; and a button to update the plot:

import pandas as pd
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, clear_output

df = pd.read_csv("https://raw.githubusercontent.com/juliangermek/aia/main/treasure_data.csv", sep=";")

min_value_x = df["x"].min()
max_value_x = df["x"].max()
min_value_y = df["y"].min()
max_value_y = df["y"].max()

min_x = widgets.BoundedIntText(
    value=min_value_x,
    min=min_value_x,
    max=max_value_x,
    step=1,
    description='x lower limit:',
)
max_x = widgets.BoundedIntText(
    value=max_value_x,
    min=min_value_x,
    max=max_value_x,
    step=1,
    description='x upper limit:',
)

min_y = widgets.BoundedIntText(
    value=min_value_y,
    min=min_value_y,
    max=max_value_y,
    step=1,
    description='y lower limit:',
)
max_y = widgets.BoundedIntText(
    value=max_value_y,
    min=min_value_y,
    max=max_value_y,
    step=1,
    description='y upper limit:',
)

button = widgets.Button(description="Update image", button_style="info")
output = widgets.Output()

def on_button_clicked(b):
    with output:
        clear_output()

        is_within_x_domain = (df.x > min_x.value) & (df.x < max_x.value)
        is_within_y_domain = (df.y > min_y.value) & (df.y < max_y.value)

        df2 = df[is_within_x_domain & is_within_y_domain]
        plt.figure(figsize=(20, 4))
        plt.scatter(df2["x"], df2["y"])

button.on_click(on_button_clicked) # Run when button is clicked
on_button_clicked(button) # Run when cell is run (so show image on start)

display(min_x)
display(max_x)
display(min_y)
display(max_y)

display(button, output)

enter image description here

You should be able to run the code in this colab notebook (as long as you are signed in into a google account): https://colab.research.google.com/drive/1eblVXf8J9V0VaObig1isJAbfadfuDIbK?usp=sharing

When I click on the update button, a new plot is generated, but the old plot is not deleted although I use clear_output() in the function triggered by the button. Why is this and how can I clear the old plot before displaying the new one?

Thanks!


Solution

  • As far as I know, the Output widget can not handle matplotlib plots. In ipywidgets documents:

    The Output widget can capture and display stdout, stderr and rich output generated by IPython.

    So you can't use this widget. Here is a workaround (which is not neat):

    import pandas as pd
    import matplotlib.pyplot as plt
    import ipywidgets as widgets
    from IPython.display import display, clear_output
    
    df = pd.read_csv("https://raw.githubusercontent.com/juliangermek/aia/main/treasure_data.csv", sep=";")
    
    min_value_x = df["x"].min()
    max_value_x = df["x"].max()
    min_value_y = df["y"].min()
    max_value_y = df["y"].max()
    
    min_x = widgets.BoundedIntText(
        value=min_value_x,
        min=min_value_x,
        max=max_value_x,
        step=1,
        description='x lower limit:',
    )
    max_x = widgets.BoundedIntText(
        value=max_value_x,
        min=min_value_x,
        max=max_value_x,
        step=1,
        description='x upper limit:',
    )
    
    min_y = widgets.BoundedIntText(
        value=min_value_y,
        min=min_value_y,
        max=max_value_y,
        step=1,
        description='y lower limit:',
    )
    max_y = widgets.BoundedIntText(
        value=max_value_y,
        min=min_value_y,
        max=max_value_y,
        step=1,
        description='y upper limit:',
    )
    
    button = widgets.Button(description="Update image", button_style="info")
    
    def on_button_clicked(b):
        clear_output()
        display(min_x, max_x, min_y, max_y, button)
            
        is_within_x_domain = (df.x > min_x.value) & (df.x < max_x.value)
        is_within_y_domain = (df.y > min_y.value) & (df.y < max_y.value)
    
        df2 = df[is_within_x_domain & is_within_y_domain]
        plt.figure(figsize=(20, 4))
        plt.scatter(df2["x"], df2["y"])
    
    button.on_click(on_button_clicked) # Run when button is clicked
    on_button_clicked(button) # Run when cell is run (so show image on start)