Skip to content

Commit 6615143

Browse files
committed
Refactor strip_text & strip_text_background
1 parent 9d3b673 commit 6615143

File tree

75 files changed

+495
-181
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+495
-181
lines changed

doc/changelog.qmd

+14
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ title: Changelog
112112
- Fixed theming of `axis_text`, `axis_text_x` and `axis_text_y` so when they can be
113113
overriden if previously set to `element_blank()`.
114114

115+
- Fixed `strip_text_x_background`s to always have equal heights and `strip_text_y_background`s
116+
to have equal widths. This applies to cases where the texts have unequal number of lines or
117+
they are rotated but have different lengths.
118+
119+
- Fixed the empty space below the plot title (or subtitle) when `strip_text_x_background`s are
120+
partly or fully aligned within the panel. This space (hole), was not accounted for by any of
121+
the themeables. This affected `theme_xkcd` and any `theme` settings that tried to slide the
122+
text / background into the panel.
123+
124+
- Fixed the empty space to the left the right margin when the `strip_text_y_background`s are
125+
partly or fully aligned within the panel. This space (hole), was not accounted for by any of
126+
the themeables. This affected `theme_xkcd` and any `theme` settings that tried to slide the
127+
text / background into the panel.
128+
115129
## v0.14.5
116130
(2025-01-02)
117131
[![](https://zenodo.org/badge/DOI/10.5281/zenodo.14587381.svg)](https://doi.org/10.5281/zenodo.14587381)

plotnine/_mpl/layout_manager/_layout_items.py

+85-23
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from matplotlib.text import Text
88

9+
from plotnine._mpl.patches import StripTextPatch
10+
from plotnine._utils import ha_as_float, va_as_float
911
from plotnine.exceptions import PlotnineError
1012

1113
from ..utils import (
@@ -35,7 +37,11 @@
3537
from plotnine._mpl.text import StripText
3638
from plotnine.iapi import legend_artists
3739
from plotnine.themes.elements import margin as Margin
38-
from plotnine.typing import StripPosition
40+
from plotnine.typing import (
41+
HorizontalJustification,
42+
StripPosition,
43+
VerticalJustification,
44+
)
3945

4046
from ._spaces import LayoutSpaces
4147

@@ -299,9 +305,9 @@ def axis_ticks_y(self, ax: Axes) -> Iterator[Tick]:
299305

300306
return chain(major, minor)
301307

302-
def strip_text_x_height(self, position: StripPosition) -> float:
308+
def strip_text_x_extra_height(self, position: StripPosition) -> float:
303309
"""
304-
Height taken up by the top strips
310+
Height taken up by the top strips that is outside the panels
305311
"""
306312
if not self.strip_text_x:
307313
return 0
@@ -311,11 +317,23 @@ def strip_text_x_height(self, position: StripPosition) -> float:
311317
for st in self.strip_text_x
312318
if st.patch.position == position
313319
]
314-
return self.calc.max_height(artists)
315320

316-
def strip_text_y_width(self, position: StripPosition) -> float:
321+
heights = []
322+
323+
for a in artists:
324+
info = (
325+
a.text.draw_info
326+
if isinstance(a, StripTextPatch)
327+
else a.draw_info
328+
)
329+
h = self.calc.height(a)
330+
heights.append(max(h + h * info.strip_align, 0))
331+
332+
return max(heights)
333+
334+
def strip_text_y_extra_width(self, position: StripPosition) -> float:
317335
"""
318-
Width taken up by the right strips
336+
Width taken up by the top strips that is outside the panels
319337
"""
320338
if not self.strip_text_y:
321339
return 0
@@ -325,7 +343,19 @@ def strip_text_y_width(self, position: StripPosition) -> float:
325343
for st in self.strip_text_y
326344
if st.patch.position == position
327345
]
328-
return self.calc.max_width(artists)
346+
347+
widths = []
348+
349+
for a in artists:
350+
info = (
351+
a.text.draw_info
352+
if isinstance(a, StripTextPatch)
353+
else a.draw_info
354+
)
355+
w = self.calc.width(a)
356+
widths.append(max(w + w * info.strip_align, 0))
357+
358+
return max(widths)
329359

330360
def axis_ticks_x_max_height_at(self, location: AxesLocation) -> float:
331361
"""
@@ -489,6 +519,8 @@ def _adjust_positions(self, spaces: LayoutSpaces):
489519

490520
self._adjust_axis_text_x(justify)
491521
self._adjust_axis_text_y(justify)
522+
self._strip_text_x_background_equal_heights()
523+
self._strip_text_y_background_equal_widths()
492524

493525
def _adjust_axis_text_x(self, justify: TextJustifier):
494526
"""
@@ -574,6 +606,36 @@ def to_horizontal_axis_dimensions(value: float, ax: Axes) -> float:
574606
text, ha, -axis_text_col_width, 0, width=width
575607
)
576608

609+
def _strip_text_x_background_equal_heights(self):
610+
"""
611+
Make the strip_text_x_backgrounds have equal heights
612+
613+
The smaller heights are expanded to match the largest height
614+
"""
615+
if not self.strip_text_x:
616+
return
617+
618+
heights = [self.calc.bbox(t.patch).height for t in self.strip_text_x]
619+
max_height = max(heights)
620+
relative_heights = [max_height / h for h in heights]
621+
for text, scale in zip(self.strip_text_x, relative_heights):
622+
text.patch.expand = scale
623+
624+
def _strip_text_y_background_equal_widths(self):
625+
"""
626+
Make the strip_text_y_backgrounds have equal widths
627+
628+
The smaller widths are expanded to match the largest width
629+
"""
630+
if not self.strip_text_y:
631+
return
632+
633+
widths = [self.calc.bbox(t.patch).width for t in self.strip_text_y]
634+
max_width = max(widths)
635+
relative_widths = [max_width / w for w in widths]
636+
for text, scale in zip(self.strip_text_y, relative_widths):
637+
text.patch.expand = scale
638+
577639

578640
def _text_is_visible(text: Text) -> bool:
579641
"""
@@ -596,16 +658,15 @@ class TextJustifier:
596658
def horizontally(
597659
self,
598660
text: Text,
599-
ha: str | float,
661+
ha: HorizontalJustification | float,
600662
left: float,
601663
right: float,
602664
width: float | None = None,
603665
):
604666
"""
605667
Horizontally Justify text between left and right
606668
"""
607-
lookup = {"left": 0.0, "center": 0.5, "right": 1.0}
608-
rel = lookup.get(ha, ha) # pyright: ignore[reportCallIssue, reportArgumentType]
669+
rel = ha_as_float(ha)
609670
if width is None:
610671
width = self.spaces.items.calc.width(text)
611672
x = rel_position(rel, width, left, right)
@@ -615,50 +676,51 @@ def horizontally(
615676
def vertically(
616677
self,
617678
text: Text,
618-
va: str | float,
679+
va: VerticalJustification | float,
619680
bottom: float,
620681
top: float,
621682
height: float | None = None,
622683
):
623684
"""
624685
Vertically Justify text between bottom and top
625686
"""
626-
lookup = {
627-
"top": 1.0,
628-
"center": 0.5,
629-
"baseline": 0.5,
630-
"center_baseline": 0.5,
631-
"bottom": 0.0,
632-
}
633-
rel = lookup.get(va, va) # pyright: ignore[reportCallIssue, reportArgumentType]
687+
rel = va_as_float(va)
634688

635689
if height is None:
636690
height = self.spaces.items.calc.height(text)
637691
y = rel_position(rel, height, bottom, top)
638692
text.set_y(y)
639693
text.set_verticalalignment("bottom")
640694

641-
def horizontally_across_panel(self, text: Text, ha: str | float):
695+
def horizontally_across_panel(
696+
self, text: Text, ha: HorizontalJustification | float
697+
):
642698
"""
643699
Horizontally Justify text accross the panel(s) width
644700
"""
645701
self.horizontally(text, ha, self.spaces.l.left, self.spaces.r.right)
646702

647-
def horizontally_across_plot(self, text: Text, ha: str | float):
703+
def horizontally_across_plot(
704+
self, text: Text, ha: HorizontalJustification | float
705+
):
648706
"""
649707
Horizontally Justify text across the plot's width
650708
"""
651709
self.horizontally(
652710
text, ha, self.spaces.l.plot_left, self.spaces.r.plot_right
653711
)
654712

655-
def vertically_along_panel(self, text: Text, va: str | float):
713+
def vertically_along_panel(
714+
self, text: Text, va: VerticalJustification | float
715+
):
656716
"""
657717
Horizontally Justify text along the panel(s) height
658718
"""
659719
self.vertically(text, va, self.spaces.b.bottom, self.spaces.t.top)
660720

661-
def vertically_along_plot(self, text: Text, va: str | float):
721+
def vertically_along_plot(
722+
self, text: Text, va: VerticalJustification | float
723+
):
662724
"""
663725
Vertically Justify text along the plot's height
664726
"""

plotnine/_mpl/layout_manager/_spaces.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ class right_spaces(_side_spaces):
433433
margin_alignment: float = 0
434434
legend: float = 0
435435
legend_box_spacing: float = 0
436-
strip_text_y_width_right: float = 0
436+
strip_text_y_extra_width: float = 0
437437

438438
def _calculate(self):
439439
items = self.items
@@ -452,7 +452,7 @@ def _calculate(self):
452452
self.legend = self.legend_width
453453
self.legend_box_spacing = theme.getp("legend_box_spacing")
454454

455-
self.strip_text_y_width_right = items.strip_text_y_width("right")
455+
self.strip_text_y_extra_width = items.strip_text_y_extra_width("right")
456456

457457
# Adjust plot_margin to make room for ylabels that protude well
458458
# beyond the axes
@@ -545,7 +545,7 @@ class top_spaces(_side_spaces):
545545
plot_subtitle_margin_bottom: float = 0
546546
legend: float = 0
547547
legend_box_spacing: float = 0
548-
strip_text_x_height_top: float = 0
548+
strip_text_x_extra_height: float = 0
549549

550550
def _calculate(self):
551551
items = self.items
@@ -578,7 +578,7 @@ def _calculate(self):
578578
self.legend = self.legend_height
579579
self.legend_box_spacing = theme.getp("legend_box_spacing") * F
580580

581-
self.strip_text_x_height_top = items.strip_text_x_height("top")
581+
self.strip_text_x_extra_height = items.strip_text_x_extra_height("top")
582582

583583
# Adjust plot_margin to make room for ylabels that protude well
584584
# beyond the axes
@@ -1061,7 +1061,7 @@ def _calculate_panel_spacing_facet_wrap(self) -> tuple[float, float]:
10611061
# Only interested in the proportion of the strip that
10621062
# does not overlap with the panel
10631063
if strip_align_x > -1:
1064-
self.sh += self.t.strip_text_x_height_top * (1 + strip_align_x)
1064+
self.sh += self.t.strip_text_x_extra_height * (1 + strip_align_x)
10651065

10661066
if facet.free["x"]:
10671067
self.sh += self.items.axis_text_x_max_height_at(

0 commit comments

Comments
 (0)