Skip to content

Commit 0795dd7

Browse files
committed
fix(inset): skip footer inset when host has no footer text
1 parent 519bac2 commit 0795dd7

6 files changed

Lines changed: 69 additions & 28 deletions

File tree

plotnine/_mpl/layout_manager/_plot_side_space.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414
from dataclasses import replace
1515
from functools import cached_property
1616
from typing import TYPE_CHECKING
17-
from warnings import warn
1817

19-
from plotnine.exceptions import PlotnineError, PlotnineWarning
18+
from plotnine.exceptions import PlotnineError
2019
from plotnine.facets import facet_grid, facet_null, facet_wrap
2120

2221
from ._plot_layout_items import PlotLayoutItems
@@ -806,17 +805,10 @@ def _arrange_insets(self):
806805
elif inset.align_to == "plot":
807806
(x1, y1), (x2, y2) = self.plot_area_coordinates
808807
elif inset.align_to == "footer":
809-
(x1, y1), (x2, y2) = self.footer_area_coordinates
810-
# footer_height is exactly 0.0 when there is no footer text
811-
if (y2 - y1) < 1e-9:
812-
warn(
813-
"An inset with align_to='footer' was placed, but "
814-
"the plot has no footer text so the footer band has "
815-
"no height. The inset will not be shown. Set a "
816-
"footer with labs(footer=...).",
817-
PlotnineWarning,
818-
)
808+
if not self.plot.labels.get("footer", ""):
819809
continue
810+
811+
(x1, y1), (x2, y2) = self.footer_area_coordinates
820812
else: # "full"
821813
# Note that this isn't necessarily the figure's coordinates,
822814
# rather the entire ggplot area.

plotnine/composition/_inset_element.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from copy import deepcopy
44
from dataclasses import dataclass
55
from typing import TYPE_CHECKING, Any, Literal
6+
from warnings import warn
67

8+
from ..exceptions import PlotnineWarning
79
from ._inset_image import _InsetImage
810

911
if TYPE_CHECKING:
@@ -255,10 +257,14 @@ class Insets(list[inset_element]):
255257
List of insets attached to a ggplot
256258
"""
257259

260+
# The host plot these insets are drawn into.
261+
_host: ggplot
262+
258263
def _setup(self, parent: ggplot):
259264
"""
260265
Inherit the host figure and figure-owner-only theme props
261266
"""
267+
self._host = parent
262268
for inset in self:
263269
inset._setup(parent)
264270

@@ -279,6 +285,16 @@ def draw(self, which: Literal["above", "below"]):
279285
insets = [inset for inset in self if not inset.on_top][::-1]
280286

281287
for inset in insets:
288+
if inset.align_to == "footer" and not self._host.labels.get(
289+
"footer", ""
290+
):
291+
warn(
292+
"An inset with align_to='footer' was placed, but the "
293+
"plot has no footer text. The inset will not be shown. "
294+
"Set a footer with labs(footer=...).",
295+
PlotnineWarning,
296+
)
297+
continue
282298
inset._draw_in_host()
283299

284300
def __and__(self, rhs) -> Insets:
1.83 KB
Loading
4.87 KB
Loading
3.18 KB
Loading

tests/test_inset_element.py

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import numpy as np
2+
import pandas as pd
23
import pytest
34
from PIL import Image, ImageDraw
45

56
from plotnine import (
7+
aes,
8+
coord_cartesian,
69
element_line,
710
element_rect,
811
element_text,
912
facet_wrap,
13+
geom_col,
14+
ggplot,
1015
labs,
1116
theme,
17+
theme_void,
1218
)
1319
from plotnine._utils.yippie import geom as g
1420
from plotnine._utils.yippie import plot
1521
from plotnine.composition import inset_element, plot_annotation
1622
from plotnine.exceptions import PlotnineWarning
23+
from plotnine.themes.elements import margin
1724

1825

1926
def _smiley() -> np.ndarray:
@@ -382,32 +389,58 @@ class TestFooterInset:
382389
align_to="footer" maps the inset bbox onto the footer band
383390
"""
384391

392+
p = (
393+
plot.white
394+
+ labs(footer="Source: Example Corp")
395+
+ theme(
396+
plot_margin=0.01,
397+
plot_footer=element_text(
398+
margin=margin(t=0.01, b=0.01, unit="fig")
399+
),
400+
plot_footer_background=element_rect(fill="#E9E9E9"),
401+
)
402+
)
403+
385404
def test_footer_logo_right_aligned(self):
386405
# A logo placed in the right portion of the footer band, inline
387406
# with left-justified footer text.
388-
p = (
389-
plot.white
390-
+ g.points
391-
+ labs(footer="Source: Example Corp")
392-
+ inset_element(
393-
SMILEY_FACE,
394-
left=0.9,
395-
bottom=0.0,
396-
right=1.0,
397-
top=1.0,
398-
align_to="footer",
399-
)
407+
p = self.p + inset_element(
408+
SMILEY_FACE,
409+
left=0.85,
410+
bottom=0.15,
411+
right=1 - 0.01,
412+
top=0.85,
413+
align_to="footer",
414+
anchor="right",
400415
)
401416
assert p == "footer_logo_right_aligned"
402417

418+
def test_footer_plot_right_aligned(self):
419+
# A plot placed in the right portion of the footer band, inline
420+
# with left-justified footer text.
421+
data = pd.DataFrame({"x": ["a", "b", "c"], "y": [1, 2, 3]})
422+
p_inset = (
423+
ggplot(data, aes("x", "y", fill="x"))
424+
+ geom_col(show_legend=False)
425+
+ coord_cartesian(expand=False)
426+
+ theme_void()
427+
)
428+
p = self.p + inset_element(
429+
p_inset,
430+
left=0.85,
431+
bottom=0.15,
432+
right=1 - 0.01,
433+
top=0.85,
434+
align_to="footer",
435+
)
436+
assert p == "footer_plot_right_aligned"
437+
403438
def test_footer_inset_without_footer_text(self):
404439
# No footer text -> degenerate footer band. The inset is skipped
405440
# and a warning is emitted; the rendered plot is just the host.
406441
# The == comparison draws the plot, which triggers the warning.
407-
p = (
408-
plot.white
409-
+ g.points
410-
+ inset_element(SMILEY_FACE, 0.9, 0.0, 1.0, 1.0, align_to="footer")
442+
p = plot.white + inset_element(
443+
SMILEY_FACE, 0.9, 0.0, 1.0, 1.0, align_to="footer"
411444
)
412445
with pytest.warns(PlotnineWarning, match="no footer text"):
413446
assert p == "footer_inset_no_footer_text"

0 commit comments

Comments
 (0)