Skip to content

Commit c4f621f

Browse files
committed
Draw insets into the host figure
Adds ggplot._draw_insets to render each attached inset into the host's figure as the final step of draw(). Refactors _create_figure on both ggplot and Compose into independent guards for the figure and the gridspec so a pre-assigned figure (an inset reusing its host's) still flows through to gridspec creation. Adds a _zorder class default on ggplot and Compose that the inset path raises above the host.
1 parent 3cc576a commit c4f621f

2 files changed

Lines changed: 56 additions & 18 deletions

File tree

plotnine/composition/_compose.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ class Compose:
130130
"""
131131
_sidespaces: CompositionSideSpaces
132132

133+
_zorder: int = 0
134+
"""
135+
Drawing zorder for every axes in this composition
136+
137+
It is propagated down the tree at draw time so sub-plots inherit their
138+
parent's value, and raised on inset compositions so their axes paint
139+
above the host.
140+
"""
141+
133142
def __init__(self, items: list[ggplot | Compose]):
134143
# The way we handle the plots has consequences that would
135144
# prevent having a duplicate plot in the composition.
@@ -461,19 +470,21 @@ def _create_figure(self):
461470
"""
462471
Create figure & gridspecs for all sub compositions
463472
"""
464-
if hasattr(self, "figure"):
465-
return
473+
if not hasattr(self, "figure"):
474+
import matplotlib.pyplot as plt
466475

467-
import matplotlib.pyplot as plt
476+
from plotnine._mpl.layout_manager import PlotnineLayoutEngine
468477

469-
from plotnine._mpl.gridspec import p9GridSpec
470-
from plotnine._mpl.layout_manager import PlotnineLayoutEngine
478+
self.figure = plt.figure()
479+
self.figure.set_layout_engine(PlotnineLayoutEngine(self))
471480

472-
figure = plt.figure()
473-
self._generate_gridspecs(
474-
figure, p9GridSpec(1, 1, figure, nest_into=None)
475-
)
476-
figure.set_layout_engine(PlotnineLayoutEngine(self))
481+
if not hasattr(self, "_gridspec"):
482+
from plotnine._mpl.gridspec import p9GridSpec
483+
484+
self._generate_gridspecs(
485+
self.figure,
486+
p9GridSpec(1, 1, self.figure, nest_into=None),
487+
)
477488

478489
def _generate_gridspecs(self, figure: Figure, container_gs: p9GridSpec):
479490
from plotnine import ggplot

plotnine/ggplot.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ class ggplot:
135135

136136
_sidespaces: PlotSideSpaces
137137

138+
_zorder: int = 0
139+
"""
140+
Drawing zorder for every axes created for this plot
141+
142+
The default (``0``) keeps the plot at the base layer. `_draw_insets`
143+
raises this on inset plots so they render above their host.
144+
"""
145+
138146
def __init__(
139147
self,
140148
data: Optional[DataLike] = None,
@@ -373,6 +381,10 @@ def draw(self, *, show: bool = False) -> Figure:
373381
# Artist object theming
374382
self.theme.apply()
375383

384+
# Insets render after host theming is finalised so their
385+
# own draw picks up a fully-realised host figure.
386+
self._draw_insets()
387+
376388
return figure
377389

378390
def _setup(self) -> Figure:
@@ -387,17 +399,18 @@ def _create_figure(self):
387399
"""
388400
Create gridspec for the panels
389401
"""
390-
if hasattr(self, "figure"):
391-
return
402+
if not hasattr(self, "figure"):
403+
import matplotlib.pyplot as plt
392404

393-
import matplotlib.pyplot as plt
405+
from ._mpl.layout_manager import PlotnineLayoutEngine
394406

395-
from ._mpl.gridspec import p9GridSpec
396-
from ._mpl.layout_manager import PlotnineLayoutEngine
407+
self.figure = plt.figure()
408+
self.figure.set_layout_engine(PlotnineLayoutEngine(self))
397409

398-
self.figure = plt.figure()
399-
self._gridspec = p9GridSpec(1, 1, self.figure)
400-
self.figure.set_layout_engine(PlotnineLayoutEngine(self))
410+
if not hasattr(self, "_gridspec"):
411+
from ._mpl.gridspec import p9GridSpec
412+
413+
self._gridspec = p9GridSpec(1, 1, self.figure)
401414

402415
def _build(self):
403416
"""
@@ -593,6 +606,20 @@ def _draw_watermarks(self):
593606
for wm in self.watermarks:
594607
wm.draw(self.figure)
595608

609+
def _draw_insets(self):
610+
"""
611+
Draw every inset attached to this plot into the host figure
612+
613+
Each inset reuses the host's figure instead of creating its own.
614+
The inset's zorder is raised above the host's so its axes paint
615+
on top; for Compose insets this zorder is propagated down the
616+
sub-tree when the composition is drawn.
617+
"""
618+
for inset in self._insets:
619+
inset.obj.figure = self.figure
620+
inset.obj._zorder = self._zorder + 1
621+
inset.obj.draw()
622+
596623
def _draw_plot_background(self):
597624
from matplotlib.lines import Line2D
598625
from matplotlib.patches import Rectangle

0 commit comments

Comments
 (0)