Skip to content

Only clear Axis once when creating an Axes #26164

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 3 commits into from
Jul 7, 2023

Conversation

oscargus
Copy link
Member

@oscargus oscargus commented Jun 21, 2023

PR summary

Helps with #26162

Speed up the Axes creation. Reduces the number of calls to Axis.clear from six to one per axis leading to about 20% reduction for a 10 x 10 subplot.

Seems like this (or some other change) enables registering polar and geo axis to spines.

PR checklist

@oscargus oscargus force-pushed the noclearonregister branch 4 times, most recently from a15b0ec to 8107d05 Compare June 21, 2023 18:02
@jklymak
Copy link
Member

jklymak commented Jun 21, 2023

This breaks polar unfortunately, but maybe thats easy to fix?

@oscargus oscargus force-pushed the noclearonregister branch from 8107d05 to 01822a2 Compare June 21, 2023 19:03
@oscargus
Copy link
Member Author

oscargus commented Jun 21, 2023

Seems like registering the axis fixes it locally (except one test where the PDF doesn't pass, but all other formats...).

I honestly cannot spot the difference when switching between the two PDFs, but apparently three pixels are different in the PNG version of the PDF...

@oscargus oscargus force-pushed the noclearonregister branch 4 times, most recently from f07c0b0 to 2aa5cdd Compare June 21, 2023 19:42
@oscargus
Copy link
Member Author

The next thing is probably to skip using set for Tick and call the setters directly and/or set the attributes directly. Creating ticks is about 40% of the time right now.

But that will not go in this PR.

@oscargus oscargus force-pushed the noclearonregister branch from 2aa5cdd to 1652026 Compare June 21, 2023 20:55
@oscargus oscargus marked this pull request as ready for review June 21, 2023 21:43
+ self.transAxes)
# Must have a dummy spine for 'inner'
inner_spine = Spine.linear_spine(self, 'bottom')
return {'polar': polar_spine, 'inner': inner_spine}
Copy link
Member

Choose a reason for hiding this comment

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

Is requiring this extra 'inner' something other axes are going to need? eg, maybe check this works w/ cartopy?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'm not happy with this consequence.

I think a better solution is to split Spline.clear into two, so that one can avoid clearing the registered axis. In this way there will only be one clear per axis and this can be avoided.

It still opens the question as to whether the spines should have their Axis registered (as that now seems possible without any test failures).

Copy link
Member

Choose a reason for hiding this comment

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

FWIW I just ran the Cartopy tests against this locally and they seem fine to me.

== 1 failed, 790 passed, 24 skipped, 2 xfailed, 2 xpassed, 8585 warnings in 84.52s (0:01:24) ==

(the one failure is related to #25162 I think)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks!

That probably means that we can still register the Axis on the Spines, but it would still be better to only clear the Axis once.

@oscargus oscargus marked this pull request as draft June 22, 2023 07:57
@oscargus oscargus force-pushed the noclearonregister branch from 1652026 to 0c072a5 Compare June 22, 2023 11:49
@oscargus
Copy link
Member Author

Now this only clears the Axis once. Thought it made sense to create a separate method for clearing the "spine-part" of the spine, despite only a single statement in it. Better readability and easier to expand.

Now, the Axis is registered to the spine for polar and geo, although it is from clear to me what this spine dict actually does and how it is documented (and how that differs between Axes-classes).

@oscargus oscargus marked this pull request as ready for review June 22, 2023 12:15
@oscargus oscargus changed the title Do not clear Axis when registering spine Only clear Axis once when creating an Axes Jun 22, 2023
@oscargus oscargus force-pushed the noclearonregister branch from 0c072a5 to 651ba19 Compare June 22, 2023 13:18
@@ -2619,7 +2625,8 @@ def set_ticks_position(self, position):
self.set_tick_params(which='both', right=True, labelright=False,
left=True, labelleft=True)
else:
assert False, "unhandled parameter not caught by _check_in_list"
_api.check_in_list(['left', 'right', 'both', 'default', 'none'],
position=position)
Copy link
Member Author

Choose a reason for hiding this comment

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

This halves the number of calls to check_in_list, from ~100.000 to ~50.000 in the example. For cases like this, where it is called from a commonly used __init__-method it is probably better to have it last. Although, in general error checking may be better off early.

@@ -209,7 +214,8 @@ def _apply_tickdir(self, tickdir):
# further updates ticklabel positions using the new pads.
if tickdir is None:
tickdir = mpl.rcParams[f'{self.__name__}.direction']
_api.check_in_list(['in', 'out', 'inout'], tickdir=tickdir)
else:
_api.check_in_list(['in', 'out', 'inout'], tickdir=tickdir)
Copy link
Member Author

Choose a reason for hiding this comment

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

The rcParam is already validated.

if m is None:
m = "default"
else:
_api.check_in_list(("anchor", "default"), rotation_mode=m)
Copy link
Member Author

Choose a reason for hiding this comment

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

If this change is OK one should probably clarify that setting None will return "default" in the doc-string.

@@ -674,7 +678,12 @@ def __init__(self, axes, *, pickradius=15):
self._major_tick_kw = dict()
self._minor_tick_kw = dict()

self.clear()
if clear:
self.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 guess I thought clear did a lot more than this.

I think that the doctoring should maybe clarify what the heck "clear on creation" means, or why one would or would not want to do it. I'm pretty surprised that everything "works" with just setting the convert and units to "None". If that is all robust (and tests seem to pass!) that is really a great simplification.

Copy link
Member Author

Choose a reason for hiding this comment

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

The thing is that Axis.clear is called from Axes.clear, sp in that case we don't not have to call it on creation.

Those two attributes are defined by clear, but since they are checked before clear is called now, they must be defined in __init__.

I do not really think that calling clear as part of the creation is a very nice design pattern, but I guess it requires quite a bit of an effort to get rid of it.

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.

Modulo fixing the doc string

@oscargus oscargus force-pushed the noclearonregister branch from 7c9b3ee to 6ae3134 Compare June 25, 2023 11:23
@oscargus
Copy link
Member Author

I have updated the doc-string.

I would just like to highlight another change here that may or may not be controversial. Passing None to set_rotation_mode will now make get_rotation_mode return "default" rather than None. This is somewhat consistent with many other function where passing None just really sets it to a default value. Notable, no other code changes was required so one can assume that most code checks for "anchor" and then handles the two other case as the "else". This simplifies the argument checking a bit for a very commonly used function (every Tick and label).

@oscargus oscargus force-pushed the noclearonregister branch from 6ae3134 to 8a0ad0d Compare June 25, 2023 12:15
clear : bool, default: True
Whether to clear the Axis on creation. This is not required, e.g., when
creating an Axis as part of an Axes, as ``Axes.clear`` will call
``Axis.clear``.
Copy link
Member Author

Choose a reason for hiding this comment

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

Not possible to link as there are three Axes.clear methods and I wanted to clearly state the difference between Axes.clear and Axis.clear.

@oscargus oscargus mentioned this pull request Jul 4, 2023
1 task
from library code where it is known that the Axis is cleared separately.
"""
self._position = None # clear position

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
self.stale = True

Copy link
Member

Choose a reason for hiding this comment

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

I'll try to do a follow up PR, I suspect most of the clear methods do not mark the artist as stale when they should....

@jklymak
Copy link
Member

jklymak commented Jul 6, 2023

@oscargus please self merge if you are OK with @tacaswell's changes!

@oscargus oscargus merged commit d2fc3a4 into matplotlib:main Jul 7, 2023
@oscargus oscargus deleted the noclearonregister branch July 7, 2023 01:06
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.

4 participants