Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix clearing subfigures #22138

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
Mar 30, 2022
Merged

Fix clearing subfigures #22138

merged 1 commit into from
Mar 30, 2022

Conversation

stanleyjs
Copy link
Contributor

@stanleyjs stanleyjs commented Jan 7, 2022

PR Summary

Closes #22137

Figure.clf() and Figure.clear() were broken
when Figure contained axes not stored in Figure._localaxes. # Moved the
clear and clf methods to FigureBase, so Subfigures can now be
independently cleared. Added a routine to clear Figure.subfigs when
Figure.clear() is called. This effects the API as it removes active
subfigure references when clearing a figure.

This scenario occurred when Figure was instantiated with Subfigures.
The bug was thrown in a call to _localaxes.remove in FigureBase.delaxes.
The children Subfigures do not register their axes in _localaxes,
which meant that the _localaxes are empty during clear, leading to the bug.
This was fixed first by moving the .clear and .clf
methods into FigureBase, so that Subfigure would inherit these methods.
This allowed for Subfigure to clear its own axes, but because each child
Subfigure shares its _axstack with its parent Figure, the clf
function had to be modified: an extra call to _axstack.clear was removed.
This call had no effect on normal operation, as the stack is previously cleared
in clf by iterating over the _localaxes returned by FigureBase.axes;
however, in the case of Subfigure.clear, _axstack.clear cleared
the entire _axstack of the parent, thus wiping all axes independent of their
parent subfigure/figure. With these two changes (clf/clear inheritance and
removing the call to _axstack), subfigures can now be cleared.
However, the initial bug remained. To fix the bug in the clearing of the parent figure,
an additional loop was added to the beginning of clf. This loop recurses through
child Subfigures,clearing all children before returning to the original function.
As the child axes, which live in _axstack but not _localaxes, are no longer in
Figure.axes, the remainder of clf functions as before, clearing out
Figure.axes without risk of calling delaxes on an axis that is not contained in
_localaxes. Finally, to make FigureBase.clf an exhaustive function that truely removes
all references to child objects, a line was added to clear Figure.subfigs of any
unlinked, cleared subfigures.

PR Checklist

Tests and Styling

  • [ x] Has pytest style unit tests (and pytest passes).
  • [x ] Is Flake 8 compliant (install flake8-docstrings and run flake8 --docstring-convention=all).

Documentation

  • [ x] New features are documented, with examples if plot related.
  • [ x] New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • [ x] API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).
  • [ x] Documentation is sphinx and numpydoc compliant (the docs should build without error).

Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

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

Mostly seems good. However, I don't think clearing a subfigure should remove the subfigure (any more than clearing the top-level figure closes it).

@stanleyjs stanleyjs requested a review from jklymak January 7, 2022 14:50
@stanleyjs
Copy link
Contributor Author

Okay @jklymak thanks for your review, I have responded and fixed everything. The only concern I have is the comment about whether or not the list of subfigures should be entirely cleared in the parent when the parent calls clear. As I mentioned, I am concerned about any callbacks when a GUI widget is tracking an axis in the subfigures.

@jklymak
Copy link
Member

jklymak commented Jan 7, 2022

I would expect fig.clear to remove all children. If there are callbacks that I expected to stay alive, I shouldn't clear the whole figure?

@stanleyjs
Copy link
Contributor Author

Sounds fine to me @jklymak. The latest commit follows this convention.

@jklymak jklymak changed the title Figure of subfigures clearing #22137 and individual subfigure clearing Clearing subfigures Jan 7, 2022
@jklymak
Copy link
Member

jklymak commented Jan 7, 2022

/home/circleci/project/doc/api/next_api_changes/behavior/22135-JSS3.rst:2: WARNING: Title underline too short. ;-)

@stanleyjs
Copy link
Contributor Author

Wow. Fixed.

@jklymak what tool checks for RST style?

@jklymak
Copy link
Member

jklymak commented Jan 7, 2022

Our docs fail to build if there are warnings.

@tacaswell tacaswell added this to the v3.5.2 milestone Jan 7, 2022
@@ -688,6 +688,86 @@ def test_removed_axis():
fig.canvas.draw()


def test_figure_clear():
Copy link
Member

Choose a reason for hiding this comment

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

I have a mild preference to split each of these cases into its own test, but maybe that you can keep re-using the same figure is part of the test?

Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

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

Left a minor comment about the test, but not willing to hold merging on that.

@tacaswell
Copy link
Member

@stanleyjs Would you mind also squashing this to one commit (or we can do that on merge)?

@jklymak
Copy link
Member

jklymak commented Jan 8, 2022

doc build is still unhappy...

@jklymak
Copy link
Member

jklymak commented Jan 20, 2022

Hi @stanleyjs unfortunately this needs a rebase whereby you will need to resolve the conflicts in figure.py. When you have done so and fixed the doc mistake, please ping me and I'll try an merge expeditiously. If you need assistance with a rebase, feel free to ask here or on gitter. Thanks!

@jklymak jklymak marked this pull request as draft January 20, 2022 08:29
@stanleyjs
Copy link
Contributor Author

Sorry, did not mean to let this stagnate. I'll look into this ASAP

@stanleyjs
Copy link
Contributor Author

@jklymak It did not appear to need an actual rebase, i was able to just resolve the merge conflicts. We'll see if it passes CI

@tacaswell
Copy link
Member

@stanleyjs We prefer to rebase rather than merge the main branch into the feature branches.

@stanleyjs
Copy link
Contributor Author

@tacaswell Very well. I definitely don't know how to rebase, but I guess there's a first time for everything

@stanleyjs
Copy link
Contributor Author

stanleyjs commented Jan 26, 2022

@tacaswell @jklymak Can you point me in a direction on how to optimally use rebase? Or can you rebase it yourself? I just rebased it once, then the branch was 200 commits behind, so I merged it and tried to rebase the merge and the previous rebase into a single commit again and got stuck manually merging 200+ commits. I aborted after awhile, as I could not tell what commit was important without manually verifying each change in main. I have no idea what to do.

@tacaswell
Copy link
Member

Using rebasing definitly takes a few times through to have it make sense! We have some documentation on rebasing at https://matplotlib.org/stable/devel/gitwash/development_workflow.html#rebasing-on-trunk and the best thing I have ever read about git is https://tom.preston-werner.com/2009/05/19/the-git-parable.html

When git suggests you merge the remote branch into yours when the push fails, it is giving you conservative but very bad advice! At that point the correct path is git push --force-with-lease remote branch_name.


The steps I did here:

  • went back to the commit before you merged master by clicking through the GH ui
  • eventually I got to https://github.com/tacaswell/matplotlib/tree/6e4b1d9c45c413691a84edf04227cad039c78e29 which let me use the GH ui to create a branch on github at that commit (which I called "restore")
  • I then checked out that branch locally
  • I rebased that branch onto origin/master
  • I had to manually resolve conflicts on 3 commits (dace9f4, 9dfcfd5, 1d5ba46). The first two were real conflicts that I think I resolved correctly, the third was whitespace. At each broken step git left me with the merge confilct markers. I use emacs + magit for this, but most IDEs have some sort of helper tool for dealing with this.
  • I checked out 47c4a23 and pushed that to my fork as subfigure-clear as a backup!
  • I force-pushed my rebased branch to your branch (I can do this because of the "let maintainers push to this branch" box which it ticked in your UI)

Hopefully these notes are more helpful than confusing!

It might still be worth using git rebase -i to squash this all down to one commit any way.

@tacaswell tacaswell force-pushed the subfigure-clear branch 2 times, most recently from 8a30509 to f26ff69 Compare March 6, 2022 03:54
tacaswell
tacaswell previously approved these changes Mar 6, 2022
@tacaswell
Copy link
Member

I'm not quite sure what happened here but somehow you had picked up changes on many many files. The diff from the current branch to cc71237 is:

diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index 7ef783265d..57ea70597f 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -922,17 +922,16 @@ default: %(va)s
         self.suppressComposite = None
         self.callbacks = cbook.CallbackRegistry()
 
+        # first clear the axes in any subfigures
         for subfig in self.subfigs:
-            # first clear the axes in any subfigures
             subfig.clf(keep_observers=keep_observers)
+        self.subfigs = []
 
         for ax in tuple(self.axes):  # Iterate over the copy.
             ax.cla()
             self.delaxes(ax)  # Remove ax from self._axstack.
-
         self._axstack = _AxesStack()
         self.artists = []
-        self.subfigs = []
         self.lines = []
         self.patches = []
         self.texts = []
@@ -2166,6 +2165,7 @@ class Figure(FigureBase):
             naxes=len(self.axes),
         )
 
+    @_api.make_keyword_only("3.6", "facecolor")
     def __init__(self,
                  figsize=None,
                  dpi=None,
@@ -2356,7 +2356,7 @@ class Figure(FigureBase):
         # figure...
         for ax in self.axes:
             if hasattr(ax, '_colorbar'):
-                # colorbars list themselvs as a colorbar.
+                # colorbars list themselves as a colorbar.
                 return False
         return True
 
@@ -2850,10 +2850,6 @@ class Figure(FigureBase):
         if toolbar is not None:
             toolbar.update()
 
-    def clear(self, keep_observers=False):
-        """Clear the figure -- synonym for `clf`."""
-        self.clf(keep_observers=keep_observers)
-        
     @_finalize_rasterization
     @allow_rasterization
     def draw(self, renderer):

I meant to remove the subclass clear (the one from the parent class will do and correctly get the right clf) and I moved the resetting of self.subfigs up a bit to make it easier to read (which is subjective).

Comment on lines 919 to 925
Set *keep_observers* to True if, for example,
a gui widget is tracking the Axes in the figure.
"""
Copy link
Member

Choose a reason for hiding this comment

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

Please use numpydoc for docstrings.

Co-authored-by: Elliott Sales de Andrade <[email protected]>
@tacaswell
Copy link
Member

I squashed down to one commit so we pass the PR cleanliness check (not add / remove a file).

@tacaswell tacaswell dismissed their stale review March 10, 2022 15:24

I did too much work on this PR so recuse my self from reviewing.

@QuLogic QuLogic changed the title Clearing subfigures Fix clearing subfigures Mar 15, 2022

self.stale = True

# synonym for `clf`."""
Copy link
Member

Choose a reason for hiding this comment

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

Extra trailing quotes.

Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

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

One naming issue here. clf means "clear last figure", but here we are using it to clear subfigures that were not necessarily the "last" in any meaningful sense. I would argue that FigureBase should call this FigureBase.clear and call subfig.clear(), and that Figure.clf should alias to Figure.clear. Otherwise, to me it is not clear if Subfig.clf should clear itself or clear the whole figure.

@@ -913,6 +913,45 @@ def _break_share_link(ax, grouper):
# Break link between any twinned axes
_break_share_link(ax, ax._twinned_axes)

def clf(self, keep_observers=False):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def clf(self, keep_observers=False):
def clear(self, keep_observers=False):


# first clear the axes in any subfigures
for subfig in self.subfigs:
subfig.clf(keep_observers=keep_observers)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
subfig.clf(keep_observers=keep_observers)
subfig.clear(keep_observers=keep_observers)

self.stale = True

# synonym for `clf`."""
clear = clf
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
clear = clf

self.delaxes(ax) # Remove ax from self._axstack.

# docstring inherited
super().clf(keep_observers=keep_observers)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
super().clf(keep_observers=keep_observers)
super().clear(keep_observers=keep_observers)


def clear(self, keep_observers=False):
"""Clear the figure -- synonym for `clf`."""
self.clf(keep_observers=keep_observers)

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def clear(self, keep_observers=False):
"""Clear the figure -- synonym for `clf`."""
self.clf(keep_observers=keep_observers)

@tacaswell
Copy link
Member

One naming issue here. clf means "clear last figure",

I have always read this as "CLear Figure" / "CLear Axes", but that aside, Figures have clf and if we want SubFigures to be drop-ins for Figures then SubFigures also need clf, but I'm on board with flipping the usage and alias direction to prefer clear.

@jklymak
Copy link
Member

jklymak commented Mar 30, 2022

Oh, hmmm, well I looked up Matlab's docs and they just say "clear figure", so maybe you are right, and indeed clf takes a figure argument in Matlab, so I guess I am incorrect.

@jklymak jklymak merged commit a395083 into matplotlib:main Mar 30, 2022
@lumberbot-app
Copy link

lumberbot-app bot commented Mar 30, 2022

Owee, I'm MrMeeseeks, Look at me.

There seem to be a conflict, please backport manually. Here are approximate instructions:

  1. Checkout backport branch and update it.
git checkout v3.5.x
git pull
  1. Cherry pick the first parent branch of the this PR on top of the older branch:
git cherry-pick -x -m1 a395083238625500dd3fa879e4976617d5353342
  1. You will likely have some merge/cherry-pick conflict here, fix them and commit:
git commit -am 'Backport PR #22138: Fix clearing subfigures'
  1. Push to a named branch:
git push YOURFORK v3.5.x:auto-backport-of-pr-22138-on-v3.5.x
  1. Create a PR against branch v3.5.x, I would have named this PR:

"Backport PR #22138 on branch v3.5.x (Fix clearing subfigures)"

And apply the correct labels and milestones.

Congratulations — you did some good work! Hopefully your backport PR will be tested by the continuous integration and merged soon!

Remember to remove the Still Needs Manual Backport label once the PR gets merged.

If these instructions are inaccurate, feel free to suggest an improvement.

@tacaswell
Copy link
Member

I had a commit (not pushed) to fix the naming. I'll make a new PR.

tacaswell pushed a commit to tacaswell/matplotlib that referenced this pull request Mar 31, 2022
Fix clearing subfigures

(cherry picked from commit a395083)
tacaswell pushed a commit to tacaswell/matplotlib that referenced this pull request Mar 31, 2022
Merge pull request matplotlib#22138 from stanleyjs/subfigure-clear

Fix clearing subfigures

(cherry picked from commit a395083)
tacaswell pushed a commit to tacaswell/matplotlib that referenced this pull request Apr 5, 2022
Merge pull request matplotlib#22138 from stanleyjs/subfigure-clear

Fix clearing subfigures

(cherry picked from commit a395083)
tacaswell pushed a commit to tacaswell/matplotlib that referenced this pull request Apr 7, 2022
Merge pull request matplotlib#22138 from stanleyjs/subfigure-clear

Fix clearing subfigures

(cherry picked from commit a395083)
QuLogic added a commit that referenced this pull request Apr 8, 2022
…-v3.5.x

Backport PR #22138: Fix clearing subfigures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: Cannot clear figure of subfigures
5 participants