Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Simpler "pyplotless" use pattern.#14024

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
anntzer wants to merge1 commit intomatplotlib:mainfromanntzer:unpyplot

Conversation

anntzer
Copy link
Contributor

@anntzeranntzer commentedApr 23, 2019
edited
Loading

cf. mailing list discussion on garbage collection.

Right now, if one does not want to use pyplot (e.g., batch use), one can
do

from matplotlib.figure import Figurefig = Figure(); ... <plot> ...; fig.savefig(...)

but it is impossible to show() the figure interactively (i.e. pyplot
cannot "adopt" the figure) e.g. for debugging.

This patch makes it possible: with it, one can do

fig = plt.new_figure_manager(num).canvas.figure... <plot> ...fig.savefig()plt.show(figures=[fig])

Note that this doesnot register the figure with pyplot; in particular
fig is garbage-collected when it goes out of scope, does not
participate in gcf(); etc. So this is "pyplotless" in the sense that
there is no global figure registry anymore, but pyplot still stays in
charge of the GUI integration.

Obviously theplt.new_figure_manager(num).canvas.figure expression
could / should be encapsulated in a helper function, up to bikeshedding.

Thenum parameter is needed as matplotlib currently uses it to set the
figure title, but that can easily be made optional too.

Finally note thatplt.show([fig]) would be a nicer API, but right now
the first parameter to plt.show isblock, which is undergoing
transition to kwonly.

IOW the end-goal would be e.g.

# intentionally terrible name as placeholder :)fig = plt.new_figure_not_globally_registered("some title")... <plot> ...fig.savefig()plt.show([fig])

some more thoughts:
While this "logically" belongs to pyplot in the sense that pyplot is in charge of the GUI integration, this doesn't actually interact with any of the pyplot plotting functions that operate on gcf()/gca(), so perhaps it may be better (to avoid user confusion) to have a separate submodule just hostingnew_figure_not_globally_registered andshow (in which case the latter could have the "better" APIshow([fig], *, block=False) immediately).


Currently thenum argument to new_figure_manager needs to be an int, but note that plt.figure() supports both int and string and the logic to support both can easily be pushed down to new_figure_manager... but preferably after merging#13569 and#13581.

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code isFlake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

vnmabus, phsyron, konstin, edmundsj, and nschloe reacted with thumbs up emoji
@anntzeranntzer added this to thev3.2.0 milestoneApr 23, 2019
@jklymak
Copy link
Member

Huh, as a somewhat knowledgeable user, I had zero idea thatpyplot was in charge of GUI integration. When I first read the above I wasn't clear why we don't just have or addfig.show(), which I guess would mean we would need a way for figures to get their own manager.

So, I think that we can go ahead and try to disassociate the GUI integration from pyplot, or we can just accept that some basic things get done in pyplot that have nothing to do w/ thegcf integration.

@anntzer
Copy link
ContributorAuthor

The problem offig.show() is, how would that work if you want to show two figures? (basically you'd want the first show() to not block (so that you can reach the second show(), but the second one to block (so that the process doesn't terminate immediately). As far as I can see, having a single function that takes as parameter a list of figures to be shown is necessary to handle this (not so rare) case.

@jklymak
Copy link
Member

Forgetting about our current concept of "block", I'd expect you couldshow as many figures as you want and the process wouldn't end until you manually close all the figures.

@anntzer
Copy link
ContributorAuthor

So effectively figure.show() would behave like plt.show(block=False),except that the process does not end at the end of the program? I guess that can work, too.

@jklymak
Copy link
Member

So effectively figure.show() would behave like plt.show(block=False),except that the process does not end at the end of the program? I guess that can work, too.

Thats how I'd expect a GUI to behave, but no doubt I'm missing a subtlety somewhere...

@ImportanceOfBeingErnest
Copy link
Member

figure.show() doesn't start or run an event loop, whileplt.show() does (or in case ofion emulates one), right? So wouldplt.show([fig1]) start an event loop or not and would it be blocking in case ofioff?

Would

fig1 = plt.figure()fig2 = mpl.figure.Figure()plt.show([fig2])

be showing fig1 as well?

@anntzer
Copy link
ContributorAuthor

fig.show() can take care of spinning up an event loop if none is running yet, that's not really a problem.

Whether a call toshow() would implicitly also include all pyplot-generated figures is just a design choice that needs to be made, I would prefer not but either way is fine.

@ImportanceOfBeingErnest
Copy link
Member

I think we should in general prepare for a way to reshow closed pyplot figures. This is currently one of the most annoying restrictions users face, i.e.

fig, ax = plt.subplots()ax.plot(...)plt.show() # Figure is shown, User closes it.# At this point there is no way[*] to show the figure again.# Suggestion would be toplt.show(figures=[fig])
[*] "No way" in the sense of......you don't want to do this:
import matplotlib.pyplot as pltdef reshow(fig):    import importlib    import matplotlib.backends    import matplotlib.backend_bases    backend_mod = importlib.import_module(f"matplotlib.backends.backend_{plt.get_backend().lower()}")    Backend = type("Backend", (matplotlib.backends._Backend,), vars(backend_mod))    fm = Backend.new_figure_manager_given_figure(1, fig)    matplotlib.backend_bases.Gcf.set_active(fm)    plt.show()    fig1, ax1 = plt.subplots()ax1.plot([1,2], label="ABC")plt.show() #Now reshow the figurereshow(fig1)

@jklymak
Copy link
Member

I’m not understanding the situation where the user expects a figure to be able to be reopened. If I click the little red x I expect whatever was there to disappear. You could argue that it could prompt to save but otherwise?

@ImportanceOfBeingErnest
Copy link
Member

Sure, you could register a callback to aclose_event that pickles the figure before destroying the manager, such that if unpickled it can be shown again. That is equally cumbersome.

@jklymak
Copy link
Member

By "save", I meant saving as an image.

I'm 👎 on saving figures so they can be resurrected as an interactive thing later. At the most practical, such figures won't survive changes of matplotlib versions if any internal organization of the objects has changed. Pickle, itself, became incompatible between py2.x and py3.x (I'll never willingly use pickle again). Overall it just seems like a lot of hassle. And why? Users should be able to re-create the figure from their script and not rely on manual fiddling with the GUI or a bunch of manual typing in the REPL.

I admit this is not a universal opinion, but I think having figures that robustly resurrect themselves is really hard from a developers perspective, and not a good idea from the user's perspective.

@ImportanceOfBeingErnest
Copy link
Member

Matplotlib supports pickling of figures just fine (of course in- and output versions must be the same!) and there is a bunch of tests for that, so this is an existing feature, not for debate here. I only mentionned it as workaround for reshowing a figure. And I mentionned the desire for reshowing a figure here, because I think it needs to be taken into account when designingplt.show(figures).

jklymak reacted with thumbs up emoji

@anntzer
Copy link
ContributorAuthor

@ImportanceOfBeingErnest re-show()ing a figure basically just works with this PR.
Actually there's a bug whenclosing a qt figurefor the second time with a key shortcut ("q") because the private _destroying attribute is not cleared out, and I guess othermanagers may have similar issues, but on pyplot's side I think this works fine.

@ImportanceOfBeingErnest
Copy link
Member

I see. I was missing the fact that apparently closed figures keep their manager. But that wouldn't be the case for a pickled figure, right?

This is what happens
import pickleimport matplotlib.pyplot as pltfig, ax = plt.subplots()ax.plot([12, 13], label="ABCDE")plt.close(fig)with open('fig.pickle', 'wb') as f:    pickle.dump(fig, f)with open('fig.pickle', 'rb') as f:    fig1 = pickle.load(f)plt.show(figures=[fig1])

gives

Traceback (most recent call last):  File "untitled3.py", line 20, in <module>    plt.show(figures=[fig1])  File "d:\***\matplotlib\lib\matplotlib\pyplot.py", line 262, in show    return _show(*args, **kw)  File "d:\***\matplotlib\lib\matplotlib\cbook\deprecation.py", line 409, in wrapper    return func(*args, **kwargs)  File "d:\***\matplotlib\lib\matplotlib\backend_bases.py", line 3278, in show    if figures is not None else Gcf.get_all_fig_managers())  File "d:\***\matplotlib\lib\matplotlib\backend_bases.py", line 3277, in <listcomp>    managers = ([figure.canvas.manager for figure in figures]AttributeError: 'NoneType' object has no attribute 'manager'

Not sure if it should work though.

@anntzer
Copy link
ContributorAuthor

anntzer commentedSep 19, 2019
edited
Loading

A manager is (holding) a GUI widget, so it's going to be tricky to pickle :p
The relevant piece of code is basically at

ifgetattr(self.canvas,'manager',None) \
and
ifrestore_to_pylab:
-- note that the unpickler explicitly recreates a fresh manager if restore_to_pylab is set, and the pickler explicitly doesn't set it if the figure has been evicted from Gcf (as happens with close()).
Probably just a matter of replacing restore_to_pylab by a tristate for the behavior on unpickling:

  • don't create a manager
  • create a manager, but don't register with pyplot
  • create a manager, and register to pyplot and show the figure

but this can clearly be done separately from (after?) this PR.

@ImportanceOfBeingErnest
Copy link
Member

Yes pickling is again a bad example, I suppose. Let's just take

plt.show(figures=[plt.Figure()])

which will also not have a manager. If that is expected, maybe a better error message should tell people exactly which figures they can show this way.

@anntzer
Copy link
ContributorAuthor

anntzer commentedSep 19, 2019
edited
Loading

That should just be a matter of callingnew_figure_manager_given_figure(fig), which can be done automatically withinshow() for managerless figures. Well, that adopts the figure into Gcf so we need to take it out of Gcf (so that again it can get properly gc'd), but that's the idea. (But note that the original intent was for people to useplt.new_figure_not_globally_registered("title") (which would create a manager) instead of the Figure constructor directly -- but I guess we may as well avoid adding a new API).

@anntzeranntzer mentioned this pull requestOct 31, 2020
7 tasks
"""
Show all figures.

`show` blocks by calling `mainloop` if *block* is ``True``, or if it
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
`interactive` mode.
"""
managers = Gcf.get_all_fig_managers()
managers = ([figure.canvas.manager for figure in figures]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should be more forgiving to canvases without managers?

Copy link
ContributorAuthor

@anntzeranntzerNov 1, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't think ignoring (silently or not) figures we can't show because they have no manager is really helpful?

One idea would be to auto-setup a manager for them, though.

@tacaswell
Copy link
Member

I am 👍 on this.

@timhoffm
Copy link
Member

Semi OT: While I see the reason behind this approach,plt.show([fig]) is still a crutch, andfig.show() would be much more intuitive from a user perspective.

@anntzer
Copy link
ContributorAuthor

The problem offig.show() is that (per the above) it's not really clear whether we want that to default to blocking or not.

Right now, if one does not want to use pyplot (e.g., batch use), one cando    from matplotlib.figure import Figure    fig = Figure(); ... <plot> ...; fig.savefig(...)but it is impossible to show() the figure interactively (i.e. pyplotcannot "adopt" the figure) e.g. for debugging.This patch makes it possible: with it, one can do    fig = plt.new_figure_manager(num).canvas.figure    ... <plot> ...    fig.savefig()    plt.show(figures=[fig])Note that this does *not* register the figure with pyplot; in particular*fig* is garbage-collected when it goes out of scope, does notparticipate in gcf(); etc.  So this is "pyplotless" in the sense thatthere is no global figure registry anymore, but pyplot still stays incharge of the GUI integration.Obviously the `plt.new_figure_manager(num).canvas.figure` expressioncould / should be encapsulated in a helper function, up to bikeshedding.The `num` parameter is needed as matplotlib currently uses it to set thefigure title, but that can easily be made optional too.Finally note that `plt.show([fig])` would be a nicer API, but right nowthe first parameter to plt.show is `block`, which is undergoingtransition to kwonly.IOW the end-goal would be e.g.    # intentionally terrible name as placeholder :)    fig = plt.new_figure_not_globally_registered("some title")    ... <plot> ...    fig.savefig()    plt.show([fig])
@timhoffm
Copy link
Member

I know it's not that simple :sad:.

@QuLogicQuLogic modified the milestones:v3.4.0,v3.5.0Jan 21, 2021
@jklymakjklymak marked this pull request as draftApril 23, 2021 16:33
@QuLogicQuLogic modified the milestones:v3.5.0,v3.6.0Aug 23, 2021
@timhoffmtimhoffm modified the milestones:v3.6.0,unassignedApr 30, 2022
@story645story645 modified the milestones:unassigned,needs sortingOct 6, 2022
@github-actions
Copy link

Since this Pull Request has not been updated in 60 days, it has been marked "inactive." This does not mean that it will be closed, though it may be moved to a "Draft" state. This helps maintainers prioritize their reviewing efforts. You can pick the PR back up anytime - please ping us if you need a review or guidance to move the PR forward! If you do not plan on continuing the work, please let us know so that we can either find someone to take the PR over, or close it.

@github-actionsgithub-actionsbot added the status: inactiveMarked by the “Stale” Github Action labelJun 14, 2023
@jklymak
Copy link
Member

Mplgui basically solves this issue. Now we just need a path to merging it!

@github-actionsgithub-actionsbot removed the status: inactiveMarked by the “Stale” Github Action labelJun 16, 2023
@github-actions
Copy link

Since this Pull Request has not been updated in 60 days, it has been marked "inactive." This does not mean that it will be closed, though it may be moved to a "Draft" state. This helps maintainers prioritize their reviewing efforts. You can pick the PR back up anytime - please ping us if you need a review or guidance to move the PR forward! If you do not plan on continuing the work, please let us know so that we can either find someone to take the PR over, or close it.

@github-actionsgithub-actionsbot added the status: inactiveMarked by the “Stale” Github Action labelAug 16, 2023
@anntzer
Copy link
ContributorAuthor

This is basically tracked by the mplgui work.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@tacaswelltacaswelltacaswell left review comments

@timhoffmtimhoffmtimhoffm left review comments

Assignees
No one assigned
Labels
status: inactiveMarked by the “Stale” Github Actionstatus: needs comment/discussionneeds consensus on next steptopic: pyplot API
Projects
None yet
Milestone
future releases
Development

Successfully merging this pull request may close these issues.

7 participants
@anntzer@jklymak@ImportanceOfBeingErnest@tacaswell@timhoffm@QuLogic@story645

[8]ページ先頭

©2009-2025 Movatter.jp