Contour geoms - #1118
Merged
Merged
Contour geoms#1118
Conversation
Contour layers can now derive break values from a band count or width, accept explicit values, or call a user-supplied break function. The default selects values for ten bands across the surface range. Surface coordinates can also be arranged into sorted x and y axes, with missing cells represented by `nan`.
A bin count below one, a zero width, and a negative width previously reached dependencies and produced inconsistent exceptions, warnings, or empty output. Contour break selection now rejects each value immediately with a `PlotnineError`.
Gridded surfaces can now produce contour lines and the filled regions between neighbouring contour values. Each disconnected line or region receives its own plot group, and filled regions retain the rings needed to represent holes. The results include contour values, scaled values, piece identifiers, and band bounds suitable for computed-aesthetic mappings.
Equal break values made band labelling increase precision forever. Labelling now stops at float64 round-trip precision and raises a `PlotnineError` when the values remain indistinguishable. Line and band calculations that generate no vertices now return zero-row data with the same columns and categorical levels as populated results. Downstream stats can process empty contours without special cases.
Contour calculation requires rows and columns of x and y values. Rotated grids provide neither, so surface coordinates are now aligned with the axes before contouring and restored afterwards. Repeated coordinates retain their last value and emit a warning. Grid angle detection uses the dominant neighbour angle, with the longest convex-hull edge as a fallback.
The rotation test built its fixture from rounded coordinates, unlike the raw layer data used in practice. This difference could conceal failures at large coordinate magnitudes. The fixture now uses direct rotation arithmetic and covers a grid offset by 100,000 units, so angle estimation is tested against its actual input precision.
`geom_polygon` now treats each `subgroup` as one ring within a polygon. Oppositely wound inner rings render as holes, while the first ring forms the exterior. `subgroup` divides an existing plot group instead of creating a new one. Non-linear coordinates close and interpolate each ring independently.
Under non-linear coordinates, interpolation treated only plot-group changes as path breaks. It therefore connected a polygon's exterior ring to its holes. Ring changes now mark the same hard boundary as group changes. Boundary segments receive no interpolated points, so each ring ends before the next begins.
`geom_contour` maps a gridded surface to separate paths at selected heights. Its stat accepts x, y, and z values together with a band count, width, explicit values, or a break function. Facet panels share contour heights. Rotated grids are aligned before contouring, duplicate coordinates keep their last value, and missing cells remain unfilled.
When a rotated surface produced no contours, restoring its coordinates tried to reduce an empty array and raised. Rotation now returns empty arrays unchanged, so a valid empty contour result reaches the layer. Populated and empty results now follow the same coordinate-restoration path.
`geom_contour_filled` fills each region between neighbouring contour values. Disjoint regions receive separate polygon groups, and inner rings become holes through the `subgroup` aesthetic. The default mapping fills by ordered band interval. Computed variables expose each band's bounds, midpoint, scaled upper bound, piece, and ring.
Document `subgroup` as the ring index within each polygon piece. Ring 0 is the exterior; later rings are holes.
The group-apply utility annotated each extra positional argument as a tuple, so Pyright rejected valid scalar and array arguments. Its variadic parameter now accepts values of any type, matching its pass-through behaviour.
`stat_density_2d` can now contour `density`, `ndensity`, or `count`. It computes break values across the complete layer, so facet panels use comparable contour heights. Callers can choose a band count, width, explicit values, or a break function. The previous `levels` parameter is deprecated. Without contouring, the stat exposes the density measures and observation count directly.
The observation count is constant within a density group, so the contour stage restored it as if it were an original aesthetic. Other density-grid columns disappeared only because they varied by point. Contoured output now excludes every column created by density estimation, including `n`, while retaining original constant aesthetics.
`geom_density_2d_filled` now fills the regions between neighbouring contour values from a two-dimensional kernel density estimate. It supports the same contour variable and break controls as `geom_density_2d`. The default fill maps to ordered band intervals. Computed variables expose the density values, band bounds, pieces, and polygon rings available at each stage.
Document the density variables available before contouring and the band variables available afterwards. The band output includes interval labels, bounds, midpoints, scaled levels, polygon pieces, and ring indices.
Describe a piece as one disconnected polygon within a band, rather than as the complete band. Keep every computed-aesthetic description in the same grammatical form.
Rounding the estimated grid angle worked for small integer grids but moved realistic coordinates off their shared rows and columns. The gaps produced broken or missing contours. Angle selection now uses rounded values only to find the dominant direction, then returns the full-precision mean. The inverse rotation uses a wider rounding tolerance, so rotated grids realign without discarding excess precision.
Verify that facet panels share contour heights, missing surface values leave holes, and rotated grids produce the same complete contours as axis-aligned grids.
Document the new line, filled-surface, and filled-density contour geoms. Describe polygon holes through the `subgroup` aesthetic and the new contour controls and shared panel heights for two-dimensional density estimates.
Verify that a rotated grid at realistic coordinate scales recovers its original row and column counts. Approximate angle equality alone did not detect precision loss that separated aligned coordinates. Also contain the expected missing-value warning and confirm that both facet panels contribute data before checking their shared contour spacing. Clarify why the rotation tolerance depends on grid extent.
Contouring above the surface range or over identical points produced no rows and lost the layer's panel metadata. Plot building then raised a bare `KeyError` instead of drawing an empty panel. Empty contour results now retain their full schema and original constant columns. `ndensity` now scales by the largest non-missing density, so one missing estimate no longer makes the complete column missing.
Tables may leave `subgroup` missing for polygons that contain only one ring. Those rings were dropped during grouping, which either removed a hole without warning or raised an unrelated concatenation error. Missing values now identify one ring like any other. The ring renders in linear coordinates and remains continuous while non-linear coordinates interpolate its edges.
Single-cell grids, flat surfaces with a requested band count, and filled contours with only one break previously raised dependency errors or divided by zero. They now warn that no contours were generated and return an empty result with the expected schema. Explicit break values are now sorted and deduplicated. Break functions always receive a contour distance derived from the band count, the requested width, or one tenth of the range.
Passing deprecated `levels` with `bins` or `breaks` silently discarded the explicit replacement. The replacement now takes precedence, and the warning identifies which parameter was used. The deprecation now uses `FutureWarning` and states that `levels` will be removed in a future version.
Surface contouring needs the API that calculates several contour values in one call. The version supplied indirectly by Matplotlib does not guarantee that API, so declare `contourpy>=1.2.0` directly. Also document that two-dimensional density plots now use ten contour bands by default instead of five.
A polygon without holes can either number its only ring or leave its `subgroup` missing. Compare both encodings with the same reference image to state that they represent identical shapes. Cartesian coverage catches a ring dropped during grouping. Polar coverage catches a missing value incorrectly treated as a boundary at every vertex.
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.
This PR adds
geom_contour,geom_contour_filledandgeom_density_2d_filled, so you can draw a surface sampled on a grid as contour lines or filled bands. Todaygeom_density_2donly contours a density it computes itself from raw points, which leaves no way to show the shape of a dataset likefaithfuld, where adensityis already storedfor every
waiting/eruptionspair.( ggplot(faithfuld, aes("waiting", "eruptions", z="density")) + geom_contour_filled(bins=8) )This PR also gives
geom_polygonasubgroupaesthetic, so a filled band can have holes.One behaviour change to flag:
stat_density_2dnow splits density into ten bands instead of five and uses the same contour heights in every panel, so existinggeom_density_2dplots gain contour lines.levelsis deprecated in favour ofbinsandbreaks.closes #1084