Skip to content

Commit 7484bd7

Browse files
authored
Stat for aligning lines before stacking (#4889)
1 parent 7ebb6bd commit 7484bd7

13 files changed

+361
-14
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ Collate:
231231
'scale-view.r'
232232
'scale-viridis.r'
233233
'scales-.r'
234+
'stat-align.R'
234235
'stat-bin.r'
235236
'stat-bin2d.r'
236237
'stat-bindot.r'

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ export(ScaleDiscrete)
252252
export(ScaleDiscreteIdentity)
253253
export(ScaleDiscretePosition)
254254
export(Stat)
255+
export(StatAlign)
255256
export(StatBin)
256257
export(StatBin2d)
257258
export(StatBindot)
@@ -630,6 +631,7 @@ export(should_stop)
630631
export(stage)
631632
export(standardise_aes_names)
632633
export(stat)
634+
export(stat_align)
633635
export(stat_bin)
634636
export(stat_bin2d)
635637
export(stat_bin_2d)

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# ggplot2 (development version)
22

3+
* Added `stat_align()` to align data without common x-coordinates prior to
4+
stacking. This is now the default stat for `geom_area()` (@thomasp85, #4850)
5+
36
* Fix a bug in `stat_contour_filled()` where break value differences below a
47
certain number of digits would cause the computations to fail (@thomasp85,
58
#4874)

R/geom-ribbon.r

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,14 @@ GeomRibbon <- ggproto("GeomRibbon", Geom,
130130

131131
data <- unclass(data) #for faster indexing
132132

133+
# In case the data comes from stat_align
134+
upper_keep <- if (is.null(data$align_padding)) TRUE else !data$align_padding
135+
133136
# The upper line and lower line need to processed separately (#4023)
134137
positions_upper <- data_frame0(
135-
x = data$x,
136-
y = data$ymax,
137-
id = ids
138+
x = data$x[upper_keep],
139+
y = data$ymax[upper_keep],
140+
id = ids[upper_keep]
138141
)
139142

140143
positions_lower <- data_frame0(
@@ -203,7 +206,7 @@ GeomRibbon <- ggproto("GeomRibbon", Geom,
203206

204207
#' @rdname geom_ribbon
205208
#' @export
206-
geom_area <- function(mapping = NULL, data = NULL, stat = "identity",
209+
geom_area <- function(mapping = NULL, data = NULL, stat = "align",
207210
position = "stack", na.rm = FALSE, orientation = NA,
208211
show.legend = NA, inherit.aes = TRUE, ...,
209212
outline.type = "upper") {

R/stat-align.R

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#' @inheritParams layer
2+
#' @inheritParams geom_point
3+
#' @export
4+
#' @rdname geom_ribbon
5+
stat_align <- function(mapping = NULL, data = NULL,
6+
geom = "area", position = "identity",
7+
...,
8+
na.rm = FALSE,
9+
show.legend = NA,
10+
inherit.aes = TRUE) {
11+
layer(
12+
data = data,
13+
mapping = mapping,
14+
stat = StatAlign,
15+
geom = geom,
16+
position = position,
17+
show.legend = show.legend,
18+
inherit.aes = inherit.aes,
19+
params = list2(
20+
na.rm = na.rm,
21+
...
22+
)
23+
)
24+
}
25+
26+
#' @rdname ggplot2-ggproto
27+
#' @format NULL
28+
#' @usage NULL
29+
#' @export
30+
StatAlign <- ggproto("StatAlign", Stat,
31+
extra_params = c("na.rm", "orientation"),
32+
required_aes = c("x", "y"),
33+
34+
setup_params = function(data, params) {
35+
params$flipped_aes <- has_flipped_aes(data, params, ambiguous = TRUE)
36+
x_name <- flipped_names(params$flipped_aes)$x
37+
y_name <- flipped_names(params$flipped_aes)$y
38+
x_cross <- dapply(data, "group", function(d) {
39+
pivots <- cumsum(rle(d[[y_name]] < 0)$lengths)
40+
pivots <- pivots[-length(pivots)]
41+
cross <- vapply(pivots, function(i) {
42+
y <- d[[y_name]][c(i, i+1)]
43+
x <- d[[x_name]][c(i, i+1)]
44+
-y[1]*diff(x)/diff(y) + x[1]
45+
}, numeric(1))
46+
data_frame(cross = cross)
47+
})
48+
unique_loc <- unique(sort(c(data[[x_name]], x_cross$cross)))
49+
adjust <- diff(range(unique_loc, na.rm = TRUE)) * 0.001
50+
adjust <- min(adjust, min(diff(unique_loc))/3)
51+
unique_loc <- sort(c(unique_loc - adjust, unique_loc, unique_loc + adjust))
52+
params$unique_loc <- unique_loc
53+
params$adjust <- adjust
54+
params
55+
},
56+
57+
compute_group = function(data, scales, flipped_aes = NA, unique_loc = NULL, adjust = 0) {
58+
data <- flip_data(data, flipped_aes)
59+
if (length(unique(data$x)) == 1) {
60+
# Not enough data to align
61+
return(new_data_frame())
62+
}
63+
# Sort out multiple observations at the same x
64+
if (anyDuplicated(data$x)) {
65+
data <- dapply(data, "x", function(d) {
66+
if (nrow(d) == 1) return(d)
67+
d <- d[c(1, nrow(d)), ]
68+
d$x[1] <- d$x[1] - adjust
69+
d
70+
})
71+
}
72+
y_val <- approxfun(data$x, data$y)(unique_loc)
73+
keep <- !is.na(y_val)
74+
x_val <- unique_loc[keep]
75+
y_val <- y_val[keep]
76+
x_val <- c(min(x_val) - adjust, x_val, max(x_val) + adjust)
77+
y_val <- c(0, y_val, 0)
78+
79+
data_aligned <- data_frame0(
80+
x = x_val,
81+
y = y_val,
82+
data[1, setdiff(names(data), c("x", "y"))],
83+
align_padding = c(TRUE, rep(FALSE, length(x_val) - 2), TRUE),
84+
flipped_aes = flipped_aes
85+
)
86+
flip_data(data_aligned, flipped_aes)
87+
}
88+
)

man/geom_ribbon.Rd

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

man/ggplot2-ggproto.Rd

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 63 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)