Matplotlib draw in console instead of cell output when call in widget event callback

Here is the code to reproduce the problem

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

def wait_for_change(widget, value):
    future = asyncio.Future()
    def getvalue(change):
        # make the new value available
        future.set_result(change.new)
        widget.unobserve(getvalue, value)
    widget.observe(getvalue, value)
    return future
    
def draw(scale):
    x = np.arange(0, scale*np.pi,0.1)   # start,stop,step
    y = np.sin(x)
    plt.plot(x,y)
    plt.show()

from ipywidgets import IntSlider
slider = IntSlider()

async def f():
    for i in range(100):
        print('did work %s'%i)
        x = await wait_for_change(slider, 'value')
        draw(x)
        print('async function continued with value %s'%x)
asyncio.ensure_future(f())
slider

The expected behavior is to draw image in cell, but turn out in console log instead. Is there anything I can do to fix this problem?

Actually there’s several issues going on here.

I think the main one is that now with ipywidgets explicitly handling the output is necessary to control what goes to output vs. the Log console. You’ve noticed this using JupyterLab; however, since Jupyter Notebook 7+ is built on the same tech, it is necessary there now, too.

The second half of my posts here covers what you need to do to improve your code to current best practice to control output.
Additional posts addressing this practice with ipywidgets can be found here.

Additionally, I think though that matplotlib isn’t compatible with aysncio? I may completely misinterpreting a comment here:


There always seems to be multiple ways to do these things; however, I’m not seeing the need for asyncio here. You can use ipywidgets interactive and if you want the print prompts you include you can add them, sending them to the Output widget, see here. I think my code below addresses maybe much of what you were trying to do in your code example. I think you wanted to limit the user to repeating the plot 100 times? I didn’t implement that because I wasn’t following quite if that was the purpose or if it was necessary. (I think I can easily add it but not quite getting if it was needed and not wanting to slide 100 times to test it, I left it out for now.) Feel free to clarify and if my implementation lacks something else, please let me know?
It is largely based on here:

import matplotlib.pyplot as plt
import ipywidgets as widgets
import numpy as np
from IPython import display 

out = widgets.Output()

i = 0

def PlotAndNote(scale):
    global i
    x = np.arange(0, scale*np.pi,0.1)   # start,stop,step
    y = np.sin(x)
    plt.plot(x,y)
    with out:
        if scale == 0:
            print('did work %s'%i)
            i+=1
        else:
            print('did work %s'%i)
            print('plotting continued with value %s'%scale)
            i+=1

scale_slider=widgets.IntSlider(min=0,max=100,value=0, continuous_update = False) #Create our intslider such that the range is [0,100] and default is 0; and use `continuous_update = False` to restrict execution to mouse release events,see https://ipywidgets.readthedocs.io/en/stable/examples/Using%20Interact.html#disabling-continuous-updates

gui = widgets.interactive(PlotAndNote, scale=scale_slider) #Create our interactive graphic with the slider as the argument
out_vbox = widgets.VBox([gui,out])
out_vbox

I left out %matplotlib inline as I think that is the default these days.

You can see the code work in temporary Jupyter sessions with ipywidgets installed already spawned via MyBinder.org by clicking here.

1 Like

Thank you. I found a work around that works for me by creating figure outside the callback, and pass the handler to the event callback so that it can update the figure by calling the canvas.draw method.
Ref

But what I hope is for ipywidget to support blocking execution instead of callback, I found some issues on github and I also post one on SO.

1 Like