Skip to content

Commit c8e1534

Browse files
authored
Merge branch 'main' into binned_zerorange
2 parents 42a0cd0 + 853266c commit c8e1534

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+929
-537
lines changed

.github/workflows/R-CMD-check.yaml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,8 @@ jobs:
6262
cache-version: 2
6363
extra-packages: >
6464
any::rcmdcheck,
65-
maps=?ignore-before-r=3.5.0,
6665
Hmisc=?ignore-before-r=3.6.0,
67-
mapproj=?ignore-before-r=3.5.0,
68-
multcomp=?ignore-before-r=3.5.0,
69-
quantreg=?ignore-before-r=3.5.0,
66+
quantreg=?ignore-before-r=3.6.0,
7067
needs: check
7168

7269
- uses: r-lib/actions/check-r-package@v2

NEWS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
* `scale_*_binned()` handles zero-range limits more gracefully (@teunbrand,
44
#5066)
5+
* `ggsave()` warns when multiple `filename`s are given, and only writes to the
6+
first file (@teunbrand, #5114).
7+
* Fixed a regression in `geom_hex()` where aesthetics were replicated across
8+
bins (@thomasp85, #5037 and #5044)
9+
* Fixed spurious warning when `weight` aesthetic was used in `stat_smooth()`
10+
(@teunbrand based on @clauswilke's suggestion, #5053).
11+
* The `lwd` alias now correctly replaced by `linewidth` instead of `size`
12+
(@teunbrand based on @clauswilke's suggestion #5051).
13+
* Fixed a regression in `Coord$train_panel_guides()` where names of guides were
14+
dropped (@maxsutton, #5063)
515

616
# ggplot2 3.4.0
717
This is a minor release focusing on tightening up the internals and ironing out

R/aes-evaluation.r

Lines changed: 152 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,114 @@
11
#' Control aesthetic evaluation
22
#'
3-
#' Most aesthetics are mapped from variables found in the data. Sometimes,
4-
#' however, you want to delay the mapping until later in the rendering process.
5-
#' ggplot2 has three stages of the data that you can map aesthetics from. The
6-
#' default is to map at the beginning, using the layer data provided by the
7-
#' user. The second stage is after the data has been transformed by the layer
8-
#' stat. The third and last stage is after the data has been transformed and
9-
#' mapped by the plot scales. The most common example of mapping from stat
10-
#' transformed data is the height of bars in [geom_histogram()]:
11-
#' the height does not come from a variable in the underlying data, but
12-
#' is instead mapped to the `count` computed by [stat_bin()]. An example of
13-
#' mapping from scaled data could be to use a desaturated version of the stroke
14-
#' colour for fill. If you want to map directly from the layer data you should
15-
#' not do anything special. In order to map from stat transformed data you
3+
#' @description
4+
#' Most [aesthetics][aes()] are mapped from variables found in the data.
5+
#' Sometimes, however, you want to delay the mapping until later in the
6+
#' rendering process. ggplot2 has three stages of the data that you can map
7+
#' aesthetics from, and three functions to control at which stage aesthetics
8+
#' should be evaluated.
9+
#'
10+
#' @description
11+
#' `after_stat()` replaces the old approaches of using either `stat()`, e.g.
12+
#' `stat(density)`, or surrounding the variable names with `..`, e.g.
13+
#' `..density..`.
14+
#'
15+
#' @usage
16+
#' # These functions can be used inside the `aes()` function
17+
#' # used as the `mapping` argument in layers, for example:
18+
#' # geom_density(mapping = aes(y = after_stat(scaled)))
19+
#'
20+
#' @param x <[`data-masking`][rlang::topic-data-mask]> An aesthetic expression
21+
#' using variables calculated by the stat (`after_stat()`) or layer aesthetics
22+
#' (`after_scale()`).
23+
#' @param start <[`data-masking`][rlang::topic-data-mask]> An aesthetic
24+
#' expression using variables from the layer data.
25+
#' @param after_stat <[`data-masking`][rlang::topic-data-mask]> An aesthetic
26+
#' expression using variables calculated by the stat.
27+
#' @param after_scale <[`data-masking`][rlang::topic-data-mask]> An aesthetic
28+
#' expression using layer aesthetics.
29+
#'
30+
#' @details
31+
#' # Staging
32+
#' Below follows an overview of the three stages of evaluation and how aesthetic
33+
#' evaluation can be controlled.
34+
#'
35+
#' ## Stage 1: direct input
36+
#' The default is to map at the beginning, using the layer data provided by
37+
#' the user. If you want to map directly from the layer data you should not do
38+
#' anything special. This is the only stage where the original layer data can
39+
#' be accessed.
40+
#'
41+
#' ```r
42+
#' # 'x' and 'y' are mapped directly
43+
#' ggplot(mtcars) + geom_point(aes(x = mpg, y = disp))
44+
#' ```
45+
#'
46+
#' ## Stage 2: after stat transformation
47+
#' The second stage is after the data has been transformed by the layer
48+
#' stat. The most common example of mapping from stat transformed data is the
49+
#' height of bars in [geom_histogram()]: the height does not come from a
50+
#' variable in the underlying data, but is instead mapped to the `count`
51+
#' computed by [stat_bin()]. In order to map from stat transformed data you
1652
#' should use the `after_stat()` function to flag that evaluation of the
1753
#' aesthetic mapping should be postponed until after stat transformation.
18-
#' Similarly, you should use `after_scale()` to flag evaluation of mapping for
19-
#' after data has been scaled. If you want to map the same aesthetic multiple
20-
#' times, e.g. map `x` to a data column for the stat, but remap it for the geom,
21-
#' you can use the `stage()` function to collect multiple mappings.
22-
#'
23-
#' `after_stat()` replaces the old approaches of using either `stat()` or
24-
#' surrounding the variable names with `..`.
25-
#'
26-
#' @note Evaluation after stat transformation will have access to the
27-
#' variables calculated by the stat, not the original mapped values. Evaluation
28-
#' after scaling will only have access to the final aesthetics of the layer
29-
#' (including non-mapped, default aesthetics). The original layer data can only
30-
#' be accessed at the first stage.
31-
#'
32-
#' @param x An aesthetic expression using variables calculated by the stat
33-
#' (`after_stat()`) or layer aesthetics (`after_scale()`).
34-
#' @param start An aesthetic expression using variables from the layer data.
35-
#' @param after_stat An aesthetic expression using variables calculated by the
36-
#' stat.
37-
#' @param after_scale An aesthetic expression using layer aesthetics.
54+
#' Evaluation after stat transformation will have access to the variables
55+
#' calculated by the stat, not the original mapped values. The 'computed
56+
#' variables' section in each stat lists which variables are available to
57+
#' access.
58+
#'
59+
#' ```r
60+
#' # The 'y' values for the histogram are computed by the stat
61+
#' ggplot(faithful, aes(x = waiting)) +
62+
#' geom_histogram()
63+
#'
64+
#' # Choosing a different computed variable to display, matching up the
65+
#' # histogram with the density plot
66+
#' ggplot(faithful, aes(x = waiting)) +
67+
#' geom_histogram(aes(y = after_stat(density))) +
68+
#' geom_density()
69+
#' ```
70+
#'
71+
#' ## Stage 3: after scale transformation
72+
#' The third and last stage is after the data has been transformed and
73+
#' mapped by the plot scales. An example of mapping from scaled data could
74+
#' be to use a desaturated version of the stroke colour for fill. You should
75+
#' use `after_scale()` to flag evaluation of mapping for after data has been
76+
#' scaled. Evaluation after scaling will only have access to the final
77+
#' aesthetics of the layer (including non-mapped, default aesthetics).
78+
#'
79+
#' ```r
80+
#' # The exact colour is known after scale transformation
81+
#' ggplot(mpg, aes(cty, colour = factor(cyl))) +
82+
#' geom_density()
3883
#'
84+
#' # We re-use colour properties for the fill without a separate fill scale
85+
#' ggplot(mpg, aes(cty, colour = factor(cyl))) +
86+
#' geom_density(aes(fill = after_scale(alpha(colour, 0.3))))
87+
#' ```
88+
#'
89+
#' ## Complex staging
90+
#' If you want to map the same aesthetic multiple times, e.g. map `x` to a
91+
#' data column for the stat, but remap it for the geom, you can use the
92+
#' `stage()` function to collect multiple mappings.
93+
#'
94+
#' ```r
95+
#' # Use stage to modify the scaled fill
96+
#' ggplot(mpg, aes(class, hwy)) +
97+
#' geom_boxplot(aes(fill = stage(class, after_scale = alpha(fill, 0.4))))
98+
#'
99+
#' # Using data for computing summary, but placing label elsewhere.
100+
#' # Also, we're making our own computed variable to use for the label.
101+
#' ggplot(mpg, aes(class, displ)) +
102+
#' geom_violin() +
103+
#' stat_summary(
104+
#' aes(
105+
#' y = stage(displ, after_stat = 8),
106+
#' label = after_stat(paste(mean, "±", sd))
107+
#' ),
108+
#' geom = "text",
109+
#' fun.data = ~ round(data.frame(mean = mean(.x), sd = sd(.x)), 2)
110+
#' )
111+
#' ```
39112
#' @rdname aes_eval
40113
#' @name aes_eval
41114
#'
@@ -55,6 +128,52 @@
55128
#' # Use stage to modify the scaled fill
56129
#' ggplot(mpg, aes(class, hwy)) +
57130
#' geom_boxplot(aes(fill = stage(class, after_scale = alpha(fill, 0.4))))
131+
#'
132+
#' # Making a proportional stacked density plot
133+
#' ggplot(mpg, aes(cty)) +
134+
#' geom_density(
135+
#' aes(
136+
#' colour = factor(cyl),
137+
#' fill = after_scale(alpha(colour, 0.3)),
138+
#' y = after_stat(count / sum(n[!duplicated(group)]))
139+
#' ),
140+
#' position = "stack", bw = 1
141+
#' ) +
142+
#' geom_density(bw = 1)
143+
#'
144+
#' # Imitating a ridgeline plot
145+
#' ggplot(mpg, aes(cty, colour = factor(cyl))) +
146+
#' geom_ribbon(
147+
#' stat = "density", outline.type = "upper",
148+
#' aes(
149+
#' fill = after_scale(alpha(colour, 0.3)),
150+
#' ymin = after_stat(group),
151+
#' ymax = after_stat(group + ndensity)
152+
#' )
153+
#' )
154+
#'
155+
#' # Labelling a bar plot
156+
#' ggplot(mpg, aes(class)) +
157+
#' geom_bar() +
158+
#' geom_text(
159+
#' aes(
160+
#' y = after_stat(count + 2),
161+
#' label = after_stat(count)
162+
#' ),
163+
#' stat = "count"
164+
#' )
165+
#'
166+
#' # Labelling the upper hinge of a boxplot,
167+
#' # inspired by June Choe
168+
#' ggplot(mpg, aes(displ, class)) +
169+
#' geom_boxplot(outlier.shape = NA) +
170+
#' geom_text(
171+
#' aes(
172+
#' label = after_stat(xmax),
173+
#' x = stage(displ, after_stat = xmax)
174+
#' ),
175+
#' stat = "boxplot", hjust = -0.5
176+
#' )
58177
NULL
59178

60179
#' @rdname aes_eval

R/aes.r

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ NULL
3131
#' are typically omitted because they are so common; all other aesthetics must be named.
3232
#' @seealso [vars()] for another quoting function designed for
3333
#' faceting specifications.
34+
#'
35+
#' [Delayed evaluation][aes_eval] for working with computed variables.
3436
#' @return A list with class `uneval`. Components of the list are either
3537
#' quosures or constants.
3638
#' @export

R/coord-.r

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,11 @@ Coord <- ggproto("Coord",
158158

159159
train_panel_guides = function(self, panel_params, layers, default_mapping, params = list()) {
160160
aesthetics <- c("x", "y", "x.sec", "y.sec")
161-
names(aesthetics) <- aesthetics
161+
162162
# If the panel_params doesn't contain the scale, there's no guide for the aesthetic
163163
aesthetics <- intersect(aesthetics, names(panel_params$guides))
164+
165+
names(aesthetics) <- aesthetics
164166

165167
panel_params$guides <- lapply(aesthetics, function(aesthetic) {
166168
axis <- substr(aesthetic, 1, 1)

R/geom-.r

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,12 @@ Geom <- ggproto("Geom",
7878
# Trim off extra parameters
7979
params <- params[intersect(names(params), self$parameters())]
8080

81-
lapply(split(data, data$PANEL), function(data) {
81+
if (nlevels(as.factor(data$PANEL)) > 1L) {
82+
data_panels <- split(data, data$PANEL)
83+
} else {
84+
data_panels <- list(data)
85+
}
86+
lapply(data_panels, function(data) {
8287
if (empty(data)) return(zeroGrob())
8388

8489
panel_params <- layout$panel_params[[data$PANEL[1]]]

R/geom-dotplot.r

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@
1717
#' to match the number of dots.
1818
#'
1919
#' @eval rd_aesthetics("geom", "dotplot")
20-
#' @section Computed variables:
21-
#' \describe{
22-
#' \item{x}{center of each bin, if binaxis is "x"}
23-
#' \item{y}{center of each bin, if binaxis is "x"}
24-
#' \item{binwidth}{max width of each bin if method is "dotdensity";
25-
#' width of each bin if method is "histodot"}
26-
#' \item{count}{number of points in bin}
27-
#' \item{ncount}{count, scaled to maximum of 1}
28-
#' \item{density}{density of points in bin, scaled to integrate to 1,
29-
#' if method is "histodot"}
30-
#' \item{ndensity}{density, scaled to maximum of 1, if method is "histodot"}
31-
#' }
20+
#' @eval rd_computed_vars(
21+
#' x = 'center of each bin, if `binaxis` is `"x"`.',
22+
#' y = 'center of each bin, if `binaxis` is `"x"`.',
23+
#' binwidth = 'maximum width of each bin if method is `"dotdensity"`;
24+
#' width of each bin if method is `"histodot"`.',
25+
#' count = "number of points in bin.",
26+
#' ncount = "count, scaled to a maximum of 1.",
27+
#' density = 'density of points in bin, scaled to integrate to 1, if method
28+
#' is `"histodot"`.',
29+
#' ndensity = 'density, scaled to maximum of 1, if method is `"histodot"`.'
30+
#' )
3231
#'
3332
#' @inheritParams layer
3433
#' @inheritParams geom_point

R/geom-hex.r

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,19 @@ GeomHex <- ggproto("GeomHex", Geom,
8080

8181
n <- nrow(data)
8282

83-
data <- data[rep(seq_len(n), each = 6), ]
84-
data$x <- rep.int(hexC$x, n) + data$x
85-
data$y <- rep.int(hexC$y, n) + data$y
83+
hexdata <- data[rep(seq_len(n), each = 6), c("x", "y")]
84+
hexdata$x <- rep.int(hexC$x, n) + hexdata$x
85+
hexdata$y <- rep.int(hexC$y, n) + hexdata$y
8686

87-
coords <- coord$transform(data, panel_params)
87+
coords <- coord$transform(hexdata, panel_params)
8888

8989
ggname("geom_hex", polygonGrob(
9090
coords$x, coords$y,
9191
gp = gpar(
92-
col = coords$colour,
93-
fill = alpha(coords$fill, coords$alpha),
94-
lwd = coords$linewidth * .pt,
95-
lty = coords$linetype,
92+
col = data$colour,
93+
fill = alpha(data$fill, data$alpha),
94+
lwd = data$linewidth * .pt,
95+
lty = data$linetype,
9696
lineend = lineend,
9797
linejoin = linejoin,
9898
linemitre = linemitre

R/ggplot-global.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ggplot_global$all_aesthetics <- .all_aesthetics
3434
"pch" = "shape",
3535
"cex" = "size",
3636
"lty" = "linetype",
37-
"lwd" = "size",
37+
"lwd" = "linewidth",
3838
"srt" = "angle",
3939
"adj" = "hjust",
4040
"bg" = "fill",

R/save.r

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ ggsave <- function(filename, plot = last_plot(),
7777
device = NULL, path = NULL, scale = 1,
7878
width = NA, height = NA, units = c("in", "cm", "mm", "px"),
7979
dpi = 300, limitsize = TRUE, bg = NULL, ...) {
80+
if (length(filename) != 1) {
81+
if (length(filename) == 0) {
82+
cli::cli_abort("{.arg filename} cannot be empty.")
83+
}
84+
len <- length(filename)
85+
filename <- filename[1]
86+
cli::cli_warn(c(
87+
"{.arg filename} must have length 1, not length {len}.",
88+
"!" = "Only the first, {.file {filename}}, will be used."
89+
))
90+
}
8091

8192
dpi <- parse_dpi(dpi)
8293
dev <- plot_dev(device, filename, dpi = dpi)
@@ -147,8 +158,24 @@ plot_dim <- function(dim = c(NA, NA), scale = 1, units = "in",
147158
}
148159

149160
if (limitsize && any(dim >= 50)) {
161+
units <- switch(
162+
units,
163+
"in" = "inches",
164+
"cm" = "centimeters",
165+
"mm" = "millimeters",
166+
"px" = "pixels"
167+
)
168+
msg <- paste0(
169+
"Dimensions exceed 50 inches ({.arg height} and {.arg width} are ",
170+
"specified in {.emph {units}}"
171+
)
172+
if (units == "pixels") {
173+
msg <- paste0(msg, ").")
174+
} else {
175+
msg <- paste0(msg, " not pixels).")
176+
}
150177
cli::cli_abort(c(
151-
"Dimensions exceed 50 inches ({.arg height} and {.arg width} are specified in {.emph {units}} not pixels).",
178+
msg,
152179
"i" = "If you're sure you want a plot that big, use {.code limitsize = FALSE}.
153180
"), call = call)
154181
}

0 commit comments

Comments
 (0)