FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Native support for showing OOP-created figures · Issue #19956 · matplotlib/matplotlib · GitHub

Repository navigation

Native support for showing OOP-created figures #19956

Closed

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

  1. anntzer commented on Apr 14, 2021

    Contributor

    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).

  2. edmundsj commented on Apr 14, 2021

    Author

    OK, then I'd be totally fine with something like pyplot.show_figure(fig)

  3. tacaswell commented on Apr 15, 2021

    Member

    I have some worries about making

    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    fig = Figure()
    plt.show(fig)

    work. The first is that if you create an empty figure it has an Agg canvas (not the canvas that would be picked by pyplot via the backend selection), so plt.show(fig) would have to "promote" the canvas to be the correct GUI-flavored backend. A related issue is that as part of plt.figure we create the Manager which wraps the Canvas widget 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 does plt.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

    m = plt.new_figure_manager_given_figure(0, fig)

    and then you will be able to fig.show() and if you are in plt.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 call fig.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.

  4. jklymak commented on Apr 15, 2021

    Member

    Thanks @tacaswell. We should pin this answer.

  5. anntzer commented on Apr 15, 2021

    Contributor

    The first is that if you create an empty figure it has an Agg canvas (not the canvas that would be picked by pyplot via the backend selection)

    This is not correct, Figure().canvas is initially of type FigureCanvasBase.

    so plt.show(fig) would have to "promote" the canvas to be the correct GUI-flavored backend. A related issue is that as part of plt.figure we create the Manager which wraps the Canvas widget 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?

    It would seem reasonable to promote FigureCanvasBase to the canvas type specified by rcParams["backend"]; if there's already a concrete canvas subclass installed we can check its required_interactive_framework and accept it as is if it is compatible and throw if it isn't. What to do fig.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 for rcParams["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.

    What should we do if the user does plt.show(plt.figure())?

    Ditto.

    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?

    Again, we can check based on required_interactive_framework.

    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.

    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.

  6. tacaswell commented on Apr 15, 2021

    Member

    This is not correct, Figure().canvas is initially of type FigureCanvasBase.

    This is like the third time I have gotten that wrong 😞 🐑 . I'll remember it correctly one of these days...

    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.

    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.figure and Figure. When you do plt.show(fig) then do we expect any existing pyplot-managed figures to be shown? If you make two figures via Figure, do we need to support plt.show([fig1, fig2]) or plt.show(fig1, fig2, ...)?

    We also recently made block=True keyword only because we did have users who were doing plt.show(fig) which because figures are True-ish bool(fig) == True it 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.

  7. anntzer commented on Apr 15, 2021

    Contributor

    Actually I made block kwonly specifically to later support plt.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 of plt.show(fig, block=False) calls spread through many places and a final plt.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.

  8. jklymak commented on Apr 15, 2021

    Member

    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 pyplot is our very simple GUI manager, and if a user wants something fancier they should spin up their own simple GUI manager.

  9. tacaswell commented on Apr 15, 2021

    Member

    I am slowly convincing myself that @anntzer is right, but still have doubts.

  10. nschloe commented on Jul 2, 2021

    Contributor

    For the sake of comparison, pyvista does it like this:

    import pyvista as pv
    
    p = pv.Plotter()
    cyl = pv.Cylinder()
    p.add_mesh(cyl)
    p.show()

    As a library author, this is very convenient for me: I create a Plotter object that I return, and give the user full flexibility what she wants to do with it. p.show() is easy enough:

    def fun_from_my_library():
        import pyvista as pv
        p = pv.Plotter()
        # generate a fancy plot
        return p
    
    fun_from_my_library().show()
    # or
    fun_from_my_library().save("out.png")
    # or ...

    The same works with mpl figures for fig.savefig(), not for show().

    I gather from the above discussion that some might rather want to see
    plt.show(fig) instead of fig.show(), but perhaps that's something to consider.

  11. github-actions commented on 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!

  12. tacaswell commented on Dec 11, 2023

    Member
  13. timhoffm commented on Sep 15, 2025

    Member

    Superseded by #29855.

  14. added this to the v3.11.0 milestone on Sep 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL