Repository navigation
v0.24.0
Minor bump: geom_col() and geom_area() now stack by default, like
ggplot2 (an output change; see Migration below), plus legend-layout
and missing-value stacking fixes from the #167 review, each guarded by a
test.
Changed
geom_col()'s default position is nowposition_stack(), matching
geom_bar()'s (#169) — ggplot2 defaultsgeom_coltoposition_stack
too, and 0.22/0.23 left it atPositionIdentity, a parity gap called
out in review #167 finding 3 and tracked as #169. Migration: a plot
that callsgeom_col()with more than one group (e.g. a mapped
fill/colour) at the same x and relies on the old identity behaviour
(bars drawn on top of each other fromy = 0, not stacked) must now
add an explicit.position(PositionIdentity)to keep that output; a
plot that already set.position(...)explicitly, or that never has
more than one group at a shared x, is unaffected. TheggplotCLI's
--geom colchanges the same way (src/bin/ggplot.rs, same
GGPlot::geom_col()call).tests/default_output_023.rs's
all_positive_stackcase (geom_col()'s default, two groups at one
x) exercises this — see its updated expected hash and data-fn comment;
tests/issue169_geom_col_default_stack.rschecks the default matches
an explicitPositionStackand no longer matchesPositionIdentity.
tests/integration.rs'stest_layer_position_override_buildswitched
fromgeom_col()togeom_point()to keep testing an override of an
identity default.geom_area()'s default position is nowposition_stack()too,
matching ggplot2 (review #167 finding 5 on #171) — the same parity gap
as #169, landed in this same minor so every default-position break
ships in one release instead of trickling in across several.
Migration: a plot that callsgeom_area()with more than one
series (a mappedfill/colour/group) at the same x and relies on
the old identity behaviour (bands drawn on top of each other from
y = 0, not stacked) must now add an explicit
.position(PositionIdentity); a plot with only one series, or that
already set.position(...)explicitly, is unaffected. Theggplot
CLI's--geom areachanges the same way.tests/default_output_022.rs's
areacase changes too, for an unrelated reason worth calling out: its
fixture (band_data()) happens to carry its own pre-existingymin
column (there forgeom_ribbon's case, not this one), which
PositionIdentityalways left untouched butPositionStacknow
overwrites with the (single-series, all-zero) baseline it computes —
see that file's updated expected hash and docstring note.
Known limitation: ggplot2 pairsgeom_areawithstat_align
(each series interpolated onto the union of x before stacking);
ggplot-rs stacks by exact x, so a series missing an x makes the series
above it dip at that x (#172).
tests/geom_area_default_stack.rschecks the default matches an
explicitPositionStackand no longer matchesPositionIdentity.
Fixed
- A vertical legend's explicit
ncolgrid that auto-reduces its column
count to fit (#159) now reclaims the panel width that reduction no
longer needs, instead of keeping the wider, pre-reduction column
count's reservation forever (#168, review #167 finding 5 on #171).
Reclaiming it is a single bounded retry, not an unconditional
recompute: the tighter reservation is re-checked against the same fit
test the reduction loop uses, against every currently-planned guide
— not only the guide(s) the reduction loop itself reduced — and is
kept only if every one of them still fits under it. A follow-up review
round caught that first cut checking only the reduced guides:legend_x
moves for every guide sharing the row once the reservation tightens,
not just the one(s) reduction touched, so a second guide that already
fit at the old, widerlegend_x(its base width estimate is a fixed
6·text_size, unrelated to the reduced guide's own labels), or whose
labels were truncated against that oldlegend_x, shifted right by
the same amount with nothing re-checking it — up to 40px off-canvas
with no warning in a 7,992-plot sweep, 7.3% of cases with two
simultaneous guides. Checking every plan removed all of those. A grid
that would overflow at the tighter reservation even after that (the
pre-existing #159 limitation where an explicitncol(1)grid has no
fit check of its own) keeps the wider, already-safe reservation
instead, so this can only shrink the reservation when doing so is
provably safe, never regress a plot that already rendered correctly.
tests/legend_wrap.rs'slegend_panel_width_is_reclaimed_after_column_reduction
andlegend_panel_width_reservation_is_not_loosened_when_unsafecover
the single-guide case (both sides);two_guides_ncol_retighten_repro_a_stays_on_canvas
and..._repro_b_stays_on_canvascover the two-guide regression
(untouched guide and truncated guide respectively). - A bottom/top legend mixing a colourbar (a continuous
colour/fill
scale) with a discrete guide now leaves the discrete guide only the
row width left over after the colourbar's own fixed share, instead of
splitting the whole row width among the discrete guides alone — the
wrapped discrete grid could still run past the canvas once the
colourbar's share was added back on top (review #167 finding 7).
tests/legend_wrap.rs'sbottom_legend_colourbar_leaves_room_for_the_discrete_guide. - A
PlotGrid's collected-legend path (collect_legends(true)) now
propagates the warnings its own legend planning produces (e.g. an
invalidnrow(0)/ncol(0)on the collected legend), instead of
silently discarding them — unlike the same setting on a single plot's
own legend, which already warned (review #167 finding 6).
tests/legend_wrap.rs'splotgrid_collected_legend_propagates_its_own_warnings. - The legend bbox pre-pass used to measure a
PlotGrid's collected
legend (compose::capture_legend's internalMeasurebackend) now
forwards a drawn label's<title>tooltip to its inner backend
instead of silently dropping it (review #167 finding 4) — latent
today (a collected legend's labels never get 0.23's truncate-with-
<title>treatment yet), but would otherwise have resurfaced as soon
as they did.src/compose.rs's
measure_forwards_the_title_to_its_inner_backendunit test. - A missing (
NaN/SQLNULL)yis now skipped inposition_stack
andposition_fill(it contributes 0 to its bucket's running
total/denominator) and its owny/ymin/ymaxare leftNaN— not
drawn — matching ggplot2'spos_stack/position_fillexactly (review
#167 finding 2/3 on #171).±Infis treated as missing the same way.
This only matters forgeom_line/geom_step/
geom_path, which keep a missing row as a line break instead of
dropping it (Geom::keeps_na_as_break); every other geom already drops
non-finite rows beforeStat/Positionever see them. Also fixes a
pre-existing, unrelated bug the same code touches: aValue::Nay
(SQLNULL, as opposed to a floatNaN) used to fall back to0.0
viaas_f64().unwrap_or(0.0)and get stacked as a real point at the
top of its bucket — a line drawn straight through a row the "removed 1
row" warning said was gone.src/position/stack.rs's
missing_y_contributes_zero_and_does_not_poison_its_bucketand
value_na_is_treated_like_float_nan_not_zero,src/position/fill.rs's
sibling test, andtests/position_stack_r.rs's
stacked_line_with_a_missing_y_leaves_other_series_intact_at_that_x
(an end-to-endgeom_lineregression, sincekeeps_na_as_breakis
what makes this reachable at all).