Repository navigation
v0.23.0
Minor bump: per-panel dodge/jitter, negative stacking, legend wrap for
grids/multiple guides, vertical legend fit. Default output stays byte-
identical except for the deliberate changes under "Changed", each guarded
by a test.
Added
DrawBackend::draw_text_with_title(the default draws the text and
drops the title; the native SVG backend writes the<title>) andrender::text::truncate_to_width(a newpub fnin the publicrender::textmodule): draws (or measures) a text
run with an attached full-text<title>tooltip, used by #159's label
truncation below to keep a truncated label's full text available via
hover even though the drawn text itself is shortened. The truncated
label is a plain text run, not agg-mark: an SVG consumer that treats
"has a<title>" as "is a data mark" should select ongg-markinstead.Position::group_order(defaultNone) and
PositionParams::group_order: the layer-wide group order a faceted
dodge/dodge2 ranks its groups by (#153, see Changed).tests/default_output_023.rs, on the same pattern as
tests/default_output_022.rs: a battery of fixed plots exercising
#153/#154/#155/#159's deliberate changes plus their byte-identical
unaffected counterparts, hashed with FNV-1a. Wired into CI'ssf,
no-default-features test job alongsidedefault_output_022. Extended
(review #167) with aPositionFillmixed-sign case alongside
PositionStack's, andtests/position_panels_r.rsgained a dodge-
order case with two panels listing their groups in opposite row order
(#153 finding 2), andtests/legend_wrap.rsgained aPlotGrid
collected-legend case with two simultaneous discrete guides (#155
finding 1).
Breaking
PositionParamshas a new public field,group_order: Option<Vec<String>>(#153). Code that buildsPositionParamswith a
struct literal must addgroup_order: None(or use
..Default::default()).cargo semver-checksflags this as
constructible_struct_adds_field; it's the only API break.
Deprecated
guide::legend::draw_legendis deprecated (#[deprecated(since = "0.23.0")])
and will be removed in a later minor release: it's unused internally as
of this release — #155's rerouting ofcompose::capture_legendto the
crate-internaldraw_legend_planned(so aPlotGrid's collected legend
gets the same wrap/grid treatment a single plot's legend does) removed
draw_legend's only caller. There is no direct public replacement
(draw_legend_plannedispub(crate)); legends are drawn automatically
byGGPlot's own render methods, which was always the supported way to
get a legend onto a plot.
Changed
- A faceted
position_dodge/position_dodge2now computes each panel's
group count/offset from that panel's own rows instead of the rows
pooled across panels (#153), matching ggplot2 4.0.3 (verified with
layer_data(): a group missing from one panel's x level no longer
shifts the groups that panel does have). Group rank — which group
dodges left vs right — still comes from the whole layer's first-
appearance order (review #167 finding 2), not from each panel's own
row order, so every panel agrees, as in ggplot2 4.0.3. (ggplot2 ranks by
the sortedgroupid, so the two still differ when the data's first
appearance order isn't sorted; 0.22.2 behaved the same.) A panel
whose rows happen to list two groups in a different order than
another panel still draws a given group on the same side in both. A
facetedposition_jitter's default width/height (resolution(x) * 0.4) is now likewise measured per panel — also verified with
layer_data(): two panels with very different x spacing get
differently-scaled jitter even under shared, fixed facet scales. A
plot with 2+ distinct facet panels and a dodge or jitter layer may
render differently; an unfaceted plot, or one with a single panel, is
unaffected (tests/position_panels_r.rs, including
facet_dodge_order_is_global_not_per_panel_first_appearance). position_stack/position_fillnow split a group's positive and
negative values at each x into two independent stacks instead of one
pooled running total (#154), matching ggplot2's internalpos/neg
split (collide/pos_stack): positives stack upward from 0 using only
the positive sum as the total (orposition_fill's normalisation
denominator), negatives stack downward from 0 using only the
(absolute) negative sum, each with its ownvjustanchoring, verified
against ggplot2 4.0.3 withlayer_data()(a 4-group bar at one x with y = 3,
-2, 4, -1:position_stack(vjust = 0.5)gives y/ymin/ymax (5.5, 4, 7),
(-2, -3, -1), (2, 0, 4), (-0.5, -1, 0);position_fill(vjust = 0)gives
(4/7, 4/7, 1), (-1, -1, -1/3), (0, 0, 4/7), (-1/3, -1/3, 0)). All-
positive (or all-negative) data at an x is byte-identical — the other
sign's bucket is simply empty (src/position/stack.rs,
src/position/fill.rsunit tests).- A plot's auto-wrap/grid legend layout (0.22) now applies to every
simultaneous discrete guide independently, not only when the plot has
exactly one (#155): two discrete guides (e.g.colourandshapeboth
mapped to discrete variables) laid out side by side each wrap against
an equal share of the available width, and a vertical explicit
nrow/ncolgrid on either reserves its own extra width. Because
GuideLegend(the.guides(...)argument) is a single, global
setting, an explicitnrow/ncolon it now applies to every
discrete guide, including one sitting next to a continuous guide
(e.g. a colourbar plus a discreteshape) — a discrete guide next to
a continuous one now participates in auto-wrap/grid too. In 0.22 an
explicitnrow/ncolwas silently ignored as soon as the plot had
two or more legend scales of any kind, discrete+discrete or
discrete+continuous. APlotGrid'scollect_legends(true)strip also
now wraps its collected legend against the grid's own page size,
instead of always drawing the plain, unwrapped path — previously the
only legend eligible for wrap/grid was a single plot's single discrete
guide, rendered directly (not collected into a grid strip); two
simultaneous discrete guides collected this way are laid out side by
side, each against its own equal share of the page width, the same as
a single plot's legend (review #167 finding 1 —capture_legend
measures the collected legend against a fake zero-width panel, so the
step to the next guide must use the shareplan_all_legendsactually
planned against, not that panel's own width). A plot with one discrete
guide that already fit is unaffected (tests/legend_wrap.rs's
two_discrete_guides_each_auto_wrap,
plotgrid_collected_legend_auto_wrapsand
plotgrid_collected_legend_two_guides_sit_side_by_sidecover the new
cases). - A vertical legend's explicit
ncolgrid that still runs off the canvas
after reserving(cols - 1) * col_pitch(0.22's "warn only" check) now
tries harder before giving up (#159): it first reduces the column
count toward 1, keeping the first count that fits; if even one column
overflows, it truncates labels with an ellipsis instead (the full label
stays available via a<title>tooltip on the label text, escaped the
same way every other text run is —DrawBackend::draw_text_with_title, new,
default no-op, SVG-only); only if it still doesn't fit after both does
it warn, as before. A grid that already fits renders byte-identical to
0.22.2 — no reduction, no truncation, no warning
(tests/legend_wrap.rs'sa_vertical_grid_that_already_fits_is_byte_identical,
plus the reduce/truncate/still-warns cases replacing
vertical_legend_ncol_grid_too_wide_for_the_canvas_warns, which
asserted the old "always warns" behaviour).