Skip to content

Commit a66b538

Browse files
authored
Merge branch 'main' into boxplot_key
2 parents 6f1ee38 + bfe4b1c commit a66b538

Some content is hidden

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

53 files changed

+733
-342
lines changed

NEWS.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
* Fixed misbehaviour of `draw_key_boxplot()` and `draw_key_crossbar()` with
44
skewed key aspect ratio (@teunbrand, #5082).
5+
* `scale_*_binned()` handles zero-range limits more gracefully (@teunbrand,
6+
#5066)
7+
* Binned scales are now compatible with `trans = "date"` and `trans = "time"`
8+
(@teunbrand, #4217).
9+
* `ggsave()` warns when multiple `filename`s are given, and only writes to the
10+
first file (@teunbrand, #5114).
511
* Fixed a regression in `geom_hex()` where aesthetics were replicated across
612
bins (@thomasp85, #5037 and #5044)
713
* Fixed spurious warning when `weight` aesthetic was used in `stat_smooth()`
@@ -10,7 +16,7 @@
1016
(@teunbrand based on @clauswilke's suggestion #5051).
1117
* Fixed a regression in `Coord$train_panel_guides()` where names of guides were
1218
dropped (@maxsutton, #5063)
13-
19+
1420
# ggplot2 3.4.0
1521
This is a minor release focusing on tightening up the internals and ironing out
1622
some inconsistencies in the API. The biggest change is the addition of the

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/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/save.r

Lines changed: 11 additions & 0 deletions
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)

R/scale-.r

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,16 +1021,22 @@ ScaleBinned <- ggproto("ScaleBinned", Scale,
10211021
x <- self$rescale(self$oob(x, range = limits), limits)
10221022
breaks <- self$rescale(breaks, limits)
10231023

1024-
x_binned <- cut(x, breaks,
1025-
labels = FALSE,
1026-
include.lowest = TRUE,
1027-
right = self$right
1028-
)
1024+
if (length(breaks) > 1) {
1025+
x_binned <- cut(x, breaks,
1026+
labels = FALSE,
1027+
include.lowest = TRUE,
1028+
right = self$right
1029+
)
1030+
midpoints <- breaks[-1] - diff(breaks) / 2
1031+
} else {
1032+
x_binned <- 1L
1033+
midpoints <- 0.5
1034+
}
10291035

10301036
if (!is.null(self$palette.cache)) {
10311037
pal <- self$palette.cache
10321038
} else {
1033-
pal <- self$palette(breaks[-1] - diff(breaks) / 2)
1039+
pal <- self$palette(midpoints)
10341040
self$palette.cache <- pal
10351041
}
10361042

@@ -1075,10 +1081,13 @@ ScaleBinned <- ggproto("ScaleBinned", Scale,
10751081
# Ensure terminal bins are same width if limits not set
10761082
if (is.null(self$limits)) {
10771083
# Remove calculated breaks if they coincide with limits
1078-
breaks <- setdiff(breaks, limits)
1084+
breaks <- breaks[!breaks %in% limits]
10791085
nbreaks <- length(breaks)
10801086
if (nbreaks >= 2) {
1081-
new_limits <- c(2 * breaks[1] - breaks[2], 2 * breaks[nbreaks] - breaks[nbreaks - 1])
1087+
new_limits <- c(
1088+
breaks[1] + (breaks[1] - breaks[2]),
1089+
breaks[nbreaks] + (breaks[nbreaks] - breaks[nbreaks - 1])
1090+
)
10821091
if (breaks[nbreaks] > limits[2]) {
10831092
new_limits[2] <- breaks[nbreaks]
10841093
breaks <- breaks[-nbreaks]

R/stat-.r

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#' `compute_panel(self, data, scales, ...)`, or
1515
#' `compute_group(self, data, scales, ...)`.
1616
#'
17-
#' `compute_layer()` is called once per layer, `compute_panel_()`
17+
#' `compute_layer()` is called once per layer, `compute_panel()`
1818
#' is called once per panel, and `compute_group()` is called once per
1919
#' group. All must return a data frame.
2020
#'

R/stat-bin.r

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@
2727
#' or left edges of bins are included in the bin.
2828
#' @param pad If `TRUE`, adds empty bins at either end of x. This ensures
2929
#' frequency polygons touch 0. Defaults to `FALSE`.
30-
#' @section Computed variables:
31-
#' \describe{
32-
#' \item{`count`}{number of points in bin}
33-
#' \item{`density`}{density of points in bin, scaled to integrate to 1}
34-
#' \item{`ncount`}{count, scaled to maximum of 1}
35-
#' \item{`ndensity`}{density, scaled to maximum of 1}
36-
#' \item{`width`}{widths of bins}
37-
#' }
30+
#' @eval rd_computed_vars(
31+
#' count = "number of points in bin.",
32+
#' density = "density of points in bin, scaled to integrate to 1.",
33+
#' ncount = "count, scaled to a maximum of 1.",
34+
#' ndensity = "density, scaled to a maximum of 1.",
35+
#' width = "widths of bins."
36+
#' )
3837
#'
3938
#' @section Dropped variables:
4039
#' \describe{

R/stat-bin2d.r

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
#' @param drop if `TRUE` removes all cells with 0 counts.
66
#' @export
77
#' @rdname geom_bin_2d
8-
#' @section Computed variables:
9-
#' \describe{
10-
#' \item{count}{number of points in bin}
11-
#' \item{density}{density of points in bin, scaled to integrate to 1}
12-
#' \item{ncount}{count, scaled to maximum of 1}
13-
#' \item{ndensity}{density, scaled to maximum of 1}
14-
#' }
8+
#' @eval rd_computed_vars(
9+
#' count = "number of points in bin.",
10+
#' density = "density of points in bin, scaled to integrate to 1.",
11+
#' ncount = "count, scaled to maximum of 1.",
12+
#' ndensity = "density, scaled to a maximum of 1."
13+
#' )
1514
stat_bin_2d <- function(mapping = NULL, data = NULL,
1615
geom = "tile", position = "identity",
1716
...,

0 commit comments

Comments
 (0)