Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
47c4c9d
basic utilities
teunbrand Nov 25, 2025
2e5bac7
tie in editions with deprecation
teunbrand Nov 25, 2025
9cf93a5
add 2025 edition for testing purposes
teunbrand Nov 25, 2025
725cdbb
add utility for supersession
teunbrand Nov 25, 2025
b2e9f0d
use supersede utility
teunbrand Nov 25, 2025
7bdb67e
move `deprecate()` and `supersede()`
teunbrand Nov 25, 2025
5774a42
fix typos
teunbrand Nov 25, 2025
c0b8fb3
document `set_edition()`
teunbrand Nov 25, 2025
97df19f
rename file
teunbrand Nov 25, 2025
dccab71
redocument
teunbrand Nov 25, 2025
fefa82d
user facing funs first
teunbrand Nov 25, 2025
ca1f39e
fix bug
teunbrand Nov 25, 2025
7318b84
avoid `as.character(NULL)`
teunbrand Nov 25, 2025
90adcd0
add tests
teunbrand Nov 25, 2025
8b182b2
fix bug
teunbrand Nov 25, 2025
0c07343
fix bug
teunbrand Nov 25, 2025
df4c101
include test for `edition_require()`
teunbrand Nov 25, 2025
012c3e2
unrustle pkgdown's jimmies
teunbrand Nov 25, 2025
ee7a0c8
roll back defunctness comparison
teunbrand Nov 28, 2025
1419a96
use mocked test
teunbrand Nov 28, 2025
352c740
correct snapshot
teunbrand Nov 28, 2025
cf43eb3
Ensure example also unsets edition
teunbrand Nov 28, 2025
58fb727
rename `set_edition()` to `set_ggplot2_edition()` to avoid namespace …
teunbrand Dec 3, 2025
bb9a134
add withr-style edition options
teunbrand Dec 3, 2025
6e522b5
further avoidance of namespace clashes
teunbrand Dec 3, 2025
4724aec
Merge branch 'main' into edition_infrastructure
teunbrand Dec 5, 2025
9125d57
Document/export also related functions
teunbrand Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ Collate:
'utilities-break.R'
'utilities-grid.R'
'utilities-help.R'
'utilities-lifecycle.R'
'utilities-patterns.R'
'utilities-performance.R'
'utilities-resolution.R'
Expand Down
4 changes: 4 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ export(geom_vline)
export(get_alt_text)
export(get_element_tree)
export(get_geom_defaults)
export(get_ggplot2_edition)
export(get_guide_data)
export(get_labs)
export(get_last_plot)
Expand Down Expand Up @@ -493,6 +494,7 @@ export(layer_grob)
export(layer_scales)
export(layer_sf)
export(lims)
export(local_ggplot2_edition)
export(make_constructor)
export(map_data)
export(margin)
Expand Down Expand Up @@ -666,6 +668,7 @@ export(scale_y_reverse)
export(scale_y_sqrt)
export(scale_y_time)
export(sec_axis)
export(set_ggplot2_edition)
export(set_last_plot)
export(set_theme)
export(sf_transform_xy)
Expand Down Expand Up @@ -750,6 +753,7 @@ export(update_stat_defaults)
export(update_theme)
export(vars)
export(waiver)
export(with_ggplot2_edition)
export(wrap_dims)
export(xlab)
export(xlim)
Expand Down
2 changes: 1 addition & 1 deletion R/annotation-logticks.R
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ annotation_logticks <- function(base = 10, sides = "bl", outside = FALSE, scaled
if (!is.null(color))
colour <- color

lifecycle::signal_stage("superseded", "annotation_logticks()", "guide_axis_logticks()")
supersede("2026", "annotation_logticks()", "guide_axis_logticks()")

if (lifecycle::is_present(size)) {
deprecate("3.5.0", I("Using the `size` aesthetic in this geom"), I("`linewidth`"))
Expand Down
2 changes: 1 addition & 1 deletion R/coord-flip.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
#' geom_area() +
#' coord_flip()
coord_flip <- function(xlim = NULL, ylim = NULL, expand = TRUE, clip = "on") {
lifecycle::signal_stage("superseded", "coord_flip()")
supersede("2026", "coord_flip()", with = I("swapping x and y aesthetics"))
check_coord_limits(xlim)
check_coord_limits(ylim)
ggproto(NULL, CoordFlip,
Expand Down
2 changes: 1 addition & 1 deletion R/coord-map.R
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ coord_map <- function(projection="mercator", ..., parameters = NULL, orientation
} else {
params <- parameters
}
lifecycle::signal_stage("superseded", "coord_map()", "coord_sf()")
supersede("2026", "coord_map()", "coord_sf()")
check_coord_limits(xlim)
check_coord_limits(ylim)

Expand Down
2 changes: 1 addition & 1 deletion R/coord-polar.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
coord_polar <- function(theta = "x", start = 0, direction = 1, clip = "on") {
theta <- arg_match0(theta, c("x", "y"))
r <- if (theta == "x") "y" else "x"
lifecycle::signal_stage("superseded", "coord_polar()", "coord_radial()")
supersede("2026", "coord_polar()", "coord_radial()")

ggproto(NULL, CoordPolar,
theta = theta,
Expand Down
2 changes: 2 additions & 0 deletions R/ggplot-global.R
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ ggplot_global$x_aes <- c("x", "xmin", "xmax", "xend", "xintercept",

ggplot_global$y_aes <- c("y", "ymin", "ymax", "yend", "yintercept",
"ymin_final", "ymax_final", "lower", "middle", "upper", "y0")

ggplot_global$edition <- NULL
6 changes: 3 additions & 3 deletions R/labels.R
Original file line number Diff line number Diff line change
Expand Up @@ -239,21 +239,21 @@ labs <- function(..., title = waiver(), subtitle = waiver(),
#' superseded. It is recommended to use the `labs(x, y, title, subtitle)`
#' arguments instead.
xlab <- function(label) {
lifecycle::signal_stage("superseded", "xlab()", "labs(x)")
supersede("2026", "xlab()", "labs(x)")
labs(x = label)
}

#' @rdname labs
#' @export
ylab <- function(label) {
lifecycle::signal_stage("superseded", "ylab()", "labs(y)")
supersede("2026", "ylab()", "labs(y)")
labs(y = label)
}

#' @rdname labs
#' @export
ggtitle <- function(label, subtitle = waiver()) {
lifecycle::signal_stage("superseded", "ggtitle()", I("labs(title, subtitle)"))
supersede("2026", "ggtitle()", I("labs(title, subtitle)"))
labs(title = label, subtitle = subtitle)
}

Expand Down
3 changes: 2 additions & 1 deletion R/limits.R
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ limits.POSIXlt <- function(lims, var, call = caller_env()) {
expand_limits <- function(...) {
data <- list2(...)

lifecycle::signal_stage("superseded", "expand_limits()")

supersede("2026", "expand_limits()")

# unpack data frame columns
data_dfs <- vapply(data, is.data.frame, logical(1))
Expand Down
141 changes: 141 additions & 0 deletions R/utilities-lifecycle.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#' Set ggplot2 edition
#'
#' ggplot2 uses the 'edition' concept to manage the lifecycles of functions and
#' arguments. Setting a recent edition opens up the latest features but also
#' closes down deprecated and superseded functionality.
#'
#' @param edition An edition. Possible values currently include `"2026"` only.
#' Can be `NULL` (default) to unset an edition.
#'
#' @returns For `set_ggplot2_edition()`, the previous `edition` value and for
#' `local_ggplot2_edition()`: `NULL`. Thesef unction are called for the side
#' effect of setting the edition though. For `with_ggplot2_edition`, the
#' result of the evaluation. For `get_ggplot2_edition()`, the currently
#' active edition.
#'
#' @export
#' @keywords internal
#'
#' @examples
#' # Setting an edition
#' set_ggplot2_edition(2026)
#'
#' # Getting the edition
#' get_ggplot2_edition()
#'
#'
#' # Unsetting an edition
#' set_ggplot2_edition()
#'
#' # Using withr-like scoping
#' with_ggplot2_edition(2026, get_ggplot2_edition())
set_ggplot2_edition <- function(edition = NULL) {
old <- ggplot_global$edition
ggplot_global$edition <- validate_edition(edition)
invisible(old)
}

#' @export
#' @param env An `environment` to use for scoping.
#' @rdname set_ggplot2_edition
local_ggplot2_edition <- function(edition = NULL, env = parent.frame()) {
old <- set_ggplot2_edition(edition)
withr::defer(set_ggplot2_edition(old), envir = env)
}

#' @export
#' @param code Code to execute in the temporary environment.
#' @rdname set_ggplot2_edition
with_ggplot2_edition <- function(edition = NULL, code) {
local_ggplot2_edition(edition)
code
}

#' @export
#' @rdname set_ggplot2_edition
get_ggplot2_edition <- function() {
ggplot_global$edition[[1]]
}

# Any new editions should be appended here and anchored to a version
edition_versions <- c(
"2025" = "4.0.0",
"2026" = "4.1.0"
)

validate_edition <- function(edition, allow_null = TRUE, call = caller_env()) {
if (is.null(edition) && allow_null) {
return(NULL)
}
edition <- as.character(edition)
check_string(edition, allow_empty = FALSE, call = call)
arg_match0(edition, names(edition_versions), error_call = call)
}

edition_require <- function(edition = NULL, what, call = caller_env()) {
edition <- validate_edition(edition)
current_edition <- get_ggplot2_edition()
if (
!is.null(current_edition) &&
as.numeric(current_edition) >= as.numeric(edition)
) {
return(invisible())
}
cli::cli_abort(
"{what} requires the {edition} edition of {.pkg ggplot2}.",
call = call
)
}

deprecate <- function(when, ..., id = NULL, always = FALSE, user_env = NULL,
escalate = NULL) {

defunct <- "3.0.0"
full <- "3.4.0"
soft <- utils::packageVersion("ggplot2")

if (identical(escalate, "delay")) {
soft <- full
full <- defunct
defunct <- "0.0.0"
}

edition <- get_ggplot2_edition()
if (!is.null(edition) && edition %in% names(edition_versions)) {
soft <- full <- defunct <- edition_versions[[edition]]
}

version <- as.package_version(when)
if (version < defunct || identical(escalate, "abort")) {
lifecycle::deprecate_stop(when, ...)
}
user_env <- user_env %||% getOption("ggplot2_plot_env") %||% caller_env(2)
if (version <= full || identical(escalate, "warn")) {
lifecycle::deprecate_warn(when, ..., id = id, always = always, user_env = user_env)
} else if (version <= soft) {
lifecycle::deprecate_soft(when, ..., id = id, user_env = user_env)
}
invisible()
}

supersede <- function(edition, what, with = NULL, ..., env = caller_env()) {
current_edition <- get_ggplot2_edition()
if (
!is.null(current_edition) &&
current_edition %in% names(edition_versions) &&
as.numeric(current_edition) >= as.numeric(edition)
) {
lifecycle::deprecate_stop(
when = paste0("edition ", edition),
what = what,
with = with,
env = env
)
}
lifecycle::signal_stage(
stage = "superseded",
what = what,
with = with,
env = env
)
}
26 changes: 0 additions & 26 deletions R/utilities.R
Original file line number Diff line number Diff line change
Expand Up @@ -789,32 +789,6 @@ as_cli <- function(..., env = caller_env()) {
cli::cli_fmt(cli::cli_text(..., .envir = env))
}

deprecate <- function(when, ..., id = NULL, always = FALSE, user_env = NULL,
escalate = NULL) {

defunct <- "3.0.0"
full <- "3.4.0"
soft <- utils::packageVersion("ggplot2")

if (identical(escalate, "delay")) {
soft <- full
full <- defunct
defunct <- "0.0.0"
}

version <- as.package_version(when)
if (version < defunct || identical(escalate, "abort")) {
lifecycle::deprecate_stop(when, ...)
}
user_env <- user_env %||% getOption("ggplot2_plot_env") %||% caller_env(2)
if (version <= full || identical(escalate, "warn")) {
lifecycle::deprecate_warn(when, ..., id = id, always = always, user_env = user_env)
} else if (version <= soft) {
lifecycle::deprecate_soft(when, ..., id = id, user_env = user_env)
}
invisible()
}

as_unordered_factor <- function(x) {
x <- as.factor(x)
class(x) <- setdiff(class(x), "ordered")
Expand Down
52 changes: 52 additions & 0 deletions man/set_ggplot2_edition.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions tests/testthat/_snaps/utilities-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# editions can be set and unset

Code
set_ggplot2_edition("nonsense")
Condition
Error in `set_ggplot2_edition()`:
! `edition` must be one of "2025" or "2026", not "nonsense".

# edition deprecation works

`foo()` was deprecated in ggplot2 4.0.0.
i Please use `bar()` instead.

---

Code
foo()
Condition
Error:
! `foo()` was deprecated in ggplot2 4.0.0 and is now defunct.
i Please use `bar()` instead.

# edition supersession works

Code
foo()
Condition
Error:
! `foo()` was deprecated in ggplot2 edition 2025 and is now defunct.
i Please use `bar()` instead.

# edition requirements work

Code
foo()
Condition
Error in `foo()`:
! foo() requires the 2025 edition of ggplot2.

Loading