Preserve path breaks during interpolation - #1116
Closed
has2k1 wants to merge 1483 commits into
Closed
Conversation
(cherry picked from commit 7a65e28)
Add a scale_position mixin that validates and carries the axis position
("left"/"right" for y, "top"/"bottom" for x), and a scale_position_view
that propagates the resolved position through coord_cartesian to the coord.
Add per-side axis theme hierarchy (axis_text/ticks/line/title each gain _x_top/_x_bottom, _y_left/_y_right children under the existing parent). Each child styles its own side's artist; coord.setup_ax makes the active side's ticks/spine visible per scale.position. coord_flip rotates the position with the swap. Delete the dead mpl<3.10 tick-param shim.
… axes Side-scoped extent accessors on the layout items (label1/label2, tick1line/tick2line via side_artists); top_space/right_space gain axis attrs and the side-spaces read the scoped accessors + side-scoped margins; per-side axis-title placement; tick text justified into the band past the panel's far edge. Anchor top/right titles at the band's panel edge (y1/x1, not y2/x2, which cropped them) and align them across compositions via axis_title_alignment, matching bottom/left. Default plots are byte-identical.
The axis text/title gap is the single margin between the text and the panel. Each side reads the margin edge that actually faces the panel: top for a bottom axis, bottom for a top axis, right for a left axis, left for a right axis. The value is read from the side-scoped themeable (axis_text_x_top, axis_title_y_right, ...), which cascades to the parent by default, so a user can override one side's margin without touching the others. The themes (gray, matplotlib, seaborn) set both edges of the axis text/title margins so every position has a gap. Docstrings updated to match. Default plots unchanged.
New baselines for the axis-position feature: x_axis_top_continuous, y_axis_right_continuous, x_axis_top_discrete, coord_flip_x_top, facet_wrap_y_right. theme_seaborn is refreshed for the per-side axis text/title margins.
Remove the temporal coupling where Layout.setup pre-assigned self.facet.plot = plot so axis_positions() could reach self.plot.scales before facet.setup(plot) ran at draw time. axis_positions() now takes `scales` explicitly and uses an explicit `is None` check (replacing getattr) so a present scale missing `position` becomes a type error rather than a silent default. compute_layout gains `axis_positions: tuple[str, str]` in the base class and both subclasses (facet_grid, facet_wrap, facet_null). Layout.setup resolves the positions from plot.scales and passes them in, dropping the early assignment.
Transform bounds, endpoints, and intercepts with `x` and `y` so radial reversal and `theta="y"` preserve their dimensions. Let geoms select transformed dimensions through the coordinate-system contract.
Measure the largest visible polar tick label in each dimension and add the larger configured label margin or non-blank tick length. Reserve that band on every panel edge because polar axes follow arcs and spokes rather than Cartesian sides. This slightly shrinks every polar panel, so each baseline image changes.
A polar panel reserved its largest tick label on all four sides. One long label therefore cost a third of the panel and bought space on three sides that had nothing long on them. Each side now reserves the labels that reach past it, from where a label attaches to the panel, its own size, and the gap that holds it off its boundary. How much of that reach falls inside the panel depends on the panel the layout is still solving for, so it is taken from the previous render. A first render over-reserves by that difference rather than clipping. Polar baseline images shift again: the panels are larger.
Move label-bound measurement out of the theta tick class and define the full-circle and outward-direction calculations beside it. This gives polar label placement one coordinate convention without changing theta-label behaviour.
Theta labels were anchored from the nearest bounding-box corner. Between the cardinal directions, this moved the label edge away from its tick and made the gap depend on label width. Centre each label on its tick, then offset the point facing the panel by the themed padding.
Matplotlib's discrete alignments made the gap between an r label and its spoke depend on the spoke angle and label dimensions. Centre each label on its spoke, then offset the edge facing the panel by the themed padding. Apply this placement to full circles and both boundaries of an arc. Recompute the placement during each draw because it depends on rendered label dimensions. Recompute it before the final redraw so r labels retain the measured position above geoms.
Polar layout reserved each label's full width or height for every panel edge. Long labels therefore shrank the panel along edges they did not extend towards. Calculate clearance from each label's anchor and outward direction, and measure only the label pair assigned to each polar boundary. For full circles, anchor r-label clearance to the configured label spoke instead of the start of the theta range.
Make the polar axes responsible for identifying full-circle panels, locating r-axis spokes, and returning outward directions. Tick placement and layout now obtain this geometry from the panel instead of duplicating it or depending on Matplotlib's `PolarAxes` type.
The 42 images cover the polar suite: arcs and donuts, reversed theta and r, secondary theta and radial axes, per-side axis theming, and the panel spacing that long tick labels and large theta text demand.
Setting `axis_text_x_bottom` after blanking `axis_text_x` left the bottom labels hidden and omitted their layout space. Preserve the blank state on unset branches while allowing the specific element to render.
`theme_void` blanks both x-axis text sides. Verify that setting the bottom side draws its labels and reserves their layout space while the top side remains blank.
Specific theme elements now render and reserve layout space when their general theme element is blank.
plot_layout now accepts and merges axes and axis_title modes. These settings do not affect rendering yet.
Build regular and design-based grids through one composition method so layout consumers share the same item placement.
Let layers fill a label view without reaching through the complete plot object.
Resolve each title from its scale, mapping, coordinate, and secondary-axis settings while leaving unbuilt plots unchanged.
plot_layout(axes=...) hides inner axes with their ticks and labels. Matching titles follow axis_title, but the surviving title remains aligned to its own plot.
Use the combined panel bounds of plots that share a collected title. Keep plot-local alignment for uncollected titles.
Add visual cases for rows, columns, design gaps, nested compositions, facets, secondary axes, and independent title collection. Document the new layout settings.
A plot that retained a shared title also retained references to the other labelled plots, so copying it copied its neighbours as well. Store item indexes on the composition instead. During layout, resolve the title span from the plots' current panel bounds.
Saving `(p1 | p2).save("composition.png", dpi=200)` used the default
resolution. The `dpi` value was applied to an inner plot, but the
composition owns the figure and determines its resolution. Matplotlib's
save resolution could also override the requested value.
Non-linear coordinates interpolated each marked break as if it were the longest segment in the panel. The added points connected separate polygons and path groups with a visible edge. Marked breaks now keep only their starting point. Polygons and paths whose aesthetics vary along a line therefore stop at each group boundary.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1116 +/- ##
==========================================
+ Coverage 87.10% 88.00% +0.89%
==========================================
Files 208 215 +7
Lines 14373 16004 +1631
Branches 1794 2051 +257
==========================================
+ Hits 12520 14084 +1564
- Misses 1286 1317 +31
- Partials 567 603 +36 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Under a non-linear coordinate system, a
geom_polygonwith several groups draws an edge from one polygon to the next, and separategeom_pathgroups join up when an aesthetic varies along the line.Without this PR, a diagonal edge runs from the bottom-left corner of the first square to the second square.