Repository navigation
Description
Problem
Often, I will create figures using matplotlib using the Figure class explicitly, because either I want to make a figure and repeatedly tweak it to see how it looks, or I am creating a lot of figures and don't need / want to deal with the pyplot interface. However, as far as I can tell, there is no native matplotlib way to show those figures since they weren't created with the pyplot interface.
An example of what I would like to do:
fig =. Figure()
ax = fig.subplots()
ax.plot([1, 2, 3, 4])
fig.show() # This gives an error (below)
Error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/jordan.e/opt/miniconda3/lib/python3.7/site-packages/matplotlib/figure.py", line 408, in show
"Figure.show works only for figures managed by pyplot, "
AttributeError: Figure.show works only for figures managed by pyplot, normally created by pyplot.figure()
The reason I don't want to use plt.subplots() followed by plt.show(), is, as mentioned above, because I often want to close and re-show the same figure. It seems really odd to me that matplotlib has a way of creating figures via a pure-OOP interface, but no easy way to display them. This is very frustrating and basically leads me to either repeatedly re-create the same plot with the pyplot interface instead of just tweaking the one I already have. I wrote a very hacky method for showing created figures based on a StackOverflow answer to this question which is below, and does not work reliably (the figure doubles in size every time I show it):
def show_figure(fig):
"""
create a dummy figure and use its manager to display "fig"
"""
dummy = plt.figure()
new_manager = dummy.canvas.manager
new_manager.canvas.figure = fig
fig.set_canvas(new_manager.canvas)
plt.show()
Proposed Solution
Either support fig.show() directly for figures created using the OOP-interface, or provide a method like pyplot.show_figure() which takes as an argument an existing figure and prints it to the screen (doing the exact same thing that a plt.plot() followed by plt.show() would do)
Activity
anntzer commentedon Apr 14, 2021
See #14024, perhaps.
As a side point, note that while I wholeheartedly agree with the idea of miniziming interaction with the pyplot global state, once you require a notion of showing a figure, there's necessarily going to be an GUI event loop running underneath, and that's necessarily a singleton (due to how GUI toolkits are written), which on Matplotlib's side is managed in pyplot (unless you want to create a QApplication/wx.App/etc. yourself).
edmundsj commentedon Apr 14, 2021
OK, then I'd be totally fine with something like
pyplot.show_figure(fig)tacaswell commentedon Apr 15, 2021
I have some worries about making
work. The first is that if you create an empty figure it has an
Aggcanvas (not the canvas that would be picked bypyplotvia the backend selection), soplt.show(fig)would have to "promote" the canvas to be the correct GUI-flavored backend. A related issue is that as part ofplt.figurewe create theManagerwhich wraps theCanvaswidget is a full GUI main window + adds a toolbar and a bunch of key bindings. Presumably we would also want to do that wrapping an promotion? However, if we did that once, what should we do on subsequent calls? What should we do if the user doesplt.show(plt.figure())? Using this method you can get a figure with a canvas that is from a different GUI toolkit than pyplot is set up to us, what should we do then?If you want to do the "promation" your self you can do
and then you will be able to
fig.show()and if you are inplt.ion()mode then you can pan/zoom. However, because you will either not have registered it with pyplot and/or not set up the stale callbacks you will not get auto-redraws (and will have to callfig.canvas.draw_idle()when ever you change the figure).I'm more inclined to go with something @anntzer suggested a while ago which is to add a
pyplot.adopt(fig)function or that does all of the wrapping / registering / book keeping to let pyplot know about your figure and then takes responsibility for it.At the end of the day, once you show a figure on screen you have converted your innocent Python script into a full-blown GUI application! The pyplot machinery does a remarkably good job at hiding this from most users on one hand and on the other we give users the tools to embed Matplotlib in a full-blown GUI of their design. I am not fully sold that there is much space in the middle where we can continue to hide the details of there being a GUI application from the user and let the users out from the oversight of pyplot.
jklymak commentedon Apr 15, 2021
Thanks @tacaswell. We should pin this answer.
anntzer commentedon Apr 15, 2021
This is not correct,
Figure().canvasis initially of typeFigureCanvasBase.It would seem reasonable to promote
FigureCanvasBaseto the canvas type specified byrcParams["backend"]; if there's already a concrete canvas subclass installed we can check itsrequired_interactive_frameworkand accept it as is if it is compatible and throw if it isn't. What to dofig.canvas.required_interactive_framework is None(i.e. a non-interactive canvas (but not FigureCanvasBase) is installed) is for us to decide, I guess throwing is cleaner but just swapping it forrcParams["backend"]seems ok too.Also, while users can do whatever they want (...), I think people passing Qt canvases to a Gtk event loop is quite rare... and again we can just explode in that case.
Ditto.
Again, we can check based on
required_interactive_framework.That's fine too, but note that here again you have to decide what to do if a noncompatible (or noninteractive) canvas is already installed.
tacaswell commentedon Apr 15, 2021
This is like the third time I have gotten that wrong 😞 🐑 . I'll remember it correctly one of these days...
fair, but feels less odd to me to do that promotion / error checking / ... as a one-off "please take ownership of this figure" rather than "please show and maybe adopt this figure".
There is also the issue that if you are mixing
plt.figureandFigure. When you doplt.show(fig)then do we expect any existing pyplot-managed figures to be shown? If you make two figures viaFigure, do we need to supportplt.show([fig1, fig2])orplt.show(fig1, fig2, ...)?We also recently made
block=Truekeyword only because we did have users who were doingplt.show(fig)which because figures are True-ishbool(fig) == Trueit was blocking (but led to weird behavior). I'm not super stoked about bringing that back.@edmundsj I want to stress that despite my verbose and relatively skeptical/negative responses this is a very reasonable request.
anntzer commentedon Apr 15, 2021
Actually I made
blockkwonly specifically to later supportplt.show([fig1, ...]). We clearly need to be able to take a list of figures (I'd prefer that over varargs figures); whether we want to support a single figure (rather than a list of 1 figure) is not particularly important (I guess it'd be nice if you have a bunch ofplt.show(fig, block=False)calls spread through many places and a finalplt.show([], block=True)to prevent immediate exiting).I'd say that if you explicitly pass figures to be shown, then pyplot figures don't get shown, but again that's something that just needs a decision.
jklymak commentedon Apr 15, 2021
Maybe we should discuss this on the call. We seem to have a tension between keep-it-simple and adding more flexibility. My naive take is that
pyplotis our very simple GUI manager, and if a user wants something fancier they should spin up their own simple GUI manager.tacaswell commentedon Apr 15, 2021
I am slowly convincing myself that @anntzer is right, but still have doubts.
nschloe commentedon Jul 2, 2021
For the sake of comparison, pyvista does it like this:
As a library author, this is very convenient for me: I create a
Plotterobject that I return, and give the user full flexibility what she wants to do with it.p.show()is easy enough:The same works with mpl
figures forfig.savefig(), not forshow().I gather from the above discussion that some might rather want to see
plt.show(fig)instead offig.show(), but perhaps that's something to consider.github-actions commentedon Dec 11, 2023
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!
tacaswell commentedon Dec 11, 2023
This turned into https://github.com/matplotlib/mpl-gui
timhoffm commentedon Sep 15, 2025
Superseded by #29855.