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

Closing a figure from the notebook does not close the python figure #4

Open
jenshnielsen opened this issue Sep 1, 2016 · 15 comments
Open
Milestone

Comments

@jenshnielsen
Copy link
Member

This is essentially a regression of matplotlib/matplotlib#4841 which happened when the nbagg backend was converted to a widget.

To reproduce create a cell with a simple plot.

plt.plot(range(10))
plt.show()

re execute the cell and observe that the figure count goes up and memory consumption goes up too. If you continue doing this you will eventually have more than 20 open figures and matplotlib will print a warning even if you only have one figure displayed.

The same is true if you explicitly close the widget using the widget close cross on the left of the figure.

I have spent some time trying to figure out how to best fix this but don't really know how to best do this. This function
is meant to trigger a close message to the python side when the figure is removed from the DOM but no longer works.

The old no longer existing close button is also intended to send a close signal to the python layer but this is not hooked up to the widget close button.

matplotlib/matplotlib#6414 has some more related issues.

@jenshnielsen jenshnielsen changed the title Closing a figure Closing a figure from the notebook does not close the python figure Sep 1, 2016
@jenshnielsen
Copy link
Member Author

@SylvainCorlay Do you have any idea of how to hook this up to the widgets and send a close message to python when the widget is destroyed/closed from the notebook

@SylvainCorlay
Copy link
Member

@jenshnielsen we can't to the latter because the close button only destroys a view. There may be other views of the same widget. And even when there is no view, the widget model could be wired to other widget models in the front end. Essentially it is a cross-language reference counting problem.

@jenshnielsen
Copy link
Member Author

That makes sense.

There is is certain mismatch between the Matplotlib APIs which and designed around GUI frameworks where there is a 1 to 1 relation ship between a figure window and it's python representation and the widgets. We will have to think more about how to handle this

@SylvainCorlay
Copy link
Member

We could close the figure when the jupyter comm is closed, and reversely. That would be a sensible thing to do in my opinion.

@jasongrout
Copy link
Contributor

As @jkrumbiegel points out at jupyterlab/jupyterlab#5373, it can also be confusing to get this error by just evaluating the cell creating the figure over and over again.

@gsaurabhr
Copy link

Any update on this? This issue still persists...

@SylvainCorlay
Copy link
Member

@gsaurabhr as per comment above, this is somewhat by design:

@jenshnielsen we can't to the latter because the close button only destroys a view. There may be other views of the same widget. And even when there is no view, the widget model could be wired to other widget models in the front end. Essentially it is a cross-language reference counting problem.

This gets more complicated if you account from multiple notebooks / front-end connected to the kernels.

@ipcoder
Copy link

ipcoder commented Aug 1, 2020

Since this problem seems to be around, can we provide at least a practically useful solution?
Here are some suggestions:

  1. Allow an option to switch into a mode in which when closing a widget the figure is closed as well.
  2. Add "close" button which will close the widget AND the figure
  3. Add a "freeze" button which will close the widget, the figure but leave the image like in "inline" mode
  4. Add something like freeze_figure() function to do that programmatically.

I would say 1 should be implemented at first priority for multiple reasons:

  • its safe, that is by default changes nothing from what it is now
  • addresses directly the root cause of the problem:

There is is certain mismatch between the Matplotlib APIs which and designed around GUI frameworks where there is a 1 to 1 relation ship between a figure window and it's python representation and the widgets.

2 & 3 would be very handy for daily work with notebooks.

@EricGallimore
Copy link

Would it be possible to eliminate the cross-language counting problem by adding a magic directive that allows a user to specify that there will only be one view of the widget?

That way, it doesn't break any of the more complicated use cases. For the the 99% of cases where only one view of a figure is used, it solves this issue.

@BrenBarn
Copy link

BrenBarn commented Sep 8, 2020

Would it be possible to eliminate the cross-language counting problem by adding a magic directive that allows a user to specify that there will only be one view of the widget?

I agree this would be a good thing to look into. And ideally also have a super-magic directive that says "all of my ipympl widgets will always ever have only one view each".

It's great that the notebook and matplotlib support all these complicated use cases but 90% of the time you're just plotting each plot in one cell and you want it to work. We shouldn't let that overwhelmingly prevalent use case become blocked by the possibility of obscure cases where someone's trying to re-use a widget in multiple places.

@martinRenou
Copy link
Member

This is an open-source project and everyone is welcome to contribute. Maybe you would like to open a Pull Request @ReblochonMasque? I would be very happy to merge it.

@martinRenou
Copy link
Member

There is this open PR here: #176

We discussed potential solutions there.

@trchudley
Copy link

I'd like to return to this and say this feature (or a toggle-able option to enable it) is something I'd very much appreciate on a user side. I often iterate plots visualising large arrays or images, and since moving from %matplotlib notebook to %matplotlib widget this has frequently becomes unwieldy when old figures are stored in-memory. Appending plt.close() to the top of a cell is an interim solution, but not always desired when you're also jumping between different figures in a notebook.

I can follow this thread from here to #176 to #343, but slowly these solutions stop addressing the problem @jenshnielsen raised originally (although a stop button as per #176 would also be appreciated to help me manage figure memory!). I've seen discussion touch upon the topic in #60 (here) #171 (here) but nothing seems to have come of it.

Has there been a conclusive decision to address or not address this issue that I have missed in my search? If not, please take this as another request from an end-user to provide an option to address this issue!

@FeldrinH
Copy link

Old figures remaining active when re-executing the cell is particularly problematic when trying to write helper functions.
Calling plt.close() or reusing the same figure might be workable in top level code. However, when trying to write a general purpose helper function that needs to create figures, it quickly becomes impossible to avoid situations where previous figures disappear, get overwritten or stop responding.

If there was some way to automatically close figures that are no longer visible then that would be a huge help.

@tovrstra
Copy link

For those who are struggling with this and want a temporary workaround to get something in line with %matplotlib inline, you can do the following:

plt.close("spam")
fig, ax = plt.subplots(num="spam")
x = np.linspace(-np.pi, np.pi, 101)
ax.plot(x, np.sin(x))

This is clumsy, I know, but it is predictable and robust.

One may argue that plt.close can be replaced by adding clear=True to plt.subplots and adding a line plt.show() in the end. However, that is not quite as robust: changes to Figure arguments of plt.subplots, like figsize, will then be ignored upon a second execution of the cell because the previous figure still exists and is not recreated with clear=True.

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