Skip to content

Commit f844850

Browse files
committed
Extract shared plot label positioning into _position_plot_labels()
Deduplicate the title/subtitle/caption/footer positioning logic that was nearly identical between PlotLayoutItems and CompositionLayoutItems. Add boundary properties to PlotSideSpaces so both side-space classes share the same interface. Remove no-op @DataClass from CompositionLayoutItems.
1 parent 11aa422 commit f844850

3 files changed

Lines changed: 110 additions & 121 deletions

File tree

Lines changed: 3 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
43
from typing import TYPE_CHECKING
54

65
from matplotlib.text import Text
76

8-
from plotnine._mpl.utils import (
9-
ArtistGeometry,
10-
TextJustifier,
11-
resize_footer_background,
12-
resize_footer_line,
13-
)
7+
from plotnine._mpl.utils import ArtistGeometry
148

159
if TYPE_CHECKING:
1610
from typing import Any
@@ -23,7 +17,6 @@
2317
from ._composition_side_space import CompositionSideSpaces
2418

2519

26-
@dataclass
2720
class CompositionLayoutItems:
2821
"""
2922
plot_annotation artists
@@ -61,61 +54,6 @@ def _move_artists(self, spaces: CompositionSideSpaces):
6154
"""
6255
Move the annotations to their final positions
6356
"""
64-
theme = self.cmp.theme
65-
plot_title_position = theme.getp("plot_title_position", "panel")
66-
plot_caption_position = theme.getp("plot_caption_position", "panel")
67-
plot_footer_position = theme.getp("plot_footer_position", "plot")
68-
justify = TextJustifier.from_boundaries(
69-
spaces.cmp.figure,
70-
plot_left=spaces.plot_left,
71-
plot_right=spaces.plot_right,
72-
plot_bottom=spaces.plot_bottom,
73-
plot_top=spaces.plot_top,
74-
panel_left=spaces.panel_left,
75-
panel_right=spaces.panel_right,
76-
panel_bottom=spaces.panel_bottom,
77-
panel_top=spaces.panel_top,
78-
)
79-
80-
if self.plot_title:
81-
ha = theme.getp(("plot_title", "ha"))
82-
self.plot_title.set_y(spaces.t.y2("plot_title"))
83-
justify.horizontally_about(
84-
self.plot_title, ha, plot_title_position
85-
)
86-
87-
if self.plot_subtitle:
88-
ha = theme.getp(("plot_subtitle", "ha"))
89-
self.plot_subtitle.set_y(spaces.t.y2("plot_subtitle"))
90-
justify.horizontally_about(
91-
self.plot_subtitle, ha, plot_title_position
92-
)
93-
94-
if self.plot_caption:
95-
ha = theme.getp(("plot_caption", "ha"), "right")
96-
self.plot_caption.set_y(spaces.b.y1("plot_caption"))
97-
justify.horizontally_about(
98-
self.plot_caption, ha, plot_caption_position
99-
)
57+
from ._plot_layout_items import _position_plot_labels
10058

101-
if self.plot_footer:
102-
ha = theme.getp(("plot_footer", "ha"), "left")
103-
self.plot_footer.set_y(spaces.b.y1("plot_footer"))
104-
justify.horizontally_about(
105-
self.plot_footer, ha, plot_footer_position
106-
)
107-
if self.plot_footer_background:
108-
resize_footer_background(
109-
self.plot_footer_background,
110-
x=spaces.l.offset,
111-
y=spaces.b.offset,
112-
height=spaces.b.footer_height,
113-
width=spaces.plot_width,
114-
)
115-
if self.plot_footer_line:
116-
resize_footer_line(
117-
self.plot_footer_line,
118-
x=spaces.l.offset,
119-
width=spaces.plot_width,
120-
y=spaces.b.offset + spaces.b.footer_height,
121-
)
59+
_position_plot_labels(spaces.cmp.figure, self.cmp.theme, spaces, self)

plotnine/_mpl/layout_manager/_plot_layout_items.py

Lines changed: 75 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from matplotlib.axes import Axes
2929
from matplotlib.axis import Tick
30+
from matplotlib.figure import Figure
3031
from matplotlib.lines import Line2D
3132
from matplotlib.patches import Rectangle
3233
from matplotlib.transforms import Transform
@@ -36,10 +37,13 @@
3637
from plotnine._mpl.text import StripText
3738
from plotnine.iapi import legend_artists
3839
from plotnine.themes.elements import margin as Margin
40+
from plotnine.themes.theme import theme
3941
from plotnine.typing import (
4042
StripPosition,
4143
)
4244

45+
from ._composition_layout_items import CompositionLayoutItems
46+
from ._composition_side_space import CompositionSideSpaces
4347
from ._plot_side_space import PlotSideSpaces
4448

4549
AxesLocation: TypeAlias = Literal[
@@ -352,67 +356,13 @@ def _move_artists(self, spaces: PlotSideSpaces):
352356
Move the artists to their final positions
353357
"""
354358
theme = self.plot.theme
355-
plot_title_position = theme.getp("plot_title_position", "panel")
356-
plot_caption_position = theme.getp("plot_caption_position", "panel")
357-
plot_footer_position = theme.getp("plot_footer_position", "plot")
358-
justify = TextJustifier.from_boundaries(
359-
spaces.plot.figure,
360-
plot_left=spaces.l.plot_left,
361-
plot_right=spaces.r.plot_right,
362-
plot_bottom=spaces.b.plot_bottom,
363-
plot_top=spaces.t.plot_top,
364-
panel_left=spaces.l.panel_left,
365-
panel_right=spaces.r.panel_right,
366-
panel_bottom=spaces.b.panel_bottom,
367-
panel_top=spaces.t.panel_top,
359+
justify = _position_plot_labels(
360+
spaces.plot.figure, theme, spaces, self
368361
)
369362

370363
if self.plot_tag:
371364
set_plot_tag_position(self.plot_tag, spaces)
372365

373-
if self.plot_title:
374-
ha = theme.getp(("plot_title", "ha"))
375-
self.plot_title.set_y(spaces.t.y2("plot_title"))
376-
justify.horizontally_about(
377-
self.plot_title, ha, plot_title_position
378-
)
379-
380-
if self.plot_subtitle:
381-
ha = theme.getp(("plot_subtitle", "ha"))
382-
self.plot_subtitle.set_y(spaces.t.y2("plot_subtitle"))
383-
justify.horizontally_about(
384-
self.plot_subtitle, ha, plot_title_position
385-
)
386-
387-
if self.plot_caption:
388-
ha = theme.getp(("plot_caption", "ha"), "right")
389-
self.plot_caption.set_y(spaces.b.y1("plot_caption"))
390-
justify.horizontally_about(
391-
self.plot_caption, ha, plot_caption_position
392-
)
393-
394-
if self.plot_footer:
395-
ha = theme.getp(("plot_footer", "ha"), "left")
396-
self.plot_footer.set_y(spaces.b.y1("plot_footer"))
397-
justify.horizontally_about(
398-
self.plot_footer, ha, plot_footer_position
399-
)
400-
if self.plot_footer_background:
401-
resize_footer_background(
402-
self.plot_footer_background,
403-
x=spaces.l.offset,
404-
y=spaces.b.offset,
405-
height=spaces.b.footer_height,
406-
width=spaces.plot_width,
407-
)
408-
if self.plot_footer_line:
409-
resize_footer_line(
410-
self.plot_footer_line,
411-
x=spaces.l.offset,
412-
width=spaces.plot_width,
413-
y=spaces.b.offset + spaces.b.footer_height,
414-
)
415-
416366
if self.axis_title_x:
417367
ha = theme.getp(("axis_title_x", "ha"), "center")
418368
self.axis_title_x.set_y(spaces.b.y1("axis_title_x"))
@@ -555,6 +505,75 @@ def _text_is_visible(text: Text) -> bool:
555505
return text.get_visible() and text._text # type: ignore
556506

557507

508+
def _position_plot_labels(
509+
figure: Figure,
510+
theme: theme,
511+
spaces: PlotSideSpaces | CompositionSideSpaces,
512+
items: PlotLayoutItems | CompositionLayoutItems,
513+
) -> TextJustifier:
514+
"""
515+
Position title, subtitle, caption, footer, and footer decorations
516+
517+
Returns the TextJustifier so the caller can reuse it for
518+
additional positioning (e.g. axis titles).
519+
"""
520+
plot_title_position = theme.getp("plot_title_position", "panel")
521+
plot_caption_position = theme.getp("plot_caption_position", "panel")
522+
plot_footer_position = theme.getp("plot_footer_position", "plot")
523+
justify = TextJustifier.from_boundaries(
524+
figure,
525+
plot_left=spaces.plot_left,
526+
plot_right=spaces.plot_right,
527+
plot_bottom=spaces.plot_bottom,
528+
plot_top=spaces.plot_top,
529+
panel_left=spaces.panel_left,
530+
panel_right=spaces.panel_right,
531+
panel_bottom=spaces.panel_bottom,
532+
panel_top=spaces.panel_top,
533+
)
534+
535+
if items.plot_title:
536+
ha = theme.getp(("plot_title", "ha"))
537+
items.plot_title.set_y(spaces.t.y2("plot_title"))
538+
justify.horizontally_about(items.plot_title, ha, plot_title_position)
539+
540+
if items.plot_subtitle:
541+
ha = theme.getp(("plot_subtitle", "ha"))
542+
items.plot_subtitle.set_y(spaces.t.y2("plot_subtitle"))
543+
justify.horizontally_about(
544+
items.plot_subtitle, ha, plot_title_position
545+
)
546+
547+
if items.plot_caption:
548+
ha = theme.getp(("plot_caption", "ha"), "right")
549+
items.plot_caption.set_y(spaces.b.y1("plot_caption"))
550+
justify.horizontally_about(
551+
items.plot_caption, ha, plot_caption_position
552+
)
553+
554+
if items.plot_footer:
555+
ha = theme.getp(("plot_footer", "ha"), "left")
556+
items.plot_footer.set_y(spaces.b.y1("plot_footer"))
557+
justify.horizontally_about(items.plot_footer, ha, plot_footer_position)
558+
if items.plot_footer_background:
559+
resize_footer_background(
560+
items.plot_footer_background,
561+
x=spaces.l.offset,
562+
y=spaces.b.offset,
563+
height=spaces.b.footer_height,
564+
width=spaces.plot_width,
565+
)
566+
if items.plot_footer_line:
567+
resize_footer_line(
568+
items.plot_footer_line,
569+
x=spaces.l.offset,
570+
width=spaces.plot_width,
571+
y=spaces.b.offset + spaces.b.footer_height,
572+
)
573+
574+
return justify
575+
576+
558577
def set_legends_position(legends: legend_artists, spaces: PlotSideSpaces):
559578
"""
560579
Place legend on the figure and justify is a required

plotnine/_mpl/layout_manager/_plot_side_space.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,38 @@ def panel_height(self) -> float:
833833
"""
834834
return self.t.panel_top - self.b.panel_bottom
835835

836+
@property
837+
def plot_left(self) -> float:
838+
return self.l.plot_left
839+
840+
@property
841+
def plot_right(self) -> float:
842+
return self.r.plot_right
843+
844+
@property
845+
def plot_bottom(self) -> float:
846+
return self.b.plot_bottom
847+
848+
@property
849+
def plot_top(self) -> float:
850+
return self.t.plot_top
851+
852+
@property
853+
def panel_left(self) -> float:
854+
return self.l.panel_left
855+
856+
@property
857+
def panel_right(self) -> float:
858+
return self.r.panel_right
859+
860+
@property
861+
def panel_bottom(self) -> float:
862+
return self.b.panel_bottom
863+
864+
@property
865+
def panel_top(self) -> float:
866+
return self.t.panel_top
867+
836868
@property
837869
def horizontal_space(self) -> float:
838870
"""

0 commit comments

Comments
 (0)