Repository navigation
v0.16.0 — hardened SVG output, slim dependency tree, host-integration features
Breaking changes
- The library is now an
rlibonly (was["cdylib", "rlib"]), which
unblocks static / emscripten side-module consumers. The wasm-bindgen browser
exports (ggplot_rs::wasm::*:render_geo,render_plot,render_bar,
render_hist,geo_bounds,scatter_frame,render_scatter_rgba,
render_scatter_xy) moved to the new, unpublished workspace crate
crates/ggplot-rs-wasm(cdylib). Build the browser bundle with
wasm-pack build crates/ggplot-rs-wasm --target web --out-dir ../../web/pkg --out-name ggplot_rs.
The library no longer depends onwasm-bindgen. - The
wasmfeature is now a deprecated alias forsf(kept so
features = ["wasm"]dependents keep compiling); it no longer adds
wasm-bindgen/serde_json. - New default-on feature
plotters(implies the newpngfeature) gates
the plotters/image-backed APIs:GGPlot::{render_svg, render_svg_with_size, render_png, render_png_with_size, save, save_with_size, ggsave},
ggpubr::{ggarrange_png, ggarrange_save_png}and
render::plotters_backend. Crates usingdefault-features = falsethat call
these must addfeatures = ["plotters"]; the self-contained
render_svg_native*path is always available.canvasnow impliespng.
cliimpliesplotters. DataFrame::add_columnno longer panics on a length mismatch: it pads the
shorter side withValue::Naand records an issue
(DataFrame::issues()/validate());GGPlot::try_buildand every
render_*/save*then returnGGError::ValidationError. This is reachable
fromGGPlot::new(Vec<(String, Vec<Value>)>)with ragged columns, which used
to panic. NewDataFrame::try_add_columnrejects the column instead.DataFrame::group_by/unique_valueskeepValue::Nadistinct from the
literal string"NA"(they used to merge into one group).label_number(…, prefix, …)is now sign-aware:-5with prefix"€"
renders"-€5"(was"€-5"), and a value rounding to zero drops its sign.PositionJittergained aseed: u64field, andwidth/heightare now
Option<f64>(None= 0.4 × the data's resolution, as in ggplot2). Build it
withPositionJitter::new(w, h),PositionJitter::default(),.with_width(),
.with_height()and.with_seed()instead of a struct literal.
GeomJitter::{width, height}are likewiseOption<f64>(defaultNone).- Jitter and
mean_cl_bootnow draw from an internal SplitMix64 stream instead
ofrand::StdRng: output is still deterministic, but the exact jittered
positions / bootstrap intervals differ from 0.15.
Added
-
ggplot_rs::format::format_value(&Value) -> String— the public tooltip /
data-label value formatter (previously the crate-privategeom::tip_value);
ggplot_rs::formatalso re-exports thelabel_*formatters. -
label_currency(prefix, suffix, big_mark, accuracy)— sign-aware currency
labels with a configurable thousands mark ("-€1.234.567").label_dollar
is now implemented on top of it (output unchanged). -
tests/native_svg.rs: smoke coverage of the plotters-free SVG path across
geoms/coords/facets/themes, run in every feature configuration. -
Value::group_key() -> GroupKey(injective w.r.t. missingness, borrows
strings) andValue::key_str() -> Cow<str>(allocation-free form of
to_group_key). -
DataFrame::try_add_column,DataFrame::issues,DataFrame::validate.
Changed
- Minimal dependency tree (
default-features = false, features = ["sf"]):
38 crates → 3 (indexmap,equivalent,hashbrown). No plotters, image,
ab_glyph, chrono, rand or getrandom. - Removed the
randdependency (and thewasm32-onlygetrandomdependency)
from the library; randomness comes from a small internal seeded PRNG. - Default jitter amount is resolution-based (ggplot2's
0.4 * resolution(x)):
integer-spaced data keeps ±0.4, finely spaced continuous data is no longer
smeared by a fixed ±0.4 data units.
Performance
stat_densityswitches to a binned estimator (linear binning onto a grid
with spacing ≤ bw/50 + a kernel table truncated at ±8 bw) for groups with
≥ 2048 values: O(n + 512·window) instead of O(n·512) kernel evaluations,
within ~2e-5 of the exact estimate relative to the peak. Smaller groups use
the exact sum, unchanged.- Layers without their own data borrow the plot data instead of cloning the
whole frame per layer. - Discrete scale training/lookups,
stat_count,position_stackand
position_filluse borrowed keys and hash lookups instead of allocating a
Stringper value and scanning the distinct x values linearly per row
(O(n·k) → O(n)). DataFrame::group_bybuilds borrowed keys (no per-rowStringclones).
Fixed
-
Empty manual scales (
scale_color_manual(vec![]),scale_fill_manual,
scale_shape_manual,scale_linetype_manual) no longer panic with a
division by zero; the geom falls back to its default colour/shape/linetype. -
format_epoch_secsis total overi64(it overflowed fori64::MIN). -
stat_binhex/geom_hexoutput order is deterministic (row-major) instead
of followingHashMapiteration order, so identical data renders identical
SVG. -
label_number/label_currencywith anaccuracythat keeps decimals no
longer inserts thousands marks into the fractional part
(1234.5@ 0.01 →"1,234.50", was"1,23,4.50"-style garbage).
Rendering robustness
- An empty plot (zero-row data, no columns, or no layers) renders an empty
panel with its title/axes instead of failing with
ValidationError("geom_… requires aesthetic …"). - A layer whose stat produces no rows (a density/violin of one value, loess on
two points, …) draws nothing; the other layers still render. - Rows with non-finite position values (
NaN,±Inf, or a transform that is
undefined such aslog10(0)) are dropped before stats, like ggplot2's
"Removed n rows containing non-finite values". Stat output is filtered the
same way. - Build warnings are exposed:
BuiltPlot::warnings()and
GGPlot::render_svg_native_with_warnings(w, h) -> (String, Vec<String>). - The native SVG backend never writes
NaN/infinto an attribute: marks with
non-finite geometry are dropped and non-finite style values fall back to
defaults. - Density/violin bandwidth follows R's
bw.nrd0, including its fallbacks for
zero-spread samples (previously a constant sample produced NaN). - A position scale's transform now applies to its whole column family
(ymin/ymax/yend, …), not onlyx/y. - Faceted panels keep per-mark hover metadata (tooltips /
data-x).
SVG host integration
- Hoverable marks carry
data-series(colour/fill/group level) and
data-value(the raw measured value; for stacked/filled bars the segment's
own value) next todata-x. Stacked-bar tooltips now report the segment
value rather than the cumulative stack top. - The root
<svg>carries the trained, expanded position domains:
data-domain="x0 x1 y0 y1"(both axes continuous),data-xdomain,
data-ydomain,data-xlevels/data-ylevels(JSON arrays for discrete axes)
anddata-flip="true"undercoord_flip. GGPlot::render_svg_native_at(x, y, w, h)renders a nested, positioned
<svg x y width height viewBox>fragment withoutxmlns, for composing
dashboards without string surgery (SvgBackend::finish_fragment).
Rect bounds
-Inf/Inf(f64::NEG_INFINITY/f64::INFINITY) ingeom_rect/
annotate("rect")xmin/xmax/ymin/ymaxextend the rect to the panel
edge (ggplot2) and do not train scales — also on discrete axes. New
Geom::allows_infinite()hook; other geoms still drop infinite positions.
Theme / colour
- Theme presets (
theme_minimal(),theme_void(), …) andtheme(t)keep a
previously setprimary_color(unless the new theme sets its own), so call
order no longer matters. primary_colorno longer overrides colours of geoms added with
geom_*_with(...)(Layer::explicit_style); default-configured geoms still
take the brand colour.ScaleColorDiscrete::sorted(),GGPlot::scale_fill_discrete_sorted()and
scale_color_discrete_sorted(): discrete colour levels in sorted order, so a
level keeps its palette colour across charts regardless of data order.
Tiles
geom_tileon a continuous axis is sized by the data resolution (smallest
gap between distinct values, like ggplot2'sresolution());width/height
are now multiples of it (default 1 = abutting tiles). The tile extents
(xmin/xmax/ymin/ymax) train the scales, so edge tiles no longer spill
past the axes and nowith_expand(…)workaround is needed. New
Geom::setup_data()hook (ggplot2'ssetup_data). Free facet scales also
train on extent columns.
New chart types
geom_candlestick()/geom_ohlc()(+_with): financial charts from the
newopen/high/low/closeaesthetics (Aes::open()…,
Aesthetic::{Open, High, Low, Close}); configurableup/downcolours;
the y scale trains on high–low; hover carriesdata-value= close and
data-series=up/down(or the mapped group).coord_radar()/CoordRadar: radar/spider charts — discrete x → spokes
(first level at 12 o'clock), y → radius from 0, straight segments, rings at
the y breaks, spokes and outside labels.geom_polygonnow groups by
group, elsecolor/fill(no longer requiresgroup), strokes with the
mapped colour, and orders radar vertices by spoke;geom_linecloses its
path under radar and orders points by mapped x position. New
Coord::is_radar()/Coord::train_scales()hooks.- Gauges:
CoordPolar::with_span(start, end)sweeps the theta range over a
partial arc (e.g.-PI/2..PI/2for a half donut) and fits the arc to the
panel;geom_rectdraws annulus sectors under polar coords, so a gauge is
geom_rectbands + ageom_segmentneedle. - Calendar heatmaps:
geom_calendar()/geom_calendar_with(tile, monday_first)
andStatCalendarlay a datex(DateTime, epoch seconds or
YYYY-MM-DD) out as week × weekday tiles with month labels, Mon/Wed/Fri
labels, month boundary outlines and square cells; spans over
MAX_CALENDAR_YEARS(50) are clipped to the most recent years with a
warning. Tiles' hover key (data-x, tooltip) is the ISO date. geom_stepaccepts±Inf(stat_ecdf's padding reaches the panel edges
again).- Gallery:
candlestick,radar,gauge,calendarexamples.