Skip to content

Plot Insets (inset_element)#1058

Open
has2k1 wants to merge 9 commits intomainfrom
inset-element
Open

Plot Insets (inset_element)#1058
has2k1 wants to merge 9 commits intomainfrom
inset-element

Conversation

@has2k1
Copy link
Copy Markdown
Owner

@has2k1 has2k1 commented May 4, 2026

closes #38

@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 74.63768% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.71%. Comparing base (35e658a) to head (9dc3152).

Files with missing lines Patch % Lines
plotnine/composition/_inset_element.py 53.84% 16 Missing and 2 partials ⚠️
plotnine/_mpl/layout_manager/_plot_side_space.py 22.22% 13 Missing and 1 partial ⚠️
plotnine/watermark.py 70.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1058      +/-   ##
==========================================
- Coverage   86.88%   86.71%   -0.18%     
==========================================
  Files         203      204       +1     
  Lines       13768    13841      +73     
  Branches     1689     1701      +12     
==========================================
+ Hits        11963    12002      +39     
- Misses       1256     1287      +31     
- Partials      549      552       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

has2k1 added 8 commits May 6, 2026 23:23
Introduces the inset_element dataclass for placing a plot or composition
as an overlay on a host plot at NPC coordinates, and the ggplot._insets
list that stores them. Wiring into the draw and layout pipeline comes
in later commits.
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.
Adds PlotSideSpaces._arrange_insets, called as the final step of arrange
once the host's panel/plot regions are finalised. Each inset's
fractional bounding box is scaled into figure coordinates relative to
its align_to region and applied to the inset's own gridspec, then a
PlotSideSpaces or CompositionSideSpaces is built and arranged to lay
out the inset's internal content within those bounds.

Also updates inset_element's docstring to use 'fractional coordinates'
instead of 'normalised parent coordinates' for terminology familiar to
matplotlib users.
Pass the owning plot's _zorder to add_subplot in facet._make_axes so
every panel axes is born at the right layer. Compose.draw propagates
the composition's _zorder to its direct children before drawing, and
the recursion carries the value down sub-compositions. Combined with
ggplot._draw_insets raising _zorder by 1 per inset boundary, insets
(including Compose insets and nested insets) end up above the host
without any post-hoc subtree walk.
Every figure-level artist owned by a plot — panel borders, titles,
strip text, legends, watermarks, plus the plot/composition background
and footer rects — now goes through a single _add_figure_artist helper
on ggplot and Compose that offsets its zorder by the owning plot's
_zorder. This guarantees an inset's whole stack paints above the host's
whole stack, including the previously-missed strip backgrounds, titles,
and legends.

INSET_ZORDER_STEP is set to 1000 (well above the largest within-plot
zorder, watermarks at 99.9) so host and inset bands never overlap.

Watermark zorder is now managed entirely by plotnine; a user-supplied
value is dropped with a PlotnineWarning, and watermark.draw reads its
host's _zorder via the parent reference set in __radd__. plot_title and
the other figure-level texts are constructed as matplotlib.text.Text
and added through the same helper, dropping figure.text indirection.
The previous docstring placed titles/captions/legends in `"full"` and
plot margins in `"plot"`. The actual region semantics are the other way
around — titles/captions/legends are part of the plot region, and
`"full"` adds the plot margin on top. Update the docstring to match.
Sibling insets all received the same _zorder (host._zorder +
INSET_ZORDER_STEP), so their figure-level artists collided inside one
band: an earlier inset's panel_border, titles, axis titles and legend
sat above a later inset's panel because they had larger numeric zorders
than the panel itself. The "later inset fully covers earlier" intent
only held for the panel area, where equal-zorder ties broke by
insertion order.

inset_element._setup now takes the 1-based index among its siblings
and assigns _zorder = parent._zorder + index * INSET_ZORDER_STEP, so
each inset occupies its own band. Insets._setup enumerates and passes
the index through.

INSET_ZORDER_STEP shrinks from 1000 to 10 now that nothing inside a
single plot reaches 99.9 anymore: watermark drops from 99.9 to 9, and
the explicit zorder=99.1 on the legend's FlexibleAnchoredOffsetbox is
removed (the mpl default of 5 is already above all decorations and
below the watermark, and ggplot._add_figure_artist offsets it into the
right band). The 0.5 gap between an inset's watermark (band top) and
the next inset's plot_background (band bottom, -0.5) is the tightest
the geometry permits and is safe — no other artist falls in it.
An inset_element's `obj` and a Compose's items share the parent's
matplotlib figure, but their own theme.figure_size and dpi default
independently. That caused the inset's `figure_size` themeable to
resize the host figure, the inset's dpi to leak into rcParams during
its draw context, and layout sites (`_plot_side_space`,
`_composition_side_space`, `margin.setup`) to read inconsistent
values.

A new `theme._inherit_figure_props` method copies these
figure-owner-only values from a parent theme. inset_element._setup
calls it on the inset's theme; Compose.draw calls it on every item
alongside the existing zorder propagation. Nested cases compose by
recursion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Insets

1 participant