Threading with Matplotlib and ipywidgets

Some consideration:

  • rather than threads, consider using the event loop
    • if you absolutely need threads, consider still using asyncio, and a deque
  • maybe have a look at using ipympl
    • doing full display updates are pretty expensive

Here’s a basic pattern: it delegates the asyncio stuff to an interval-based loop. Ideally, the do_work would also be async, but matplotlib isn’t.

import ipywidgets, asyncio, time
output = ipywidgets.Textarea()
running = ipywidgets.ToggleButton(description="start", icon="play")
interval = ipywidgets.FloatSlider(description="loop interval", value=1, min=0.001, max=10) # zero can hang if you don't await
ui = ipywidgets.VBox([ipywidgets.HBox([running, interval]), output])
tasks = dict()

def do_work(t):
    output.value = f"{t}: {time.time()}"

async def do_loop():
    t = 0
    while running.value:
        do_work(t)
        t += 1
        await asyncio.sleep(interval.value)

def on_running_changed(*change):
    task = tasks.pop("do_loop", None)
    output.value = f"maybe stopping {task}..."
    if task:
        output.value = f"stopping {task}..."
        task.cancel()
    if running.value:
        output.value = "starting..."
        tasks["do_loop"] = asyncio.get_event_loop().create_task(do_loop())
running.observe(on_running_changed, "value")

ui
2 Likes