-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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
Fix clearing subfigures #22138
Conversation
There was a problem hiding this 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).
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. |
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? |
Sounds fine to me @jklymak. The latest commit follows this convention. |
|
Wow. Fixed. @jklymak what tool checks for RST style? |
Our docs fail to build if there are warnings. |
@@ -688,6 +688,86 @@ def test_removed_axis(): | |||
fig.canvas.draw() | |||
|
|||
|
|||
def test_figure_clear(): |
There was a problem hiding this comment.
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?
There was a problem hiding this 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.
@stanleyjs Would you mind also squashing this to one commit (or we can do that on merge)? |
doc build is still unhappy... |
Hi @stanleyjs unfortunately this needs a rebase whereby you will need to resolve the conflicts in |
Sorry, did not mean to let this stagnate. I'll look into this ASAP |
@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 |
@stanleyjs We prefer to rebase rather than merge the main branch into the feature branches. |
25778c6
to
df85ec0
Compare
@tacaswell Very well. I definitely don't know how to rebase, but I guess there's a first time for everything |
@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. |
47c4a23
to
3d4c901
Compare
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 The steps I did here:
Hopefully these notes are more helpful than confusing! It might still be worth using |
0e01b31
to
cc71237
Compare
8a30509
to
f26ff69
Compare
2309392
to
188a46d
Compare
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 |
lib/matplotlib/figure.py
Outdated
Set *keep_observers* to True if, for example, | ||
a gui widget is tracking the Axes in the figure. | ||
""" |
There was a problem hiding this comment.
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]>
9979143
to
5fe0e9d
Compare
I squashed down to one commit so we pass the PR cleanliness check (not add / remove a file). |
I did too much work on this PR so recuse my self from reviewing.
|
||
self.stale = True | ||
|
||
# synonym for `clf`.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra trailing quotes.
There was a problem hiding this 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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
subfig.clf(keep_observers=keep_observers) | |
subfig.clear(keep_observers=keep_observers) |
self.stale = True | ||
|
||
# synonym for `clf`.""" | ||
clear = clf |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clear = clf |
self.delaxes(ax) # Remove ax from self._axstack. | ||
|
||
# docstring inherited | ||
super().clf(keep_observers=keep_observers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def clear(self, keep_observers=False): | |
"""Clear the figure -- synonym for `clf`.""" | |
self.clf(keep_observers=keep_observers) |
I have always read this as "CLear Figure" / "CLear Axes", but that aside, |
Oh, hmmm, well I looked up Matlab's docs and they just say "clear figure", so maybe you are right, and indeed |
Owee, I'm MrMeeseeks, Look at me. There seem to be a conflict, please backport manually. Here are approximate instructions:
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 If these instructions are inaccurate, feel free to suggest an improvement. |
I had a commit (not pushed) to fix the naming. I'll make a new PR. |
Fix clearing subfigures (cherry picked from commit a395083)
Merge pull request matplotlib#22138 from stanleyjs/subfigure-clear Fix clearing subfigures (cherry picked from commit a395083)
Merge pull request matplotlib#22138 from stanleyjs/subfigure-clear Fix clearing subfigures (cherry picked from commit a395083)
Merge pull request matplotlib#22138 from stanleyjs/subfigure-clear Fix clearing subfigures (cherry picked from commit a395083)
…-v3.5.x Backport PR #22138: Fix clearing subfigures
PR Summary
Closes #22137
Figure.clf()
andFigure.clear()
were brokenwhen
Figure
contained axes not stored inFigure._localaxes
. # Moved theclear
andclf
methods toFigureBase
, soSubfigure
s can now beindependently cleared. Added a routine to clear
Figure.subfigs
whenFigure.clear()
is called. This effects the API as it removes activesubfigure references when clearing a figure.
This scenario occurred when
Figure
was instantiated withSubfigure
s.The bug was thrown in a call to
_localaxes.remove
inFigureBase.delaxes
.The children
Subfigure
s 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 thatSubfigure
would inherit these methods.This allowed for
Subfigure
to clear its own axes, but because each childSubfigure
shares its_axstack
with its parentFigure
, theclf
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 byFigureBase.axes
;however, in the case of
Subfigure.clear
,_axstack.clear
clearedthe entire
_axstack
of the parent, thus wiping all axes independent of theirparent subfigure/figure. With these two changes (
clf/clear
inheritance andremoving 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 throughchild
Subfigures
,clearing all children before returning to the original function.As the child axes, which live in
_axstack
but not_localaxes
, are no longer inFigure.axes
, the remainder ofclf
functions as before, clearing outFigure.axes
without risk of callingdelaxes
on an axis that is not contained in_localaxes
. Finally, to makeFigureBase.clf
an exhaustive function that truely removesall references to child objects, a line was added to clear
Figure.subfigs
of anyunlinked, cleared subfigures.
PR Checklist
Tests and Styling
pytest
passes).flake8-docstrings
and runflake8 --docstring-convention=all
).Documentation
doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).