The first feature-complete release of the core grammar. Everything is built on the same computed-geometry buffer, so static and interactive output can never disagree.
Nineteen further layers, closing most of the gap with the geoms an R user
expects to find: geom_rect(), geom_polygon(), geom_raster(),
geom_abline(), geom_blank(), geom_label(), geom_linerange(),
geom_crossbar(), geom_errorbarh(), geom_rug(), geom_curve(),
geom_spoke(), geom_count(), geom_freqpoly(), geom_function(),
geom_qq(), geom_qq_line(), geom_dotplot() and geom_quantile().
Quantile regression is fitted by minimising the check loss directly, so no linear-programming dependency is needed. 2D contouring/binning and spatial layers remain unimplemented.
coord_flip() swapped the marks but not the axes, so every flipped plot
was mislabelled.geom_sankey(), geom_chord(), geom_network(), geom_upset() and
geom_consort() ignored all of their styling arguments, because those
geoms build their marks inside the stat, which never saw the layer's
parameters.NA, NaN, Inf) reached the renderer and produced
invalid SVG such as cx="NA". They are now dropped with a warning
reporting the row count, matching what the histogram path already did.
Interval columns keep their NAs, which geom_forecast_band() relies on.geom_smooth() with too few observations wrote NaN coordinates; it now
refuses with a message naming the minimum for the chosen method.stats::density() errors; they now name
the geom and the offending group.stat_bin(bins = n) dropped empty bins, so n was only honoured when
every bin was occupied.geom_point(stat = stat_bin()) works.geom_spider_response(thresholds =), geom_smooth(alpha =) and
theme(grid_color_minor =) had no effect; gradient endpoints only
applied when both were set.labels vector of the
wrong length was recycled instead of reported.to_json() emitted a bare Inf, which is not valid JSON, and left C0
control characters unescaped.build_site(gallery = FALSE) rewrites the pages without redrawing the
figures, for quick iteration on prose. Figures an earlier build wrote are
kept rather than dropped.facet_wrap() and facet_grid(), with scales = "fixed" (default),
"free", "free_x", and "free_y". The geometry buffer now carries a
list of panels; a non-faceted plot is the one-panel case.coord_polar() — the engine behind radar charts, circular bar charts,
and pie/donut wedges — and coord_flip() for horizontal layouts.animate() is implemented: it builds one geometry buffer per level of
the transition variable, with axes fixed across frames, and the
interactive target plays them with a scrubber and play/pause.
(It previously warned and did nothing.)interact() gains brush = TRUE (drag a rectangle to zoom) and accepts
a character vector of data columns for tooltip.geom_*(), theme_*(), scale_*(), coord_*(), facet_*(),
labs(), interact(), animate() and plot_size() that takes at least
one argument now also accepts a plot as its first pipe argument:
p |> geom_point() |> theme_minimal() runs the same as
p + geom_point() + theme_minimal(). + remains the grammar's one true
composition operator — reusable Theme/Scale bundles built independently
of any plot, and Reduce(+, layers, p), both still work exactly as
before; the pipe form is an additive call-site convenience, not a
replacement. Non-standard evaluation is preserved, so
p |> facet_wrap(cyl) resolves cyl against the data exactly as
p + facet_wrap(cyl) always has. A handful of genuinely zero-argument
constructors (theme_minimal(), theme_classic(), theme_modern(),
theme_dark(), theme_void(), theme_ggnext(), coord_flip()) have no
parameter to route a piped plot through and stay +-only.validate_plot() checks a plot for common statistical-graphics mistakes —
an empty plot, a continuous geom (geom_point(), geom_line(), ...)
mapped to a categorical y, a mapped aesthetic that is mostly missing, a
discrete color/fill scale with more levels than a legend can usefully
show, and a sqrt-transformed scale fed negative values — and returns
the findings.plot_check() prints that report and returns the plot unchanged, so it
sits inside a pipeline: p |> plot_check() |> render().labs(), ggtitle(), xlab(), ylab() for the title block (title,
subtitle, caption, tag) and axis titles.xlim(), ylim(), lims() for quick limit setting.scale_x_continuous() / scale_y_continuous() gain breaks, labels
(a vector or a function), expand, and trans.scale_x_log10(), scale_x_sqrt(), scale_x_reverse()
and the y equivalents.scale_x_discrete() / scale_y_discrete() for explicit level ordering.scale_color_manual() and scale_color_gradient() for palette control.plot_size() and ggnext(width =, height =) for output dimensions.theme_ggnext(), theme_minimal(), theme_classic(),
theme_modern(), theme_dark(), theme_void().theme() now takes a base argument, so any preset can be restyled,
and exposes 35 settings covering colors, typography, grid and axis
visibility, legend position, and palettes.plot_data() returns exactly the values a plot draws — post-stat,
post-position, post-facet, in data units, with internal scratch columns
and constant grouping columns removed.write_plot_data() writes that table to CSV.geom_radar(), geom_ridgeline(), geom_sankey() /
geom_alluvial(), geom_treemap(), geom_network(), geom_chord(),
geom_parallel(), geom_bump(), geom_funnel(), geom_stream(),
geom_upset().geom_shap(), geom_partial_dependence(),
geom_confusion_matrix(), geom_calibration(), geom_lift_gain(),
geom_residual(), geom_learning_curve(), geom_silhouette(),
geom_embedding(), geom_decision_boundary(), geom_forecast_band().geom_forest(), geom_swimmer(),
geom_spider_response(), geom_waterfall_response(),
geom_bland_altman(), geom_cuminc(), geom_spaghetti(),
geom_ae_heatmap(), geom_dose_response(), geom_shift(),
geom_consort().Layout algorithms are implemented from scratch: squarified treemaps (Bruls, Huizing & van Wijk), Fruchterman-Reingold force layout, Sankey node stacking with a single shared value scale, Aalen-Johansen cumulative incidence, and four-parameter log-logistic dose-response fitting.
circle, line, rect, polygon, text) are
now supported by both render targets; a new geom needs no renderer
changes.build_site() generates the documentation site, rendering every gallery
figure with ggnext itself so the gallery cannot drift from the code.ggnext_logo() draws the hex sticker with the package's own SVG writer.aes(color = "blue") is honored as a literal color rather than being
treated as a single-level category and assigned the first palette entry.aes(color = class) when class is not a column) now raises an
error naming the problem and listing the available columns, instead of
failing later with "attempt to replicate an object of type 'builtin'".print methods are re-registered in .onLoad(); S7's own
method registration was dropping them, so render() printed the full
document source instead of a summary.Initial de-risking spike: S7 class hierarchy, geom_point(), one
continuous linear scale, Cartesian coordinates, stat_identity(), the
hand-rolled SVG writer, and the interactive canvas target.