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

Skip to content

Commit d0bb8ff

Browse files
eendebakptclaude
andcommitted
Defer tick materialization during Axes init/clear
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 6dc2b5a commit d0bb8ff

2 files changed

Lines changed: 68 additions & 15 deletions

File tree

lib/matplotlib/axes/_base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,20 @@ def __clear(self):
14351435
self.xaxis.set_clip_path(self.patch)
14361436
self.yaxis.set_clip_path(self.patch)
14371437

1438+
# Ensure spines have the correct transform before any subsequent
1439+
# layout or draw. Spine.__init__ installs ``self.axes.transData`` as
1440+
# a placeholder; the real blended transform is set by
1441+
# Spine.set_position (via ``_ensure_position_is_set``). Historically
1442+
# this fired as a side effect of tick materialization during clear;
1443+
# with lazy tick lists that cascade no longer runs, so nudge it here
1444+
# for spines that still carry the placeholder (projections like
1445+
# polar or secondary axes install custom transforms and are
1446+
# skipped).
1447+
for spine in self.spines.values():
1448+
if spine._position is None and spine._transform is self.transData:
1449+
spine._position = ('outward', 0.0)
1450+
spine.set_transform(spine.get_spine_transform())
1451+
14381452
if self._sharex is not None:
14391453
self.xaxis.set_visible(xaxis_visible)
14401454
self.patch.set_visible(patch_visible)

lib/matplotlib/axis.py

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -561,11 +561,23 @@ def __get__(self, instance, owner):
561561
if self._major:
562562
instance.majorTicks = []
563563
tick = instance._get_tick(major=True)
564+
tick.clipbox = instance.clipbox
565+
tick._clippath = instance._clippath
566+
tick._clipon = instance._clipon
567+
tick.gridline.clipbox = instance.clipbox
568+
tick.gridline._clippath = instance._clippath
569+
tick.gridline._clipon = instance._clipon
564570
instance.majorTicks = [tick]
565571
return instance.majorTicks
566572
else:
567573
instance.minorTicks = []
568574
tick = instance._get_tick(major=False)
575+
tick.clipbox = instance.clipbox
576+
tick._clippath = instance._clippath
577+
tick._clipon = instance._clipon
578+
tick.gridline.clipbox = instance.clipbox
579+
tick.gridline._clippath = instance._clippath
580+
tick.gridline._clipon = instance._clipon
569581
instance.minorTicks = [tick]
570582
return instance.minorTicks
571583

@@ -908,18 +920,45 @@ def reset_ticks(self):
908920
Each list starts with a single fresh Tick.
909921
"""
910922
# Restore the lazy tick lists.
911-
try:
912-
del self.majorTicks
913-
except AttributeError:
914-
pass
915-
try:
916-
del self.minorTicks
917-
except AttributeError:
918-
pass
919-
try:
920-
self.set_clip_path(self.axes.patch)
921-
except AttributeError:
922-
pass
923+
had_major = bool(self.__dict__.pop('majorTicks', None))
924+
had_minor = bool(self.__dict__.pop('minorTicks', None))
925+
if had_major or had_minor:
926+
try:
927+
self.set_clip_path(self.axes.patch)
928+
except AttributeError:
929+
pass
930+
931+
def _existing_ticks(self, major=None):
932+
"""
933+
Yield already-materialized ticks without triggering the lazy descriptor.
934+
935+
`majorTicks` and `minorTicks` are `_LazyTickList` descriptors that
936+
create a fresh `.Tick` on first access. Several internal methods
937+
(`set_clip_path`, `set_tick_params`) need to touch every
938+
*already-materialized* tick without forcing materialization, because
939+
doing so would
940+
941+
(a) create throwaway Tick objects during ``Axes.__init__`` and
942+
``Axes.__clear`` -- defeating the whole point of the lazy lists --
943+
and
944+
945+
(b) risk re-entering the
946+
``Spine.set_position -> Axis.reset_ticks -> Axis.set_clip_path
947+
-> _LazyTickList.__get__ -> Tick.__init__ -> Spine.set_position``
948+
cascade.
949+
950+
Reading the instance ``__dict__`` directly bypasses the descriptor.
951+
952+
Parameters
953+
----------
954+
major : bool, optional
955+
If True, yield only major ticks; if False, only minor ticks;
956+
if None (default), yield major followed by minor.
957+
"""
958+
if major is None or major:
959+
yield from self.__dict__.get('majorTicks', ())
960+
if major is None or not major:
961+
yield from self.__dict__.get('minorTicks', ())
923962

924963
def minorticks_on(self):
925964
"""
@@ -988,11 +1027,11 @@ def set_tick_params(self, which='major', reset=False, **kwargs):
9881027
else:
9891028
if which in ['major', 'both']:
9901029
self._major_tick_kw.update(kwtrans)
991-
for tick in self.majorTicks:
1030+
for tick in self._existing_ticks(major=True):
9921031
tick._apply_params(**kwtrans)
9931032
if which in ['minor', 'both']:
9941033
self._minor_tick_kw.update(kwtrans)
995-
for tick in self.minorTicks:
1034+
for tick in self._existing_ticks(major=False):
9961035
tick._apply_params(**kwtrans)
9971036
# labelOn and labelcolor also apply to the offset text.
9981037
if 'label1On' in kwtrans or 'label2On' in kwtrans:
@@ -1131,7 +1170,7 @@ def _translate_tick_params(cls, kw, reverse=False):
11311170

11321171
def set_clip_path(self, path, transform=None):
11331172
super().set_clip_path(path, transform)
1134-
for child in self.majorTicks + self.minorTicks:
1173+
for child in self._existing_ticks():
11351174
child.set_clip_path(path, transform)
11361175
self.stale = True
11371176

0 commit comments

Comments
 (0)