Skip to content

Commit a8acd82

Browse files
Enable geom_ribbon() to draw separate lines for upper and lower intervals (#3529)
1 parent 56294a7 commit a8acd82

File tree

8 files changed

+101
-14
lines changed

8 files changed

+101
-14
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@
122122
* `stat_summary()` and related functions now support rlang-style lambda functions
123123
(#3568, @dkahle).
124124

125+
* `geom_ribbon()` now draws separate lines for the upper and lower intervals if
126+
`colour` is mapped by default. Similarly, `geom_area()` now draws lines for
127+
the upper in the same case by default. If you want old-style full stroking, use
128+
`outlier.type = "legacy"` (#3503, @yutannihilation).
129+
125130

126131
# ggplot2 3.2.1
127132

R/geom-ribbon.r

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#' [geom_polygon()] for general polygons
2222
#' @inheritParams layer
2323
#' @inheritParams geom_bar
24+
#' @param outline.type Type of the outline of the area; `"both"` draws both the
25+
#' upper and lower lines, `"upper"` draws the upper lines only. `"legacy"`
26+
#' draws a closed polygon around the area.
2427
#' @export
2528
#' @examples
2629
#' # Generate data
@@ -44,7 +47,10 @@ geom_ribbon <- function(mapping = NULL, data = NULL,
4447
na.rm = FALSE,
4548
orientation = NA,
4649
show.legend = NA,
47-
inherit.aes = TRUE) {
50+
inherit.aes = TRUE,
51+
outline.type = "both") {
52+
outline.type <- match.arg(outline.type, c("both", "upper", "legacy"))
53+
4854
layer(
4955
data = data,
5056
mapping = mapping,
@@ -56,6 +62,7 @@ geom_ribbon <- function(mapping = NULL, data = NULL,
5662
params = list(
5763
na.rm = na.rm,
5864
orientation = orientation,
65+
outline.type = outline.type,
5966
...
6067
)
6168
)
@@ -97,7 +104,7 @@ GeomRibbon <- ggproto("GeomRibbon", Geom,
97104
data
98105
},
99106

100-
draw_group = function(data, panel_params, coord, na.rm = FALSE, flipped_aes = FALSE) {
107+
draw_group = function(data, panel_params, coord, na.rm = FALSE, flipped_aes = FALSE, outline.type = "both") {
101108
data <- flip_data(data, flipped_aes)
102109
if (na.rm) data <- data[stats::complete.cases(data[c("x", "ymin", "ymax")]), ]
103110
data <- data[order(data$group), ]
@@ -131,23 +138,50 @@ GeomRibbon <- ggproto("GeomRibbon", Geom,
131138

132139
munched <- coord_munch(coord, positions, panel_params)
133140

134-
ggname("geom_ribbon", polygonGrob(
141+
g_poly <- polygonGrob(
135142
munched$x, munched$y, id = munched$id,
136143
default.units = "native",
137144
gp = gpar(
138145
fill = alpha(aes$fill, aes$alpha),
146+
col = if (identical(outline.type, "legacy")) aes$colour else NA
147+
)
148+
)
149+
150+
if (identical(outline.type, "legacy")) {
151+
warn(glue('outline.type = "legacy" is only for backward-compatibility ',
152+
'and might be removed eventually'))
153+
return(ggname("geom_ribbon", g_poly))
154+
}
155+
156+
munched_lines <- munched
157+
# increment the IDs of the lower line
158+
munched_lines$id <- switch(outline.type,
159+
both = munched_lines$id + rep(c(0, max(ids, na.rm = TRUE)), each = length(ids)),
160+
upper = munched_lines$id + rep(c(0, NA), each = length(ids)),
161+
abort(glue("invalid outline.type: {outline.type}"))
162+
)
163+
g_lines <- polylineGrob(
164+
munched_lines$x, munched_lines$y, id = munched_lines$id,
165+
default.units = "native",
166+
gp = gpar(
139167
col = aes$colour,
140168
lwd = aes$size * .pt,
141169
lty = aes$linetype)
142-
))
170+
)
171+
172+
ggname("geom_ribbon", grobTree(g_poly, g_lines))
143173
}
174+
144175
)
145176

146177
#' @rdname geom_ribbon
147178
#' @export
148179
geom_area <- function(mapping = NULL, data = NULL, stat = "identity",
149180
position = "stack", na.rm = FALSE, orientation = NA,
150-
show.legend = NA, inherit.aes = TRUE, ...) {
181+
show.legend = NA, inherit.aes = TRUE, ...,
182+
outline.type = "upper") {
183+
outline.type <- match.arg(outline.type, c("both", "upper", "legacy"))
184+
151185
layer(
152186
data = data,
153187
mapping = mapping,
@@ -159,6 +193,7 @@ geom_area <- function(mapping = NULL, data = NULL, stat = "identity",
159193
params = list(
160194
na.rm = na.rm,
161195
orientation = orientation,
196+
outline.type = outline.type,
162197
...
163198
)
164199
)

man/geom_ribbon.Rd

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/figs/geom-smooth/ribbon-turned-on-in-geom-smooth.svg

Lines changed: 6 additions & 2 deletions
Loading

tests/figs/position-stack/area-stacking.svg

Lines changed: 4 additions & 2 deletions
Loading

tests/testthat/test-aes-setting.r

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ test_that("alpha affects only fill colour of solid geoms", {
3232
geom_polygon(fill = "red", colour = "red", alpha = 0.5)
3333
rect <- ggplot(df, aes(xmin = x, xmax = x + 1, ymin = 1, ymax = y + 1)) +
3434
geom_rect(fill = "red", colour = "red", alpha = 0.5)
35+
# geom_ribbon() consists of polygonGrob and polylineGrob
3536
ribb <- ggplot(df, aes(x = x, ymin = 1, ymax = y + 1)) +
3637
geom_ribbon(fill = "red", colour = "red", alpha = 0.5)
3738

3839
expect_equal(layer_grob(poly)[[1]]$gp$col[[1]], "red")
3940
expect_equal(layer_grob(rect)[[1]]$gp$col[[1]], "red")
40-
expect_equal(layer_grob(ribb)[[1]]$children[[1]]$gp$col[[1]], "red")
41+
expect_equal(layer_grob(ribb)[[1]]$children[[1]]$children[[2]]$gp$col[[1]], "red")
4142

4243
expect_equal(layer_grob(poly)[[1]]$gp$fill[[1]], "#FF000080")
4344
expect_equal(layer_grob(rect)[[1]]$gp$fill[[1]], "#FF000080")
44-
expect_equal(layer_grob(ribb)[[1]]$children[[1]]$gp$fill[[1]], "#FF000080")
45+
expect_equal(layer_grob(ribb)[[1]]$children[[1]]$children[[1]]$gp$fill[[1]], "#FF000080")
4546
})

tests/testthat/test-function-args.r

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ test_that("geom_xxx and GeomXxx$draw arg defaults match", {
1313
# These aren't actually geoms, or need special parameters and can't be tested this way.
1414
geom_fun_names <- setdiff(
1515
geom_fun_names,
16-
c("geom_map", "geom_sf", "geom_smooth", "geom_column", "annotation_custom", "annotation_map",
16+
c("geom_map", "geom_sf", "geom_smooth", "geom_column", "geom_area",
17+
"annotation_custom", "annotation_map",
1718
"annotation_raster", "annotation_id")
1819
)
1920

tests/testthat/test-geom-ribbon.R

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,36 @@ test_that("geom_ribbon works in both directions", {
2626
y$flipped_aes <- NULL
2727
expect_identical(x, flip_data(y, TRUE)[,names(x)])
2828
})
29+
30+
test_that("outline.type option works", {
31+
df <- data_frame(x = 1:4, y = c(1, 1, 1, 1))
32+
33+
p <- ggplot(df, aes(x, ymin = -y, ymax = y))
34+
35+
g_ribbon_default <- layer_grob(p + geom_ribbon())[[1]]
36+
g_ribbon_upper <- layer_grob(p + geom_ribbon(outline.type = "upper"))[[1]]
37+
g_ribbon_legacy <- expect_warning(
38+
layer_grob(p + geom_ribbon(outline.type = "legacy"))[[1]],
39+
'outline.type = "legacy" is only for backward-compatibility and might be removed eventually',
40+
fixed = TRUE
41+
)
42+
g_area_default <- layer_grob(ggplot(df, aes(x, y)) + geom_area())[[1]]
43+
44+
# default
45+
expect_s3_class(g_ribbon_default$children[[1]]$children[[1]], "polygon")
46+
expect_s3_class(g_ribbon_default$children[[1]]$children[[2]], "polyline")
47+
expect_equal(g_ribbon_default$children[[1]]$children[[2]]$id, rep(c(1L, 2L), each = 4))
48+
49+
# upper
50+
expect_s3_class(g_ribbon_upper$children[[1]]$children[[1]], "polygon")
51+
expect_s3_class(g_ribbon_upper$children[[1]]$children[[2]], "polyline")
52+
expect_equal(g_ribbon_upper$children[[1]]$children[[2]]$id, rep(c(1L, NA), each = 4))
53+
54+
# legacy
55+
expect_s3_class(g_ribbon_legacy$children[[1]], "polygon")
56+
57+
# geom_area()'s default is upper
58+
expect_s3_class(g_area_default$children[[1]]$children[[1]], "polygon")
59+
expect_s3_class(g_area_default$children[[1]]$children[[2]], "polyline")
60+
expect_equal(g_area_default$children[[1]]$children[[2]]$id, rep(c(1L, NA), each = 4))
61+
})

0 commit comments

Comments
 (0)