@@ -502,6 +502,125 @@ def formatter(self, formatter):
502502 self ._formatter = formatter
503503
504504
505+ class _TickCollection :
506+ """
507+ A facade for tick lists based on _LazyTickList.
508+
509+ This provides an interface for manipulating the ticks collectively. It
510+ removes the need to address individual elements of the tick lists and thus
511+ opens up a path of replacing individual lines with collections while
512+ keeping the API stable::
513+
514+ - tick lists - tick collection
515+ - tick (Tick) - tick1lines (LineCollection)
516+ - tick1line (Line2D) - tick2lines (LineCollection)
517+ - tick2line (Line2D) ===> - tick1labels (TextCollection*)
518+ - tick1label (Text) - tick2labels (TextCollection*)
519+ - tick2label (Text) - gridline
520+ - gridline (Line2D)
521+ - tick (Tick)
522+ - ...
523+ - tick (Tick)
524+ - ...
525+
526+ (*) TextCollection does not yet exists. Probably worth implementing, but
527+ we can also use lists of Text for the time being.
528+
529+ """
530+ def __init__ (self , axis , ticklist_name ):
531+ """
532+ We cannot initialize this in Axis using
533+ ``_TickCollection(self.majorTicks)``, because that would trigger the
534+ evaluation mechanism of the _LazyTickList. Therefore we delay the
535+ access to the latest possible point via the property
536+ ``self._ticklist``.
537+ """
538+ self ._axis = axis
539+ self ._ticklist_name = ticklist_name
540+
541+ def __len__ (self ):
542+ return len (self ._ticklist )
543+
544+ @property
545+ def _ticklist (self ):
546+ """Delayed access to resolve _LazyTickList as late as possible."""
547+ return getattr (self ._axis , self ._ticklist_name )
548+
549+ def apply_params (self , ** kwargs ):
550+ """Apply **kwargs to all ticks."""
551+ for tick in self ._ticklist :
552+ tick ._apply_params (** kwargs )
553+
554+ def set_clip_path (self , clippath , transform ):
555+ """
556+ Set the clip path for all ticks.
557+
558+ See `.Artist.set_clip_path`.
559+ """
560+ for tick in self ._ticklist :
561+ tick .set_clip_path (clippath , transform )
562+
563+ def _get_tick_position (self ):
564+ """See Axis._get_ticks_position()."""
565+ tick = self ._ticklist [0 ]
566+ if (tick .tick1line .get_visible ()
567+ and not tick .tick2line .get_visible ()
568+ and tick .label1 .get_visible ()
569+ and not tick .label2 .get_visible ()):
570+ return 1
571+ elif (tick .tick2line .get_visible ()
572+ and not tick .tick1line .get_visible ()
573+ and tick .label2 .get_visible ()
574+ and not tick .label1 .get_visible ()):
575+ return 2
576+ elif (tick .tick1line .get_visible ()
577+ and tick .tick2line .get_visible ()
578+ and tick .label1 .get_visible ()
579+ and not tick .label2 .get_visible ()):
580+ return "default"
581+ else :
582+ return "unknown"
583+
584+ def get_ticks (self , numticks , tick_getter ):
585+ #if numticks is None:
586+ # numticks = len(self.get_majorticklocs())
587+
588+ while len (self ._ticklist ) < numticks :
589+ # Update the new tick label properties from the old.
590+ tick = tick_getter ()
591+ self ._ticklist .append (tick )
592+ self ._axis ._copy_tick_props (self ._ticklist [0 ], tick )
593+
594+ return self ._ticklist [:numticks ]
595+
596+ def get_tick_padding (self ):
597+ if not self ._ticklist :
598+ return 0
599+ else :
600+ return self ._ticklist [0 ].get_tick_padding ()
601+
602+ def get_pad_pixels (self ):
603+ return self ._ticklist [0 ].get_pad_pixels ()
604+
605+ def set_label_alignment (self , label1_va , label1_ha , label2_va , label2_ha ):
606+ for t in self ._ticklist :
607+ t .label1 .set_va (label1_va )
608+ t .label1 .set_ha (label1_ha )
609+ t .label2 .set_va (label2_va )
610+ t .label2 .set_ha (label2_ha )
611+
612+ def get_grid_visible (self ):
613+ # Return True/False if all grid lines are on or off, None if they are
614+ # not all in the same state.
615+ if all (tick .gridline .get_visible () for tick in self ._ticklist ):
616+ return True
617+ elif not any (tick .gridline .get_visible () for tick in self ._ticklist ):
618+ return False
619+ else :
620+ return None
621+
622+
623+
505624class _LazyTickList :
506625 """
507626 A descriptor for lazy instantiation of tick lists.
@@ -639,6 +758,9 @@ def __init__(self, axes, *, pickradius=15, clear=True):
639758 self ._major_tick_kw = dict ()
640759 self ._minor_tick_kw = dict ()
641760
761+ self ._major_ticks = _TickCollection (self , "majorTicks" )
762+ self ._minor_ticks = _TickCollection (self , "minorTicks" )
763+
642764 if clear :
643765 self .clear ()
644766 else :
@@ -695,6 +817,8 @@ def _get_axis_name(self):
695817 return next (name for name , axis in self .axes ._axis_map .items ()
696818 if axis is self )
697819
820+ # Interface to address the ticklists via a single entity
821+
698822 # During initialization, Axis objects often create ticks that are later
699823 # unused; this turns out to be a very slow step. Instead, use a custom
700824 # descriptor to make the tick lists lazy and instantiate them as needed.
@@ -965,12 +1089,10 @@ def set_tick_params(self, which='major', reset=False, **kwargs):
9651089 else :
9661090 if which in ['major' , 'both' ]:
9671091 self ._major_tick_kw .update (kwtrans )
968- for tick in self .majorTicks :
969- tick ._apply_params (** kwtrans )
1092+ self ._major_ticks .apply_params (** kwtrans )
9701093 if which in ['minor' , 'both' ]:
9711094 self ._minor_tick_kw .update (kwtrans )
972- for tick in self .minorTicks :
973- tick ._apply_params (** kwtrans )
1095+ self ._minor_ticks .apply_params (** kwtrans )
9741096 # labelOn and labelcolor also apply to the offset text.
9751097 if 'label1On' in kwtrans or 'label2On' in kwtrans :
9761098 self .offsetText .set_visible (
@@ -1107,8 +1229,8 @@ def _translate_tick_params(cls, kw, reverse=False):
11071229
11081230 def set_clip_path (self , path , transform = None ):
11091231 super ().set_clip_path (path , transform )
1110- for child in self .majorTicks + self . minorTicks :
1111- child .set_clip_path (path , transform )
1232+ self ._major_ticks . set_clip_path ( path , transform )
1233+ self . _minor_ticks .set_clip_path (path , transform )
11121234 self .stale = True
11131235
11141236 def get_view_interval (self ):
@@ -1379,12 +1501,11 @@ def get_tightbbox(self, renderer=None, *, for_layout_only=False):
13791501 return None
13801502
13811503 def get_tick_padding (self ):
1382- values = []
1383- if len (self .majorTicks ):
1384- values .append (self .majorTicks [0 ].get_tick_padding ())
1385- if len (self .minorTicks ):
1386- values .append (self .minorTicks [0 ].get_tick_padding ())
1387- return max (values , default = 0 )
1504+ pads = [
1505+ self ._major_ticks .get_tick_padding (),
1506+ self ._minor_ticks .get_tick_padding (),
1507+ ]
1508+ return max ((p for p in pads if p is not None ), default = 0 )
13881509
13891510 @martist .allow_rasterization
13901511 def draw (self , renderer ):
@@ -1651,14 +1772,8 @@ def get_major_ticks(self, numticks=None):
16511772 """
16521773 if numticks is None :
16531774 numticks = len (self .get_majorticklocs ())
1654-
1655- while len (self .majorTicks ) < numticks :
1656- # Update the new tick label properties from the old.
1657- tick = self ._get_tick (major = True )
1658- self .majorTicks .append (tick )
1659- self ._copy_tick_props (self .majorTicks [0 ], tick )
1660-
1661- return self .majorTicks [:numticks ]
1775+ return self ._major_ticks .get_ticks (
1776+ numticks , tick_getter = functools .partial (self ._get_tick , True ))
16621777
16631778 def get_minor_ticks (self , numticks = None ):
16641779 r"""
@@ -1677,14 +1792,8 @@ def get_minor_ticks(self, numticks=None):
16771792 """
16781793 if numticks is None :
16791794 numticks = len (self .get_minorticklocs ())
1680-
1681- while len (self .minorTicks ) < numticks :
1682- # Update the new tick label properties from the old.
1683- tick = self ._get_tick (major = False )
1684- self .minorTicks .append (tick )
1685- self ._copy_tick_props (self .minorTicks [0 ], tick )
1686-
1687- return self .minorTicks [:numticks ]
1795+ return self ._minor_ticks .get_ticks (
1796+ numticks , tick_getter = functools .partial (self ._get_tick , False ))
16881797
16891798 def grid (self , visible = None , which = 'major' , ** kwargs ):
16901799 """
@@ -2286,26 +2395,11 @@ def _get_ticks_position(self):
22862395 - "default" if only tick1line, tick2line and label1 are visible;
22872396 - "unknown" otherwise.
22882397 """
2289- major = self .majorTicks [0 ]
2290- minor = self .minorTicks [0 ]
2291- if all (tick .tick1line .get_visible ()
2292- and not tick .tick2line .get_visible ()
2293- and tick .label1 .get_visible ()
2294- and not tick .label2 .get_visible ()
2295- for tick in [major , minor ]):
2296- return 1
2297- elif all (tick .tick2line .get_visible ()
2298- and not tick .tick1line .get_visible ()
2299- and tick .label2 .get_visible ()
2300- and not tick .label1 .get_visible ()
2301- for tick in [major , minor ]):
2302- return 2
2303- elif all (tick .tick1line .get_visible ()
2304- and tick .tick2line .get_visible ()
2305- and tick .label1 .get_visible ()
2306- and not tick .label2 .get_visible ()
2307- for tick in [major , minor ]):
2308- return "default"
2398+ major_pos = self ._major_ticks ._get_tick_position ()
2399+ minor_pos = self ._minor_ticks ._get_tick_position ()
2400+
2401+ if major_pos == minor_pos :
2402+ return major_pos
23092403 else :
23102404 return "unknown"
23112405
0 commit comments