@@ -502,6 +502,125 @@ def formatter(self, formatter):
502
502
self ._formatter = formatter
503
503
504
504
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
+
505
624
class _LazyTickList :
506
625
"""
507
626
A descriptor for lazy instantiation of tick lists.
@@ -639,6 +758,9 @@ def __init__(self, axes, *, pickradius=15, clear=True):
639
758
self ._major_tick_kw = dict ()
640
759
self ._minor_tick_kw = dict ()
641
760
761
+ self ._major_ticks = _TickCollection (self , "majorTicks" )
762
+ self ._minor_ticks = _TickCollection (self , "minorTicks" )
763
+
642
764
if clear :
643
765
self .clear ()
644
766
else :
@@ -695,6 +817,8 @@ def _get_axis_name(self):
695
817
return next (name for name , axis in self .axes ._axis_map .items ()
696
818
if axis is self )
697
819
820
+ # Interface to address the ticklists via a single entity
821
+
698
822
# During initialization, Axis objects often create ticks that are later
699
823
# unused; this turns out to be a very slow step. Instead, use a custom
700
824
# 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):
965
1089
else :
966
1090
if which in ['major' , 'both' ]:
967
1091
self ._major_tick_kw .update (kwtrans )
968
- for tick in self .majorTicks :
969
- tick ._apply_params (** kwtrans )
1092
+ self ._major_ticks .apply_params (** kwtrans )
970
1093
if which in ['minor' , 'both' ]:
971
1094
self ._minor_tick_kw .update (kwtrans )
972
- for tick in self .minorTicks :
973
- tick ._apply_params (** kwtrans )
1095
+ self ._minor_ticks .apply_params (** kwtrans )
974
1096
# labelOn and labelcolor also apply to the offset text.
975
1097
if 'label1On' in kwtrans or 'label2On' in kwtrans :
976
1098
self .offsetText .set_visible (
@@ -1107,8 +1229,8 @@ def _translate_tick_params(cls, kw, reverse=False):
1107
1229
1108
1230
def set_clip_path (self , path , transform = None ):
1109
1231
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 )
1112
1234
self .stale = True
1113
1235
1114
1236
def get_view_interval (self ):
@@ -1379,12 +1501,11 @@ def get_tightbbox(self, renderer=None, *, for_layout_only=False):
1379
1501
return None
1380
1502
1381
1503
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 )
1388
1509
1389
1510
@martist .allow_rasterization
1390
1511
def draw (self , renderer ):
@@ -1651,14 +1772,8 @@ def get_major_ticks(self, numticks=None):
1651
1772
"""
1652
1773
if numticks is None :
1653
1774
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 ))
1662
1777
1663
1778
def get_minor_ticks (self , numticks = None ):
1664
1779
r"""
@@ -1677,14 +1792,8 @@ def get_minor_ticks(self, numticks=None):
1677
1792
"""
1678
1793
if numticks is None :
1679
1794
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 ))
1688
1797
1689
1798
def grid (self , visible = None , which = 'major' , ** kwargs ):
1690
1799
"""
@@ -2286,26 +2395,11 @@ def _get_ticks_position(self):
2286
2395
- "default" if only tick1line, tick2line and label1 are visible;
2287
2396
- "unknown" otherwise.
2288
2397
"""
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
2309
2403
else :
2310
2404
return "unknown"
2311
2405
0 commit comments