Skip to content

Commit

Permalink
Add plot_tag
Browse files Browse the repository at this point in the history
  • Loading branch information
has2k1 committed Nov 14, 2024
1 parent eb1f7dc commit d41c110
Show file tree
Hide file tree
Showing 21 changed files with 318 additions and 8 deletions.
3 changes: 3 additions & 0 deletions doc/_quartodoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ quartodoc:
- plot_margin_right
- plot_margin_top
- plot_subtitle
- plot_tag
- plot_tag_location
- plot_tag_position
- plot_title
- plot_title_position
- rect
Expand Down
21 changes: 21 additions & 0 deletions doc/changelog.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ title: Changelog
You can now position the `plot_title`, `plot_subtitle` and `plot_caption`
by alignment them with respect to the plot. ({{< issue 838 >}})

- Gained new label `tag` to create a tag for a plot. You can set it with
[](:class:`~plotnine.labs`) e.g.

```python
labs(tag="A")
```
and customise it using these themeables

[](:class:`~plotnine.themes.themeables.plot_tag`) and
[](:class:`~plotnine.themes.themeables.plot_tag_position`) and
[](:class:`~plotnine.themes.themeables.plot_tag_location`)

e.g.

```python
theme(
plot_tag="plot",
plot_tag_location="plot",
plot_tag_position="topright",
)
```

## v0.14.1
(2024-11-05)
Expand Down
50 changes: 50 additions & 0 deletions plotnine/_mpl/layout_manager/_layout_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from matplotlib.backend_bases import RendererBase
from matplotlib.text import Text

from plotnine.exceptions import PlotnineError

from ..utils import (
bbox_in_figure_space,
get_transPanels,
Expand Down Expand Up @@ -198,6 +200,7 @@ def get(name: str) -> Any:
self.plot_caption: Text | None = get("plot_caption")
self.plot_subtitle: Text | None = get("plot_subtitle")
self.plot_title: Text | None = get("plot_title")
self.plot_tag: Text | None = get("plot_tag")
self.strip_text_x: list[StripText] | None = get("strip_text_x")
self.strip_text_y: list[StripText] | None = get("strip_text_y")

Expand Down Expand Up @@ -453,6 +456,9 @@ def _adjust_positions(self, spaces: LayoutSpaces):
plot_title_position = theme.getp("plot_title_position", "panel")
plot_caption_position = theme.getp("plot_caption_position", "panel")

if self.plot_tag:
set_plot_tag_position(self.plot_tag, spaces)

if self.plot_title:
ha = theme.getp(("plot_title", "ha"))
self.plot_title.set_y(spaces.t.y2("plot_title"))
Expand Down Expand Up @@ -632,3 +638,47 @@ def set_position(
transPanels = get_transPanels(figure)
for l in legends.inside:
set_position(l.box, l.position, l.justification, transPanels)


def set_plot_tag_position(tag: Text, spaces: LayoutSpaces):
figure = spaces.plot.figure
theme = spaces.plot.theme
location = theme.getp("plot_tag_location")
position = theme.getp("plot_tag_position")

lookup = {
"topleft": (0, 1),
"top": (0.5, 1),
"topright": (1, 1),
"left": (0, 0.5),
"right": (1, 0.5),
"bottomleft": (0, 0),
"bottom": (0.5, 0),
"bottomright": (1, 0),
}

if isinstance(position, str):
if location == "margin":
(x1, y1), (x2, y2) = spaces.margin_area_coordinates
elif location == "plot":
(x1, y1), (x2, y2) = spaces.plot_area_coordinates
else:
(x1, y1), (x2, y2) = spaces.panel_area_coordinates

fx, fy = lookup[position]
width, height = spaces.items.calc.size(tag)
x = x1 * (1 - fx) + (x2 - width) * fx
y = y1 * (1 - fy) + (y2 - height) * fy

tag.set_position((x, y))
tag.set_horizontalalignment("left")
tag.set_verticalalignment("bottom")
else:
if location == "panel":
tag.set_transform(get_transPanels(figure))
elif location == "margin":
raise PlotnineError(
f"Cannot have plot_tag_location={location!r} if "
f"plot_tag_position={position!r}."
)
tag.set_position(position)
82 changes: 82 additions & 0 deletions plotnine/_mpl/layout_manager/_spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class left_spaces(_side_spaces):
"""

plot_margin: float = 0
plot_tag: float = 0
legend: float = 0
legend_box_spacing: float = 0
axis_title_y: float = 0
Expand All @@ -151,8 +152,17 @@ def _calculate(self):
theme = self.items.plot.theme
calc = self.items.calc
items = self.items
has_margin_for_plot_tag = theme.getp(
"plot_tag_location"
) in "margin" and "left" in theme.getp("plot_tag_position")

self.plot_margin = theme.getp("plot_margin_left")

if items.plot_tag and has_margin_for_plot_tag:
self.plot_tag = calc.width(items.plot_tag) + theme.getp(
("plot_tag", "margin")
).get_as("r", "fig")

if items.legends and items.legends.left:
self.legend = self._legend_width
self.legend_box_spacing = theme.getp("legend_box_spacing")
Expand Down Expand Up @@ -218,15 +228,26 @@ class right_spaces(_side_spaces):
"""

plot_margin: float = 0
plot_tag: float = 0
legend: float = 0
legend_box_spacing: float = 0
strip_text_y_width_right: float = 0

def _calculate(self):
items = self.items
theme = self.items.plot.theme
calc = self.items.calc
has_margin_for_plot_tag = theme.getp(
"plot_tag_location"
) in "margin" and "right" in theme.getp("plot_tag_position")

self.plot_margin = theme.getp("plot_margin_right")

if items.plot_tag and has_margin_for_plot_tag:
self.plot_tag = calc.width(items.plot_tag) + theme.getp(
("plot_tag", "margin")
).get_as("l", "fig")

if items.legends and items.legends.right:
self.legend = self._legend_width
self.legend_box_spacing = theme.getp("legend_box_spacing")
Expand Down Expand Up @@ -284,6 +305,7 @@ class top_spaces(_side_spaces):
"""

plot_margin: float = 0
plot_tag: float = 0
plot_title: float = 0
plot_title_margin_bottom: float = 0
plot_subtitle: float = 0
Expand All @@ -298,8 +320,18 @@ def _calculate(self):
calc = self.items.calc
W, H = theme.getp("figure_size")
F = W / H
has_margin_for_plot_tag = theme.getp(
"plot_tag_location"
) in "margin" and "top" in theme.getp("plot_tag_position")

self.plot_margin = theme.getp("plot_margin_top") * F

if items.plot_tag and has_margin_for_plot_tag:
self.plot_tag = (
calc.height(items.plot_tag)
+ theme.getp(("plot_tag", "margin")).get_as("b", "fig") * F
)

if items.plot_title:
self.plot_title = calc.height(items.plot_title)
self.plot_title_margin_bottom = (
Expand Down Expand Up @@ -369,6 +401,7 @@ class bottom_spaces(_side_spaces):
"""

plot_margin: float = 0
plot_tag: float = 0
plot_caption: float = 0
plot_caption_margin_top: float = 0
legend: float = 0
Expand All @@ -384,9 +417,18 @@ def _calculate(self):
calc = self.items.calc
W, H = theme.getp("figure_size")
F = W / H
has_margin_for_plot_tag = theme.getp(
"plot_tag_location"
) in "margin" and "bottom" in theme.getp("plot_tag_position")

self.plot_margin = theme.getp("plot_margin_bottom") * F

if items.plot_tag and has_margin_for_plot_tag:
self.plot_tag = (
calc.height(items.plot_tag)
+ theme.getp(("plot_tag", "margin")).get_as("t", "fig") * F
)

if items.plot_caption:
self.plot_caption = calc.height(items.plot_caption)
self.plot_caption_margin_top = (
Expand Down Expand Up @@ -543,6 +585,46 @@ def increase_vertical_plot_margin(self, dh: float):
self.t.plot_margin += dh
self.b.plot_margin += dh

@property
def margin_area_coordinates(
self,
) -> tuple[tuple[float, float], tuple[float, float]]:
"""
Lower-left and upper-right coordinates of the margin area
This is the area surrounded by the plot_margin and extended
to make space for the plot_tag.
"""
# Note the plot_area calculation will account the area
# taken up by the tag.
return self.plot_area_coordinates

@property
def plot_area_coordinates(
self,
) -> tuple[tuple[float, float], tuple[float, float]]:
"""
Lower-left and upper-right coordinates of the plot area
This is the area surrounded by the plot_margin.
"""
x1, x2 = self.l.x2("plot_margin"), self.r.x1("plot_margin")
y1, y2 = self.b.y2("plot_margin"), self.t.y1("plot_margin")
return ((x1, y1), (x2, y2))

@property
def panel_area_coordinates(
self,
) -> tuple[tuple[float, float], tuple[float, float]]:
"""
Lower-left and upper-right coordinates of the panel area
This is the area in which the panels are drawn.
"""
x1, x2 = self.l.left, self.r.right
y1, y2 = self.b.bottom, self.t.top
return ((x1, y1), (x2, y2))

def _calculate_panel_spacing(self) -> GridSpecParams:
"""
Spacing between the panels (wspace & hspace)
Expand Down
4 changes: 4 additions & 0 deletions plotnine/ggplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ def _draw_figure_texts(self):
title = self.labels.get("title", "")
subtitle = self.labels.get("subtitle", "")
caption = self.labels.get("caption", "")
tag = self.labels.get("tag", "")

# Get the axis labels (default or specified by user)
# and let the coordinate modify them e.g. flip
Expand All @@ -508,6 +509,9 @@ def _draw_figure_texts(self):
if caption:
targets.plot_caption = figure.text(0, 0, caption)

if tag:
targets.plot_tag = figure.text(0, 0, tag)

if labels.x:
targets.axis_title_x = figure.text(0, 0, labels.x)

Expand Down
1 change: 1 addition & 0 deletions plotnine/iapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class labels_view:
title: Optional[str] = None
caption: Optional[str] = None
subtitle: Optional[str] = None
tag: Optional[str] = None

def update(self, other: labels_view):
"""
Expand Down
5 changes: 5 additions & 0 deletions plotnine/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ class labs:
The caption at the bottom of the plot.
"""

tag: str | None = None
"""
A plot tag
"""

def __post_init__(self):
kwargs: dict[str, str] = {
f.name: value
Expand Down
1 change: 1 addition & 0 deletions plotnine/themes/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ThemeTargets:
panel_border: list[Rectangle] = field(default_factory=list)
plot_caption: Optional[Text] = None
plot_subtitle: Optional[Text] = None
plot_tag: Optional[Text] = None
plot_title: Optional[Text] = None
strip_background_x: list[StripTextPatch] = field(default_factory=list)
strip_background_y: list[StripTextPatch] = field(default_factory=list)
Expand Down
3 changes: 3 additions & 0 deletions plotnine/themes/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,11 @@ def __init__(
plot_title=None,
plot_subtitle=None,
plot_caption=None,
plot_tag=None,
plot_title_position=None,
plot_caption_position=None,
plot_tag_location=None,
plot_tag_position=None,
strip_text_x=None,
strip_text_y=None,
strip_text=None,
Expand Down
7 changes: 7 additions & 0 deletions plotnine/themes/theme_gray.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,15 @@ def __init__(self, base_size=11, base_family=None):
ma="left",
margin={"b": m, "units": "fig"},
),
plot_tag=element_text(
size=base_size * 1.2,
va="center",
ha="center",
),
plot_title_position="panel",
plot_caption_position="panel",
plot_tag_location="margin",
plot_tag_position="topleft",
strip_align=0,
strip_background=element_rect(color="none", fill="#D9D9D9"),
strip_background_x=element_rect(width=1),
Expand Down
7 changes: 7 additions & 0 deletions plotnine/themes/theme_matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,15 @@ def __init__(self, rc=None, fname=None, use_defaults=True):
ma="left",
margin={"b": m, "units": "fig"},
),
plot_tag=element_text(
size=base_size * 1.2,
va="center",
ha="center",
),
plot_title_position="panel",
plot_caption_position="panel",
plot_tag_location="margin",
plot_tag_position="topleft",
strip_align=0,
strip_background=element_rect(
fill="#D9D9D9", color="black", size=linewidth
Expand Down
7 changes: 7 additions & 0 deletions plotnine/themes/theme_seaborn.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,15 @@ def __init__(
ma="left",
margin={"b": m, "units": "fig"},
),
plot_tag=element_text(
size=base_size * 1.2,
va="center",
ha="center",
),
plot_title_position="panel",
plot_caption_position="panel",
plot_tag_location="margin",
plot_tag_position="topleft",
strip_align=0,
strip_background=element_rect(color="none", fill="#D1CDDF"),
strip_text=element_text(
Expand Down
7 changes: 7 additions & 0 deletions plotnine/themes/theme_void.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,15 @@ def __init__(self, base_size=11, base_family=None):
ma="left",
margin={"b": m, "units": "fig"},
),
plot_tag=element_text(
size=base_size * 1.2,
va="center",
ha="center",
),
plot_title_position="panel",
plot_caption_position="panel",
plot_tag_location="margin",
plot_tag_position="topleft",
strip_align=0,
strip_text=element_text(
color="#1A1A1A",
Expand Down
Loading

0 comments on commit d41c110

Please sign in to comment.