Skip to content

Attach a FigureCanvasBase by default to Figures. #12450

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

Merged
merged 1 commit into from
Nov 11, 2018

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Oct 8, 2018

This is particularly useful for headless cases, where one just wants to
take advantage of print_figure, which is defined on the base class.
For example, one can now do

for <loop>:
    figure = Figure()
    # do some plotting
    figure.savefig(...)

and let figure get gc'd at the next loop iteration; a pyplot-based
solution would instead have to explicitly close() the figure or reuse it
after clf()'ing it.

Also simplifies various places in the codebase where we had to handle
the case of .canvas = None previously.

xref #11657, #12447.

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 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

@anntzer anntzer added this to the v3.1 milestone Oct 8, 2018
@timhoffm
Copy link
Member

timhoffm commented Oct 8, 2018

First thought: Does this involve any costly setup logic? We are already quite slow in creating figures.
Answer: Apparently not. -> Ok.

Second thought: Is FigureCanvasBase the right class? It's not used explicitly except in some tests. Should we maybe have

class PrintableFigureCanvas(FigureCanvasBase):
    pass

just for semantic reasons?
Maybe not. Not sure myself.

Third thought: Do we now get more strange behavior or error message when we try things that would before fail due to the missing canvas?

@anntzer
Copy link
Contributor Author

anntzer commented Oct 8, 2018

  1. I strongly doubt it matters performance-wise (the base class __init__ just sets a bunch of attributes), and I guess you actually did time it :)
  2. Seems overkill to me.
  3. Figure().canvas.draw() now does nothing instead of raising an AttributeError ("None has no attribute "draw"), but that's sort of the point of the PR... (I guess you could split out draw() in its own subclass of FigureCanvasBase but... let's not.)
    It shouldn't be too hard to revert this if we merge it early in the 3.1 cycle and it turns out to be problematic :)

@timhoffm
Copy link
Member

timhoffm commented Oct 8, 2018

and I guess you actually did time it :)

No just looked at the code 😄

  1. Ok.

It shouldn't be too hard to revert this if we merge it early in the 3.1 cycle and it turns out to be problematic :)

Doesn't sound well thought through. I haven't either, but I'm hesitant to introduce a code change with an effect that we do not overlook.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 8, 2018

Doesn't sound well thought through. I haven't either, but I'm hesitant to introduce a code change with an effect that we do not overlook.

I actually really think it's not going to cause any problems, but as usual things are tied up in so many knots in mpl that it's hard to know if we don't try.

"rotation disabled. Set canvas then call "
"Axes3D.mouse_init().")

self._cids = [
Copy link
Member

Choose a reason for hiding this comment

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

Concerned that if we are hitting this code path with the base canvas and then it gets replaced the subscriptions will not work.

On the other hand, you are no worse than you are now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I'll leave it as it is until someone complains :)

@tacaswell
Copy link
Member

This code was last touched in b0807ae which was primarily reverting #1125

The original self.canvas = None and the logic of the canvas setting its self to the Figure came in in 770d0a0

I suspect looking at why #1125 failed would be a good guide to if we are missing something here. From a very quick look, that PR defaulted to getting the current backend from pyplot (more-or-less) where as this is is going with the base class which has just enough logic to fall-back on save.

Another option is to make canvas a property and defer creating a canvas until someone asks for it, but I am not sure it is worth the complexity / runtime cost.

This is particularly useful for headless cases, where one just wants to
take advantage of `print_figure`, which is defined on the base class.
For example, one can now do

    for <loop>:
        figure = Figure()
        # do some plotting
        figure.savefig(...)

and let `figure` get gc'd at the next loop iteration; a pyplot-based
solution would instead have to explicitly close() the figure or reuse it
after clf()'ing it.

Also simplifies various places in the codebase where we had to handle
the case of `.canvas = None` previously.
@anntzer
Copy link
Contributor Author

anntzer commented Nov 4, 2018

Looking at #1457 (comment) it looks like #1125 failed because not all Canvas classes have the same constructor signature, but that's not a problem here.
I think #1457 (which was likewise rejected) failed because it also tried to construct a FigureManager by default (even if it doesn't use it, it's still present) to workaround that signature variation.

In both cases I think using FigureCanvasBase avoids these problems.

Copy link
Contributor

@dopplershift dopplershift left a comment

Choose a reason for hiding this comment

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

This seems reasonable to me, if for no other reason than the number of checks for None it removes.

@tacaswell tacaswell merged commit 8fd45c0 into matplotlib:master Nov 11, 2018
@anntzer anntzer deleted the defaultcanvas branch November 11, 2018 11:04
tacaswell added a commit to tacaswell/matplotlib that referenced this pull request Jan 12, 2020
In 380c531 / matplotlib#12450 we set the
default canvas on Figure `__init__` to FigureCanvasBase but were still
setting it to `None` in `__setstate__`.
tacaswell added a commit to tacaswell/matplotlib that referenced this pull request Jan 13, 2020
In 380c531 / matplotlib#12450 we set the
default canvas on Figure `__init__` to FigureCanvasBase but were still
setting it to `None` in `__setstate__`.
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

Successfully merging this pull request may close these issues.

5 participants