1010from ._plot_side_space import PlotSideSpaces
1111
1212if TYPE_CHECKING :
13- from typing import Sequence , TypeAlias
13+ from typing import Any , Callable , Literal , Sequence , TypeAlias
1414
1515 from plotnine ._mpl .gridspec import p9GridSpec
1616 from plotnine ._mpl .layout_manager ._plot_side_space import (
@@ -508,73 +508,53 @@ def align_panels(self):
508508 """
509509 Align the edges of the panels in the composition
510510 """
511- for spaces in self .iter_bottom_spaces ():
512- bottoms = [space .panel_bottom for space in spaces ]
513- high = max (bottoms )
514- diffs = [high - b for b in bottoms ]
515- for space , diff in zip (spaces , diffs ):
516- space .margin_alignment += diff
517-
518- for spaces in self .iter_top_spaces ():
519- tops = [space .panel_top for space in spaces ]
520- low = min (tops )
521- diffs = [b - low for b in tops ]
522- for space , diff in zip (spaces , diffs ):
523- space .margin_alignment += diff
524-
525- for spaces in self .iter_left_spaces ():
526- lefts = [space .panel_left for space in spaces ]
527- high = max (lefts )
528- diffs = [high - l for l in lefts ]
529- for space , diff in zip (spaces , diffs ):
530- space .margin_alignment += diff
531-
532- for spaces in self .iter_right_spaces ():
533- rights = [space .panel_right for space in spaces ]
534- low = min (rights )
535- diffs = [r - low for r in rights ]
536- for space , diff in zip (spaces , diffs ):
537- space .margin_alignment += diff
511+ _equalize (
512+ self .iter_bottom_spaces (),
513+ lambda s : s .panel_bottom ,
514+ "margin_alignment" ,
515+ )
516+ _equalize (
517+ self .iter_top_spaces (),
518+ lambda s : s .panel_top ,
519+ "margin_alignment" ,
520+ how = "min" ,
521+ )
522+ _equalize (
523+ self .iter_left_spaces (),
524+ lambda s : s .panel_left ,
525+ "margin_alignment" ,
526+ )
527+ _equalize (
528+ self .iter_right_spaces (),
529+ lambda s : s .panel_right ,
530+ "margin_alignment" ,
531+ how = "min" ,
532+ )
538533
539534 def align_tags (self ):
540535 """
541536 Align the tags in the composition
542537 """
543- for spaces in self .iter_bottom_spaces ():
544- heights = [
545- space .tag_height + space .tag_alignment for space in spaces
546- ]
547- high = max (heights )
548- diffs = [high - h for h in heights ]
549- for space , diff in zip (spaces , diffs ):
550- space .tag_alignment += diff
551-
552- for spaces in self .iter_top_spaces ():
553- heights = [
554- space .tag_height + space .tag_alignment for space in spaces
555- ]
556- high = max (heights )
557- diffs = [high - h for h in heights ]
558- for space , diff in zip (spaces , diffs ):
559- space .tag_alignment += diff
560-
561- for spaces in self .iter_left_spaces ():
562- widths = [
563- space .tag_width + space .tag_alignment for space in spaces
564- ]
565- high = max (widths )
566- diffs = [high - w for w in widths ]
567- for space , diff in zip (spaces , diffs ):
568- space .tag_alignment += diff
569-
570- for spaces in self .iter_right_spaces ():
571- widths = [
572- space .tag_width + space .tag_alignment for space in spaces
573- ]
574- high = max (widths )
575- diffs = [high - w for w in widths ]
576- for space , diff in zip (spaces , diffs ):
577- space .tag_alignment += diff
538+ _equalize (
539+ self .iter_bottom_spaces (),
540+ lambda s : s .tag_height + s .tag_alignment ,
541+ "tag_alignment" ,
542+ )
543+ _equalize (
544+ self .iter_top_spaces (),
545+ lambda s : s .tag_height + s .tag_alignment ,
546+ "tag_alignment" ,
547+ )
548+ _equalize (
549+ self .iter_left_spaces (),
550+ lambda s : s .tag_width + s .tag_alignment ,
551+ "tag_alignment" ,
552+ )
553+ _equalize (
554+ self .iter_right_spaces (),
555+ lambda s : s .tag_width + s .tag_alignment ,
556+ "tag_alignment" ,
557+ )
578558
579559 def align_axis_titles (self ):
580560 """
@@ -588,20 +568,16 @@ def align_axis_titles(self):
588568 to store the value outside the _side_space and pick it up when
589569 setting the position of the texts!
590570 """
591-
592- for spaces in self .iter_bottom_spaces ():
593- clearances = [space .axis_title_clearance for space in spaces ]
594- high = max (clearances )
595- diffs = [high - b for b in clearances ]
596- for space , diff in zip (spaces , diffs ):
597- space .axis_title_alignment += diff
598-
599- for spaces in self .iter_left_spaces ():
600- clearances = [space .axis_title_clearance for space in spaces ]
601- high = max (clearances )
602- diffs = [high - l for l in clearances ]
603- for space , diff in zip (spaces , diffs ):
604- space .axis_title_alignment += diff
571+ _equalize (
572+ self .iter_bottom_spaces (),
573+ lambda s : s .axis_title_clearance ,
574+ "axis_title_alignment" ,
575+ )
576+ _equalize (
577+ self .iter_left_spaces (),
578+ lambda s : s .axis_title_clearance ,
579+ "axis_title_alignment" ,
580+ )
605581
606582 for tree in self .sub_compositions :
607583 tree .align_axis_titles ()
@@ -632,6 +608,50 @@ def resize_heights(self):
632608 self .sub_gridspec .set_height_ratios (height_ratios )
633609
634610
611+ def _equalize (
612+ spaces_iter : Iterator [Sequence [Any ]],
613+ measure : Callable [[Any ], float ],
614+ attr : str ,
615+ how : Literal ["max" , "min" ] = "max" ,
616+ ):
617+ """
618+ Equalize a measurement across spaces by adjusting an attribute
619+
620+ For each group of spaces yielded by the iterator, find the extreme
621+ value (max or min) of the measurement, then add the difference to
622+ each space's alignment attribute so that all spaces in the group end
623+ up with the same measurement.
624+
625+ Parameters
626+ ----------
627+ spaces_iter
628+ Iterator yielding groups of side spaces to equalize.
629+ Each group is a sequence of spaces along the same row or column of
630+ the composition.
631+ measure
632+ Function that extracts the value to equalize from a side space.
633+ attr
634+ Name of the alignment attribute on the side space to adjust.
635+ The difference is added to the current value.
636+ how
637+ Whether to equalize to the maximum or minimum value in each group.
638+ For "max", spaces with smaller measurements get extra alignment to
639+ match the largest.
640+ For "min", spaces with larger measurements get extra alignment to
641+ match the smallest.
642+ """
643+ for spaces in spaces_iter :
644+ values = [measure (s ) for s in spaces ]
645+ if how == "max" :
646+ target = max (values )
647+ diffs = [target - v for v in values ]
648+ else :
649+ target = min (values )
650+ diffs = [v - target for v in values ]
651+ for space , diff in zip (spaces , diffs ):
652+ setattr (space , attr , getattr (space , attr ) + diff )
653+
654+
635655# For debugging
636656def _draw_gridspecs (tree : LayoutTree ):
637657 from ..utils import draw_bbox
0 commit comments