Search code examples
matplotlibwidgetipythonjupyter-lab

In JupyterLab ipywidgets Output widget in context manger shows pyplot figure without explicit calling


If I run this example in two JupyterLab cells:

Cell[1]:

%matplotlib widget
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np

output = widgets.Output()
x = np.linspace(0, 2 * np.pi, 100)

with output:
    print('Print works as expected.')
    fig, ax = plt.subplots()
    
line, = ax.plot(x, np.sin(x))

Cell[2]:

int_slider = widgets.IntSlider(value=1, min=0, max=10, step=1)

def update(change):
    line.set_ydata(np.sin(change.new * x))
    # fig.canvas.draw() # This is not needed for update for some reason

int_slider.observe(update, 'value')
widgets.HBox([int_slider, output])

The figure is displayed under cell 1 and the slider with the text under cell 2.

The expected behavior would be showing everything under cell 2.

If I use "%matplotlib notebook" then the figure disappears from the output of cell 1. The slider and the text are displayed correctly under cell 2 together with a "Javascript Error: IPython is not defined".

I also tried "%matplotlib inline" which shows the figure incorrectly under cell 1 and the figure is not following the slider anymore.

Question: How to avoid this unexpected result and make the figure respect the Output widget like the print statement?


Solution

  • Solution

    In the first cell call plt.close() after plotting the data. This prevents the figure from being displayed right away.

    In the second cell create a new Canvas figure and use it to store the original figure. Set this Canvas and call plt.show() to display the desired plot. It will still be interactive but will only be shown after the second cell is run.

    Code

    ############## First Cell ##############
    
    %matplotlib widget
    import ipywidgets as widgets
    import matplotlib.pyplot as plt
    import numpy as np
    
    output = widgets.Output()
    x = np.linspace(0, 2 * np.pi, 100)
    
    with output:
        print('Print works as expected.')
        fig, ax = plt.subplots()
        line, = ax.plot(x, np.sin(x))
        plt.close()
    
    ############## Second Cell ##############
    
    int_slider = widgets.IntSlider(value=1, min=0, max=10, step=1)
    
    fig2 = plt.figure()
    manage = fig2.canvas.manager
    manage.canvas.figure = fig
    fig.set_canvas(manage.canvas)
    plt.show()
    
    def update(change):
        line.set_ydata(np.sin(change.new * x))
    
    int_slider.observe(update, 'value')
    widgets.HBox([int_slider, output])