Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need to create and update figures in seperate cells? #425

Open
henrypinkard opened this issue Feb 3, 2022 · 7 comments
Open

Need to create and update figures in seperate cells? #425

henrypinkard opened this issue Feb 3, 2022 · 7 comments

Comments

@henrypinkard
Copy link

Something I've seen in previous versions as well as the current version: In order to have a figure that dynamically updates while some code is executing (e.g. tracking the loss function of an optimization), I have to create the figure in a separate jupyter notebook cell and run it before a I run the code that updates. If I don't do this, then a blank figure shows and never updates. I'm using Jupyter lab. Wondering if I'm doing something wrong here? It would make the code cleaner and easier to run if it could all be put in a single cell.

Example:

Using the following function:

def update_function(fig, ax):
   while True:
       # Do some computation
       ax.clear() #clear previously drawn
       ax.plot(some_data)
       fig.canvas.draw() #force it to update

This works:

# cell 1
fig, ax = plt.subplots()
# cell 2
update_function(fig, ax)

But this does not:

# one combined cell
fig, ax = plt.subplots()
update_function(fig, ax)
@ianhi
Copy link
Collaborator

ianhi commented Feb 3, 2022

Hi @henrypinkard this is unfortunately a known issue. There is some discussion of it and potential solutions here #290

@ianhi
Copy link
Collaborator

ianhi commented Feb 3, 2022

ahh and it turns out that there is a pretty simple workaround for the meantime

%matplotlib widget
from matplotlib import pyplot as plt
from time import sleep
import numpy as np

def display_immediately(fig):
    canvas = fig.canvas
    display(canvas)
    canvas._handle_message(canvas, {'type': 'send_image_mode'}, [])
    canvas._handle_message(canvas, {'type':'refresh'}, [])
    canvas._handle_message(canvas,{'type': 'initialized'},[])
    canvas._handle_message(canvas,{'type': 'draw'},[])
    
    
with plt.ioff():
    fig = plt.figure()

display_immediately(fig)

for i in range(10):
    x, y = np.random.random(2)
    plt.scatter(x, y)
    fig.canvas.draw()
    sleep(0.1)

@henrypinkard
Copy link
Author

Got it, thanks. I'll give that a shot.

Awesome library overall!!

@ianhi
Copy link
Collaborator

ianhi commented Feb 4, 2022

as an aside I would recommend instead of clearing and redrawing every time you should use the set_data methods. e.g

line = ax.plot(some_data)[0]

# for updating
line.set_data(new_data)
fig.canvas.draw()

@henrypinkard
Copy link
Author

Yeah I think I saw that way in the example, but I opted for clear() because I'm not always calling plot. Sometimes imshow, scatter, bar, etc. As far as I can tell, the objects returned don't always have a set_data method. Thoughts?

@ianhi
Copy link
Collaborator

ianhi commented Feb 4, 2022

Yeah I think I saw that way in the example, but I opted for clear() because I'm not always calling plot. Sometimes imshow, scatter, bar, etc. As far as I can tell, the objects returned don't always have a set_data method. Thoughts?

Many (but not all artists) do have set_data or equivalent, though they are not always documented and there is no central example (matplotlib/matplotlib#19520).

In my mpl-interactions pcakage I've done a lot of the work of figuring that out and if you look through this file https://github.com/ianhi/mpl-interactions/blob/master/mpl_interactions/pyplot.py then you can find the techniques for updating various functions. There's a bit of extra stuff around the framework for buliding sliders (and also because I make every argument adjustable) but for scatter for example looking at https://github.com/ianhi/mpl-interactions/blob/0517177b687722f8d4ecf9fd89910986be3377b3/mpl_interactions/pyplot.py#L530 you can see that the steps are:

scatter = ax.scatter(x, y)

scatter.set_offsets(np.column_stack([new_x, new_y]))

and then some trickiness with a helper function for updating the axis limits automatically (though there are simpler ways)

Actually I'll bet that it wouldn't be too difficult to use mpl-interactions as an engine for updating plots in cases like this. Just would need to make a widget that doesn't get rendered anywhere and update it's value. But that's probably best discussed not on this issue

@ianhi
Copy link
Collaborator

ianhi commented Feb 4, 2022

Complete example of using mpl-interactions to auto update plots in a loop all in one cell can be found here: mpl-extensions/mpl-interactions#234

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants