Skip to content

Commit b7a4119

Browse files
committed
Merge branch 'main' into sanitise_bins
2 parents 653410f + efc53cc commit b7a4119

26 files changed

+312
-73
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
than `origin`, following in `stat_bin()`'s footsteps (@teunbrand).
55
* `stat_summary_2d()` and `stat_bin_2d()` now deal with zero-range data
66
more elegantly (@teunbrand, #6207).
7+
* `geom_ribbon()` now appropriately warns about, and removes, missing values
8+
(@teunbrand, #6243).
9+
* `guide_*()` can now accept two inside legend theme elements:
10+
`legend.position.inside` and `legend.justification.inside`, allowing inside
11+
legends to be placed at different positions. Only inside legends with the same
12+
position and justification will be merged (@Yunuuuu, #6210).
713
* New stat: `stat_manual()` for arbitrary computations (@teunbrand, #3501)
814
* Reversal of a dimension, typically 'x' or 'y', is now controlled by the
915
`reverse` argument in `coord_cartesian()`, `coord_fixed()`, `coord_radial()`

R/coord-polar.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#' # to demonstrate how these common plots can be described in the
2121
#' # grammar. Use with EXTREME caution.
2222
#'
23-
#' #' # A pie chart = stacked bar chart + polar coordinates
23+
#' # A pie chart = stacked bar chart + polar coordinates
2424
#' pie <- ggplot(mtcars, aes(x = factor(1), fill = factor(cyl))) +
2525
#' geom_bar(width = 1)
2626
#' pie + coord_polar(theta = "y")

R/coord-radial.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
#' @param r_axis_inside,rotate_angle `r lifecycle::badge("deprecated")`
2828
#'
2929
#' @note
30-
#' In `coord_radial()`, position guides are can be defined by using
30+
#' In `coord_radial()`, position guides can be defined by using
3131
#' `guides(r = ..., theta = ..., r.sec = ..., theta.sec = ...)`. Note that
3232
#' these guides require `r` and `theta` as available aesthetics. The classic
33-
#' `guide_axis()` can be used for the `r` positions and `guide_axis_theta()` can
33+
#' [guide_axis()] can be used for the `r` positions and [guide_axis_theta()] can
3434
#' be used for the `theta` positions. Using the `theta.sec` position is only
3535
#' sensible when `inner.radius > 0`.
3636
#'

R/fortify-map.R

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#' Fortify method for map objects
22
#'
3+
#' @description
4+
#' `r lifecycle::badge("deprecated")`
5+
#'
36
#' This function turns a map into a data frame that can more easily be
47
#' plotted with ggplot2.
58
#'
@@ -24,6 +27,9 @@
2427
#' geom_polygon(aes(group = group), colour = "white")
2528
#' }
2629
fortify.map <- function(model, data, ...) {
30+
lifecycle::deprecate_warn(
31+
"3.6.0", I("`fortify(<map>)`"), "map_data()"
32+
)
2733
df <- data_frame0(
2834
long = model$x,
2935
lat = model$y,
@@ -46,10 +52,10 @@ fortify.map <- function(model, data, ...) {
4652
#' for plotting with ggplot2.
4753
#'
4854
#' @param map name of map provided by the \pkg{maps} package. These
49-
#' include [maps::county()], [maps::france()],
50-
#' [maps::italy()], [maps::nz()],
51-
#' [maps::state()], [maps::usa()],
52-
#' [maps::world()], [maps::world2()].
55+
#' include [`"county"`][maps::county], [`"france"`][maps::france],
56+
#' [`"italy"`][maps::italy], [`"nz"`][maps::nz],
57+
#' [`"state"`][maps::state], [`"usa"`][maps::usa],
58+
#' [`"world"`][maps::world], or [`"world2"`][maps::world2].
5359
#' @param region name(s) of subregion(s) to include. Defaults to `.` which
5460
#' includes all subregions. See documentation for [maps::map()]
5561
#' for more details.
@@ -80,7 +86,27 @@ fortify.map <- function(model, data, ...) {
8086
map_data <- function(map, region = ".", exact = FALSE, ...) {
8187
check_installed("maps", reason = "for `map_data()`.")
8288
map_obj <- maps::map(map, region, exact = exact, plot = FALSE, fill = TRUE, ...)
83-
fortify(map_obj)
89+
90+
if (!inherits(map_obj, "map")) {
91+
cli::cli_abort(c(
92+
"{.fn maps::map} must return an object of type {.cls map}, not \\
93+
{obj_type_friendly(map_obj)}.",
94+
i = "Did you pass the right arguments?"
95+
))
96+
}
97+
98+
df <- data_frame0(
99+
long = map_obj$x,
100+
lat = map_obj$y,
101+
group = cumsum(is.na(map_obj$x) & is.na(map_obj$y)) + 1,
102+
order = seq_along(map_obj$x),
103+
.size = length(map_obj$x)
104+
)
105+
106+
names <- lapply(strsplit(map_obj$names, "[:,]"), "[", 1:2)
107+
names <- vec_rbind(!!!names, .name_repair = ~ c("region", "subregion"))
108+
df[names(names)] <- vec_slice(names, df$group)
109+
vec_slice(df, stats::complete.cases(df$lat, df$long))
84110
}
85111

86112
#' Create a layer of map borders

R/fortify-spatial.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#' Fortify method for classes from the sp package.
22
#'
3+
#' @description
4+
#' `r lifecycle::badge("deprecated")`
5+
#'
36
#' To figure out the correct variable name for region, inspect
47
#' `as.data.frame(model)`.
58
#'

R/geom-point.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@
8585
#' ggplot(mtcars, aes(wt, mpg)) +
8686
#' geom_point(shape = 21, colour = "black", fill = "white", size = 5, stroke = 5)
8787
#'
88+
#' # The default shape in legends is not filled, but you can override the shape
89+
#' # in the guide to reflect the fill in the legend
90+
#' ggplot(mtcars, aes(wt, mpg, fill = factor(carb), shape = factor(cyl))) +
91+
#' geom_point(size = 5, stroke = 1) +
92+
#' scale_shape_manual(values = 21:25) +
93+
#' scale_fill_ordinal(guide = guide_legend(override.aes = list(shape = 21)))
94+
#'
8895
#' \donttest{
8996
#' # You can create interesting shapes by layering multiple points of
9097
#' # different sizes

R/geom-ribbon.R

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,31 @@ GeomRibbon <- ggproto("GeomRibbon", Geom,
126126

127127
draw_key = draw_key_polygon,
128128

129-
handle_na = function(data, params) {
129+
handle_na = function(self, data, params) {
130+
131+
vars <- vapply(
132+
strsplit(self$required_aes, "|", fixed = TRUE),
133+
`[[`, i = 1, character(1)
134+
)
135+
if (params$flipped_aes || any(data$flipped_aes) %||% FALSE) {
136+
vars <- switch_orientation(vars)
137+
}
138+
vars <- c(vars, self$non_missing_aes)
139+
140+
missing <- detect_missing(data, vars, finite = FALSE)
141+
if (!any(missing)) {
142+
return(data)
143+
}
144+
# We're rearranging groups to account for missing values
145+
data$group <- vec_identify_runs(data_frame0(missing, data$group))
146+
data <- vec_slice(data, !missing)
147+
148+
if (!params$na.rm) {
149+
cli::cli_warn(
150+
"Removed {sum(missing)} row{?s} containing missing values or values \\
151+
outside the scale range ({.fn {snake_class(self)}})."
152+
)
153+
}
130154
data
131155
},
132156

@@ -135,7 +159,6 @@ GeomRibbon <- ggproto("GeomRibbon", Geom,
135159
flipped_aes = FALSE, outline.type = "both") {
136160
data <- check_linewidth(data, snake_class(self))
137161
data <- flip_data(data, flipped_aes)
138-
if (na.rm) data <- data[stats::complete.cases(data[c("x", "ymin", "ymax")]), ]
139162
data <- data[order(data$group), ]
140163

141164
# Check that aesthetics are constant

R/guide-axis-stack.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ NULL
2222
#' @export
2323
#'
2424
#' @examples
25-
#' #' # A standard plot
25+
#' # A standard plot
2626
#' p <- ggplot(mpg, aes(displ, hwy)) +
2727
#' geom_point() +
2828
#' theme(axis.line = element_line())

R/guides-.R

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ Guides <- ggproto(
471471
# for every position, collect all individual guides and arrange them
472472
# into a guide box which will be inserted into the main gtable
473473
# Combining multiple guides in a guide box
474-
assemble = function(self, theme) {
474+
assemble = function(self, theme, params = self$params, guides = self$guides) {
475475

476476
if (length(self$guides) < 1) {
477477
return(zeroGrob())
@@ -485,42 +485,95 @@ Guides <- ggproto(
485485
return(zeroGrob())
486486
}
487487

488+
# extract the guide position
489+
positions <- vapply(
490+
params,
491+
function(p) p$position[1] %||% default_position,
492+
character(1), USE.NAMES = FALSE
493+
)
494+
488495
# Populate key sizes
489496
theme$legend.key.width <- calc_element("legend.key.width", theme)
490497
theme$legend.key.height <- calc_element("legend.key.height", theme)
491498

492-
grobs <- self$draw(theme, default_position, theme$legend.direction)
499+
grobs <- self$draw(theme, positions, theme$legend.direction)
500+
keep <- !vapply(grobs, is.zero, logical(1), USE.NAMES = FALSE)
501+
grobs <- grobs[keep]
493502
if (length(grobs) < 1) {
494503
return(zeroGrob())
495504
}
496-
grobs <- grobs[order(names(grobs))]
505+
506+
# prepare the position of inside legends
507+
default_inside_just <- calc_element("legend.justification.inside", theme)
508+
default_inside_position <- calc_element("legend.position.inside", theme)
509+
510+
groups <- data_frame0(
511+
positions = positions,
512+
justs = list(NULL),
513+
coords = list(NULL)
514+
)
515+
516+
# we grouped the legends by the positions, for inside legends, they'll be
517+
# splitted by the actual inside coordinate
518+
for (i in which(positions == "inside")) {
519+
# the actual inside position and justification can be set in each guide
520+
# by `theme` argument, here, we won't use `calc_element()` which will
521+
# use inherits from `legend.justification` or `legend.position`, we only
522+
# follow the inside elements from the guide theme
523+
just <- params[[i]]$theme[["legend.justification.inside"]]
524+
just <- valid.just(just %||% default_inside_just)
525+
coord <- params[[i]]$theme[["legend.position.inside"]]
526+
coord <- coord %||% default_inside_position %||% just
527+
528+
groups$justs[[i]] <- just
529+
groups$coord[[i]] <- coord
530+
}
531+
532+
groups <- vec_group_loc(vec_slice(groups, keep))
533+
grobs <- vec_chop(grobs, indices = groups$loc)
534+
names(grobs) <- groups$key$positions
497535

498536
# Set spacing
499537
theme$legend.spacing <- theme$legend.spacing %||% unit(0.5, "lines")
500538
theme$legend.spacing.y <- calc_element("legend.spacing.y", theme)
501539
theme$legend.spacing.x <- calc_element("legend.spacing.x", theme)
502540

503-
Map(
504-
grobs = grobs,
505-
position = names(grobs),
506-
self$package_box,
507-
MoreArgs = list(theme = theme)
508-
)
541+
# prepare output
542+
for (i in vec_seq_along(groups)) {
543+
adjust <- NULL
544+
position <- groups$key$position[i]
545+
if (position == "inside") {
546+
adjust <- theme(
547+
legend.position.inside = groups$key$coord[[i]],
548+
legend.justification.inside = groups$key$justs[[i]]
549+
)
550+
}
551+
grobs[[i]] <- self$package_box(grobs[[i]], position, theme + adjust)
552+
}
553+
554+
# merge inside grobs into single gtable
555+
is_inside <- names(grobs) == "inside"
556+
if (sum(is_inside) > 1) {
557+
inside <- gtable(unit(1, "npc"), unit(1, "npc"))
558+
inside <- gtable_add_grob(
559+
inside, grobs[is_inside],
560+
t = 1, l = 1, clip = "off",
561+
name = paste0("guide-box-inside-", seq_len(sum(is_inside)))
562+
)
563+
grobs <- grobs[!is_inside]
564+
grobs$inside <- inside
565+
}
566+
567+
# fill in missing guides
568+
grobs[setdiff(c(.trbl, "inside"), names(grobs))] <- list(zeroGrob())
569+
570+
grobs
509571
},
510572

511573
# Render the guides into grobs
512-
draw = function(self, theme,
513-
default_position = "right",
514-
direction = NULL,
574+
draw = function(self, theme, positions, direction = NULL,
515575
params = self$params,
516576
guides = self$guides) {
517-
positions <- vapply(
518-
params,
519-
function(p) p$position[1] %||% default_position,
520-
character(1)
521-
)
522-
positions <- factor(positions, levels = c(.trbl, "inside"))
523-
524577
directions <- rep(direction %||% "vertical", length(positions))
525578
if (is.null(direction)) {
526579
directions[positions %in% c("top", "bottom")] <- "horizontal"
@@ -529,14 +582,16 @@ Guides <- ggproto(
529582
grobs <- vector("list", length(guides))
530583
for (i in seq_along(grobs)) {
531584
grobs[[i]] <- guides[[i]]$draw(
532-
theme = theme, position = as.character(positions[i]),
585+
theme = theme, position = positions[i],
533586
direction = directions[i], params = params[[i]]
534587
)
535588
}
536-
keep <- !vapply(grobs, is.zero, logical(1))
537-
split(grobs[keep], positions[keep])
589+
grobs
538590
},
539591

592+
# here, we put `inside_position` and `inside_just` in the last, so that it
593+
# won't break current implement of patchwork, which depends on the top three
594+
# arguments to collect guides
540595
package_box = function(grobs, position, theme) {
541596

542597
if (is.zero(grobs) || length(grobs) == 0) {
@@ -699,7 +754,6 @@ Guides <- ggproto(
699754
guides$name <- "guide-box"
700755
guides
701756
},
702-
703757
## Utilities -----------------------------------------------------------------
704758

705759
print = function(self) {

R/limits.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#' scales. By default, any values outside the limits specified are replaced with
55
#' `NA`. Be warned that this will remove data outside the limits and this can
66
#' produce unintended results. For changing x or y axis limits \strong{without}
7-
#' dropping data observations, see [coord_cartesian()].
7+
#' dropping data observations, see
8+
#' [`coord_cartesian(xlim, ylim)`][coord_cartesian], or use a full scale with
9+
#' [`oob = scales::oob_keep`][scales::oob_keep].
810
#'
911
#' @param ... For `xlim()` and `ylim()`: Two numeric values, specifying the left/lower
1012
#' limit and the right/upper limit of the scale. If the larger value is given first,

0 commit comments

Comments
 (0)