3

I am trying to update an interactive matplotlib figure while in a loop using JupyterLab. I am able to do this if I create the figure in a different cell from the loop, but I would prefer to create the figure and run the loop in the same cell.

Simple Code Example:

import matplotlib.pyplot as plt
import time

%matplotlib widget

fig = plt.figure()

for i in range(5):
    x = list(range(i+2))
    xx = [x**2 for x in x]
    plt.clf()
    plt.plot(x, xx)
    fig.canvas.draw()
    
    time.sleep(1)

If fig = plt.figure() is in the same cell as the loop the figure is not updated until the loop finishes:

Not Dynamically Updating

If I create the figure in a different cell I get the dynamic update, but I would like to be able to create the figure in the same cell if possible so the output is below the loop:

Requires extra cell

I have tried several answers in other questions (here, here, and here) however, they do not seem to work with interactive figures in JupyterLab. I am using the jupyter/scipy-notebook docker image as my environment, so I believe everything is set up correctly.

Is there any way to get the dynamic update in the same cell as the figure is created?

kgoodrick
  • 391
  • 3
  • 12

2 Answers2

5

If you don't want to use asyncio, you can use display(..., display_id=True) to obtain a handle and use .update() on it:

import matplotlib.pyplot as plt
import time
%matplotlib widget
fig = plt.figure()

hfig = display(fig, display_id=True)


def update():
    for i in range(5):
        print(i)
        x = list(range(i + 2))
        xx = [x**2 for x in x]
        plt.clf()
        plt.plot(x, xx)
        fig.canvas.draw()
        hfig.update(fig)
        time.sleep(1)

update()

plt.close(fig)

PS: Make sure to close your figure!

BlackHC
  • 592
  • 5
  • 10
  • I like the look of this solution, but for me it draws two figures, one in the widget and one as an image. Do you know if there is a way to remove the second plot? – kgoodrick Dec 23 '20 at 23:43
  • 1
    What notebook/jupyter-lab version are you using? I'm using notebook 6.1.4 and it works. (Also works in jupyter-lab.) I've only seen a second plot appear without `plt.close(fig)` – BlackHC Jan 06 '21 at 01:01
  • @BlackHC I think you should mention the importance of closing the figure in the text of your answer. – Lukas S Nov 16 '22 at 13:14
2

You can use asyncio, taking advantage of the IPython event loop:

import matplotlib.pyplot as plt
import asyncio
%matplotlib widget
fig = plt.figure()


async def update():
    for i in range(5):
        print(i)
        x = list(range(i + 2))
        xx = [x**2 for x in x]
        plt.clf()
        plt.plot(x, xx)
        fig.canvas.draw()
        await asyncio.sleep(1)


loop = asyncio.get_event_loop()
loop.create_task(update());

gif with a notebook with single cell being run and the animation progressing as expected

krassowski
  • 13,598
  • 4
  • 60
  • 92