Skip to content

Commit

Permalink
Add plot_title/caption_position themeables
Browse files Browse the repository at this point in the history
closes #586
closes #838
  • Loading branch information
has2k1 committed Nov 12, 2024
1 parent 78d368e commit eb4d42f
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 50 deletions.
2 changes: 2 additions & 0 deletions doc/_quartodoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -458,13 +458,15 @@ quartodoc:
- panel_spacing_y
- plot_background
- plot_caption
- plot_caption_position
- plot_margin
- plot_margin_bottom
- plot_margin_left
- plot_margin_right
- plot_margin_top
- plot_subtitle
- plot_title
- plot_title_position
- rect
- strip_align
- strip_align_x
Expand Down
21 changes: 21 additions & 0 deletions doc/changelog.qmd
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
---
title: Changelog
---
---
## v0.15.0
(not-yet-released)

### New Features

- Using
[](:class:`~plotnine.themes.themeables.plot_title_position`) and
[](:class:`~plotnine.themes.themeables.plot_caption_position`) e.g.

```python
theme(
plot_title_position="plot",
plot_caption_position="plot",
)
```

You can now position the `plot_title`, `plot_subtitle` and `plot_caption`
by alignment them with respect to the plot. ({{< issue 838 >}})


## v0.14.1
(2024-11-05)

Expand Down
58 changes: 43 additions & 15 deletions plotnine/_mpl/layout_manager/_layout_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,31 +450,39 @@ def _adjust_positions(self, spaces: LayoutSpaces):
Set the x,y position of the artists around the panels
"""
theme = self.plot.theme
plot_title_position = theme.getp("plot_title_position", "panel")
plot_caption_position = theme.getp("plot_caption_position", "panel")

if self.plot_title:
ha = theme.getp(("plot_title", "ha"))
self.plot_title.set_y(spaces.t.edge("plot_title"))
horizontally_align_text_with_panels(self.plot_title, ha, spaces)
horizontally_align_text(
self.plot_title, ha, spaces, plot_title_position
)

if self.plot_subtitle:
ha = theme.getp(("plot_subtitle", "ha"))
self.plot_subtitle.set_y(spaces.t.edge("plot_subtitle"))
horizontally_align_text_with_panels(self.plot_subtitle, ha, spaces)
horizontally_align_text(
self.plot_subtitle, ha, spaces, plot_title_position
)

if self.plot_caption:
ha = theme.getp(("plot_caption", "ha"), "right")
self.plot_caption.set_y(spaces.b.edge("plot_caption"))
horizontally_align_text_with_panels(self.plot_caption, ha, spaces)
horizontally_align_text(
self.plot_caption, ha, spaces, plot_caption_position
)

if self.axis_title_x:
ha = theme.getp(("axis_title_x", "ha"), "center")
self.axis_title_x.set_y(spaces.b.edge("axis_title_x"))
horizontally_align_text_with_panels(self.axis_title_x, ha, spaces)
horizontally_align_text(self.axis_title_x, ha, spaces)

if self.axis_title_y:
va = theme.getp(("axis_title_y", "va"), "center")
self.axis_title_y.set_x(spaces.l.edge("axis_title_y"))
vertically_align_text_with_panels(self.axis_title_y, va, spaces)
vertically_align_text(self.axis_title_y, va, spaces)

if self.legends:
set_legends_position(self.legends, spaces)
Expand All @@ -487,13 +495,17 @@ def _text_is_visible(text: Text) -> bool:
return text.get_visible() and text._text # type: ignore


def horizontally_align_text_with_panels(
text: Text, ha: str | float, spaces: LayoutSpaces
def horizontally_align_text(
text: Text,
ha: str | float,
spaces: LayoutSpaces,
how: Literal["panel", "plot"] = "panel",
):
"""
Horizontal justification
Reinterpret horizontal alignment to be justification about the panels.
Reinterpret horizontal alignment to be justification about the panels or
the plot (depending on the how parameter)
"""
if isinstance(ha, str):
lookup = {
Expand All @@ -505,20 +517,30 @@ def horizontally_align_text_with_panels(
else:
f = ha

params = spaces.gsparams
if how == "panel":
left = spaces.l.left
right = spaces.r.right
else:
left = spaces.l.plot_left
right = spaces.r.plot_right

width = spaces.items.calc.width(text)
x = params.left * (1 - f) + (params.right - width) * f
x = left * (1 - f) + (right - width) * f
text.set_x(x)
text.set_horizontalalignment("left")


def vertically_align_text_with_panels(
text: Text, va: str | float, spaces: LayoutSpaces
def vertically_align_text(
text: Text,
va: str | float,
spaces: LayoutSpaces,
how: Literal["panel", "plot"] = "panel",
):
"""
Vertical justification
Reinterpret vertical alignment to be justification about the panels.
Reinterpret vertical alignment to be justification about the panels or
the plot (depending on the how parameter).
"""
if isinstance(va, str):
lookup = {
Expand All @@ -532,9 +554,15 @@ def vertically_align_text_with_panels(
else:
f = va

params = spaces.gsparams
if how == "panel":
top = spaces.t.top
bottom = spaces.b.bottom
else:
top = spaces.t.plot_top
bottom = spaces.b.plot_bottom

height = spaces.items.calc.height(text)
y = params.bottom * (1 - f) + (params.top - height) * f
y = bottom * (1 - f) + (top - height) * f
text.set_y(y)
text.set_verticalalignment("bottom")

Expand Down
103 changes: 68 additions & 35 deletions plotnine/_mpl/layout_manager/_spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,20 @@ def edge(self, item: str) -> float:
"""
return self.sum_upto(item)

@property
def left(self):
"""
Left of the panels in figure space
"""
return self.total

@property
def plot_left(self):
"""
Distance in figure space from left edge upto where artists start
"""
return self.edge("legend")


@dataclass
class right_spaces(_side_spaces):
Expand Down Expand Up @@ -219,6 +233,20 @@ def edge(self, item: str) -> float:
"""
return 1 - self.sum_upto(item)

@property
def right(self):
"""
Right of the panels in figure space
"""
return 1 - self.total

@property
def plot_right(self):
"""
Distance in figure space from right edge upto where artists start
"""
return self.edge("legend")


@dataclass
class top_spaces(_side_spaces):
Expand Down Expand Up @@ -284,6 +312,20 @@ def edge(self, item: str) -> float:
"""
return 1 - self.sum_upto(item)

@property
def top(self):
"""
Top of the panels in figure space
"""
return 1 - self.total

@property
def plot_top(self):
"""
Distance in figure space from top edge upto where artists start
"""
return self.edge("legend")


@dataclass
class bottom_spaces(_side_spaces):
Expand Down Expand Up @@ -353,6 +395,20 @@ def edge(self, item: str) -> float:
"""
return self.sum_upto(item)

@property
def bottom(self):
"""
Bottom of the panels in figure space
"""
return self.total

@property
def plot_bottom(self):
"""
Distance in figure space from bottom edge upto where artists start
"""
return self.edge("legend")


@dataclass
class LayoutSpaces:
Expand Down Expand Up @@ -434,34 +490,6 @@ def __post_init__(self):
# Increase aspect ratio, wider panels
self._reduce_height(ratio)

@property
def left(self):
"""
Left of the panels in figure space
"""
return self.l.total

@property
def right(self):
"""
Right of the panels in figure space
"""
return 1 - self.r.total

@property
def top(self):
"""
Top of the panels in figure space
"""
return 1 - self.t.total

@property
def bottom(self):
"""
Bottom of the panels in figure space
"""
return self.b.total

def increase_horizontal_plot_margin(self, dw: float):
"""
Increase the plot_margin to the right & left of the panels
Expand Down Expand Up @@ -494,7 +522,12 @@ def _calculate_panel_spacing(self) -> GridSpecParams:
raise TypeError(f"Unknown type of facet: {type(self.plot.facet)}")

return GridSpecParams(
self.left, self.right, self.top, self.bottom, wspace, hspace
self.l.left,
self.r.right,
self.t.top,
self.b.bottom,
wspace,
hspace,
)

def _calculate_panel_spacing_facet_grid(self) -> tuple[float, float]:
Expand All @@ -513,8 +546,8 @@ def _calculate_panel_spacing_facet_grid(self) -> tuple[float, float]:
self.sh = theme.getp("panel_spacing_y") * self.W / self.H

# width and height of axes as fraction of figure width & height
self.w = ((self.right - self.left) - self.sw * (ncol - 1)) / ncol
self.h = ((self.top - self.bottom) - self.sh * (nrow - 1)) / nrow
self.w = ((self.r.right - self.l.left) - self.sw * (ncol - 1)) / ncol
self.h = ((self.t.top - self.b.bottom) - self.sh * (nrow - 1)) / nrow

# Spacing as fraction of axes width & height
wspace = self.sw / self.w
Expand Down Expand Up @@ -559,8 +592,8 @@ def _calculate_panel_spacing_facet_wrap(self) -> tuple[float, float]:
) + self.items.axis_ticks_y_max_width("all")

# width and height of axes as fraction of figure width & height
self.w = ((self.right - self.left) - self.sw * (ncol - 1)) / ncol
self.h = ((self.top - self.bottom) - self.sh * (nrow - 1)) / nrow
self.w = ((self.r.right - self.l.left) - self.sw * (ncol - 1)) / ncol
self.h = ((self.t.top - self.b.bottom) - self.sh * (nrow - 1)) / nrow

# Spacing as fraction of axes width & height
wspace = self.sw / self.w
Expand All @@ -571,8 +604,8 @@ def _calculate_panel_spacing_facet_null(self) -> tuple[float, float]:
"""
Calculate spacing parts for facet_null
"""
self.w = self.right - self.left
self.h = self.top - self.bottom
self.w = self.r.right - self.l.left
self.h = self.t.top - self.b.bottom
self.sw = 0
self.sh = 0
return 0, 0
Expand Down
2 changes: 2 additions & 0 deletions plotnine/themes/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ def __init__(
plot_title=None,
plot_subtitle=None,
plot_caption=None,
plot_title_position=None,
plot_caption_position=None,
strip_text_x=None,
strip_text_y=None,
strip_text=None,
Expand Down
2 changes: 2 additions & 0 deletions plotnine/themes/theme_gray.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ def __init__(self, base_size=11, base_family=None):
ma="left",
margin={"b": m, "units": "fig"},
),
plot_title_position="panel",
plot_caption_position="panel",
strip_align=0,
strip_background=element_rect(color="none", fill="#D9D9D9"),
strip_background_x=element_rect(width=1),
Expand Down
2 changes: 2 additions & 0 deletions plotnine/themes/theme_matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ def __init__(self, rc=None, fname=None, use_defaults=True):
ma="left",
margin={"b": m, "units": "fig"},
),
plot_title_position="panel",
plot_caption_position="panel",
strip_align=0,
strip_background=element_rect(
fill="#D9D9D9", color="black", size=linewidth
Expand Down
2 changes: 2 additions & 0 deletions plotnine/themes/theme_seaborn.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ def __init__(
ma="left",
margin={"b": m, "units": "fig"},
),
plot_title_position="panel",
plot_caption_position="panel",
strip_align=0,
strip_background=element_rect(color="none", fill="#D1CDDF"),
strip_text=element_text(
Expand Down
2 changes: 2 additions & 0 deletions plotnine/themes/theme_void.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ def __init__(self, base_size=11, base_family=None):
ma="left",
margin={"b": m, "units": "fig"},
),
plot_title_position="panel",
plot_caption_position="panel",
strip_align=0,
strip_text=element_text(
color="#1A1A1A",
Expand Down
Loading

0 comments on commit eb4d42f

Please sign in to comment.