Skip to content

Commit 0d85e56

Browse files
committed
Broadcast & / * from a host plot into its insets
`(host + inset_element(p, ...)) & theme(...)` now themes both the host and the inset; `*` is host-only. Both operators are guarded on `_insets` and return `NotImplemented` on a bare ggplot, so the existing `+` remains the answer there. The broadcast lives on `Insets.__and__`, mirroring how `Compose.__and__` iterates `self.items`. `Compose.__and__` now descends into ggplot children that carry insets so themes broadcast on a composition reach every attached inset.
1 parent 8f6bfb5 commit 0d85e56

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

plotnine/composition/_compose.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,20 +284,25 @@ def __and__(self, rhs: PlotAddable) -> Self:
284284
"""
285285
Add rhs to all plots in the composition
286286
287+
Recurses into ggplot insets too: a plot with insets receives
288+
`item & rhs` (which broadcasts to its own host and insets).
289+
287290
Parameters
288291
----------
289292
rhs:
290293
What to add.
291294
"""
292-
from plotnine import theme
295+
from plotnine import ggplot, theme
293296

294297
self = deepcopy(self)
295298

296299
if isinstance(rhs, theme):
297300
self.annotation.theme = self.annotation.theme + rhs
298301

299302
for i, item in enumerate(self):
300-
if isinstance(item, Compose):
303+
if isinstance(item, Compose) or (
304+
isinstance(item, ggplot) and item._insets
305+
):
301306
self[i] = item & rhs
302307
else:
303308
item += copy(rhs)

plotnine/composition/_inset_element.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,23 @@ def draw(self, which: Literal["above", "below"]):
262262

263263
for inset in insets:
264264
inset._draw_in_host()
265+
266+
def __and__(self, rhs) -> Insets:
267+
"""
268+
Apply rhs to every inset's obj, recursing into nested structure
269+
270+
Insets that themselves have insets receive `obj & rhs` so the
271+
broadcast reaches every nested child.
272+
"""
273+
from ..ggplot import ggplot
274+
from ._compose import Compose
275+
276+
new = Insets(deepcopy(self))
277+
for inset in new:
278+
if isinstance(inset.obj, Compose) or (
279+
isinstance(inset.obj, ggplot) and inset.obj._insets
280+
):
281+
inset.obj = inset.obj & rhs
282+
else:
283+
inset.obj = inset.obj + rhs
284+
return new

plotnine/ggplot.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,35 @@ def __truediv__(self, rhs: Self | Compose) -> Compose:
312312

313313
return Stack([self, rhs])
314314

315+
def __and__(self, rhs: PlotAddable) -> Self:
316+
"""
317+
Broadcast rhs to this plot and every inset
318+
319+
Only defined when the plot has insets. On a plot with no insets
320+
use `+` instead.
321+
"""
322+
if not self._insets:
323+
return NotImplemented
324+
325+
new = deepcopy(self)
326+
new += rhs
327+
new._insets = new._insets & rhs
328+
return new
329+
330+
def __mul__(self, rhs: PlotAddable) -> Self:
331+
"""
332+
Apply rhs to this plot only, leaving insets untouched
333+
334+
Only defined when the plot has insets. On a plot with no insets
335+
use `+` instead.
336+
"""
337+
if not self._insets:
338+
return NotImplemented
339+
340+
new = deepcopy(self)
341+
new += rhs
342+
return new
343+
315344
def __sub__(self, rhs: Self | Compose) -> Compose:
316345
"""
317346
Compose 2 plots columnwise

0 commit comments

Comments
 (0)