The ipywidgets
library offers a big collection of standard widgets and supports some really powerful third-party widgets to like ipympl
, ipyleaflet
and more... So you can build some pretty sophisticated UI right in your Jupyter Notebook, then launch your app as a dashboard with voila
or (ok, very soon!) create an interactive document with Curvenote.
When your UI starts getting bigger, developing and debugging can be tricky though β for numerous reasons β but the biggest thing is often lack of access to stdout
and stderr
from the various callback functions that you end up creating. Luckily there is a simple trick that helps solve this.
A broken exampleΒΆ
The following code has a problem, it will happily render my ipympl
plot π but the pick event does not work. When I pick on the plot nothing happens at all, no feedback, no error message, nada.
import matplotlib.pyplot as plt
import numpy as np
%matplotlib widget
plt.ioff()
fig, ax = plt.subplots(1, 1, figsize=(4,4))
plt.ion()
ax.plot(np.sin(np.random.random(100)),picker=True, pickradius=2)
selection = { "line": None }
def onpick(e):
m = e.mouseevent
e.artist.set__color('#DC143C')
e.artist.set_zorder(10)
e.artist.set_linestyle('-')
e.artist.set_marker('o')
e.artist.set_markersize(e.artist.get_markersize() + 1)
selection["line"] = e.artist
cid = fig.canvas.mpl_connect('pick_event', onpick)
display(fig.canvas)
The problem is actually on the line π
e.artist.set__color('#DC143C')
Where there should be only one underscore, but the stderr
generated in the callback function gets eaten. To solve this we need to set up a way for stderr
to get out to the notebook interface. We can do this with ipywidgets
, specifically the Output
widget!
Debugging with an output widgetΒΆ
We need to do 3 things:
- Create an instance of an
Output
widget at the top of our code display(...)
our debugOutput
widget somewhere (doesn't have to be in the same cell)- We add a conditional
with context
block at the top of our event handlers
After that, we get error messages showing up on our output display, and can also print(...)
temporary debug messages there too while developing. To remove the output, we can remove the code that creates the instance and the conditional context blocks can stay in our callbacks.
And π₯ we now have stderr
output!
%matplotlib widget
# create an output widget!
w_debug_output = Output()
plt.ioff()
fig, ax = plt.subplots(1, 1, figsize=(4,4))
plt.ion()
ax.plot(np.sin(np.random.random(100)),picker=True, pickradius=2)
def onpick(e):
with w_debug_output if w_debug_output else nullcontext():
m = e.mouseevent
e.artist.set__color('#DC143C')
e.artist.set_zorder(10)
e.artist.set_linestyle('-')
e.artist.set_marker('o')
e.artist.set_markersize(e.artist.get_markersize() + 1)
selection["line"] = e.artist
cid = fig.canvas.mpl_connect('pick_event', onpick)
display(fig.canvas)
display(w_debug_output)
This makes it pretty easy to fix our error here and chance to catch other errors while developing with ipywidgets
in a notebook. Also we can now print
debug messages and theyβll also appear in the Output
widget too.
Here's the result of this example code working below:
%matplotlib widget
# create an output widget!
w_debug_output = Output()
plt.ioff()
fig, ax = plt.subplots(1, 1, figsize=(4,4))
plt.ion()
ax.plot(np.sin(np.random.random(100)),picker=True, pickradius=2)
def onpick(e):
with w_debug_output if w_debug_output else nullcontext():
m = e.mouseevent
e.artist.set_color('#DC143C')
e.artist.set_zorder(10)
e.artist.set_linestyle('-')
e.artist.set_marker('o')
e.artist.set_markersize(e.artist.get_markersize() + 1)
selection["line"] = e.artist
cid = fig.canvas.mpl_connect('pick_event', onpick)
display(fig.canvas)
display(w_debug_output)