Skip to content

Commit 5f2011f

Browse files
authored
Calculate stat_align() per panel (#5239)
* Apply alignment panel-wise instead of globally * Add NEWS bullets * Add unit test
1 parent 3cffe34 commit 5f2011f

File tree

3 files changed

+59
-18
lines changed

3 files changed

+59
-18
lines changed

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+
* `stat_align()` is now applied per panel instead of globally, preventing issues
4+
when facets have different ranges (@teunbrand, #5227).
5+
* A stacking bug in `stat_align()` was fixed (@teunbrand, #5176).
36
* `stat_contour()` and `stat_contour_filled()` now warn about and remove
47
duplicated coordinates (@teunbrand, #5215).
58
* `annotation_logticks()` skips drawing ticks when the scale range is non-finite

R/stat-align.R

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,43 @@ StatAlign <- ggproto("StatAlign", Stat,
3333

3434
setup_params = function(data, params) {
3535
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
5436
params
5537
},
5638

39+
compute_panel = function(self, data, scales, flipped_aes, ...) {
40+
if (empty(data)) {
41+
return(data_frame0())
42+
}
43+
44+
names <- flipped_names(flipped_aes)
45+
x <- data[[names$x]]
46+
y <- data[[names$y]]
47+
48+
if (is_unique(data$group)) {
49+
# No need for interpolation
50+
cross <- x[0]
51+
} else {
52+
# Find positions where 0 is crossed
53+
pivot <- vec_unrep(data_frame0(group = data$group, y = y < 0))
54+
group_ends <- cumsum(vec_unrep(pivot$key$group)$times)
55+
pivot <- cumsum(pivot$times)[-group_ends]
56+
cross <- -y[pivot] * (x[pivot + 1] - x[pivot]) /
57+
(y[pivot + 1] - y[pivot]) + x[pivot]
58+
}
59+
60+
unique_loc <- unique(sort(c(x, cross)))
61+
adjust <- diff(range(unique_loc, na.rm = TRUE)) * 0.001
62+
adjust <- min(adjust, min(diff(unique_loc)) / 3)
63+
unique_loc <- unique(sort(c(
64+
unique_loc - adjust, unique_loc, unique_loc + adjust
65+
)))
66+
67+
ggproto_parent(Stat, self)$compute_panel(
68+
data, scales, flipped_aes = flipped_aes, unique_loc = unique_loc,
69+
adjust = adjust, ...
70+
)
71+
},
72+
5773
compute_group = function(data, scales, flipped_aes = NA, unique_loc = NULL, adjust = 0) {
5874
data <- flip_data(data, flipped_aes)
5975
if (is_unique(data$x)) {

tests/testthat/test-stat-align.R

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,25 @@ test_that("alignment with negative and positive values works", {
4242
p <- ggplot(df, aes(x, y, fill = g)) + geom_area(color = "black")
4343
expect_doppelganger("align two areas with pos/neg y", p)
4444
})
45+
46+
test_that("alignment adjusts per panel", {
47+
# In particular, the adjustment (small offset used) should take panel-wise
48+
# data into account (#5227)
49+
50+
df <- data_frame0(
51+
x = c(0, 1, 1000, 1001),
52+
y = c(-1, 1, -1, 1),
53+
g = c("A", "A", "B", "B")
54+
)
55+
p <- ggplot(df, aes(x, y))
56+
57+
# Here, x-range is large, so adjustment should be larger
58+
ld <- layer_data(p + geom_area(aes(fill = g)))
59+
expect_equal(diff(ld$x[1:2]), 1/6, tolerance = 1e-4)
60+
61+
# Here, x-ranges are smaller, so adjustment should be smaller instead of
62+
# considering the data as a whole
63+
ld <- layer_data(p + geom_area() + facet_wrap(vars(g), scales = "free_x"))
64+
expect_equal(diff(ld$x[1:2]), 1e-3, tolerance = 1e-4)
65+
66+
})

0 commit comments

Comments
 (0)