Skip to content

Commit 90e6c6b

Browse files
authored
Make position_dodge2() work for geom_violin() (#6664)
* add `group_row` parameter * run-length encode data for violins * document * use dot.case for argument name * lol at my incompetence * add violin snapshot * protect against missing columns * add test for correctness * add news bullet * placate the older R versions
1 parent 5b928ae commit 90e6c6b

File tree

6 files changed

+135
-6
lines changed

6 files changed

+135
-6
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
* Added `preserve` argument to `position_jitterdodge()` (@teunbrand, #6584).
1919
* Fixed `position_jitterdodge(jitter.height, jitter.width)` applying to the
2020
wrong dimension with flipped geoms (@teunbrand, #6535).
21+
* New `position_dodge2(group.row)` argument that can be set to `"many"` to
22+
dodge groups with more than one row, such as in `geom_violin()`
23+
(@teunbrand, #6663)
2124

2225
# ggplot2 4.0.1
2326

R/position-dodge2.R

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
#' @rdname position_dodge
33
#' @param padding Padding between elements at the same position. Elements are
44
#' shrunk by this proportion to allow space between them. Defaults to 0.1.
5+
#' @param group.row Relationship between groups and rows. Can be `"single"` if
6+
#' every row represents a single group, or `"many"` if many rows represent
7+
#' a group.
58
position_dodge2 <- function(width = NULL, preserve = "total",
6-
padding = 0.1, reverse = FALSE) {
9+
padding = 0.1, reverse = FALSE,
10+
group.row = "single") {
711
ggproto(NULL, PositionDodge2,
812
width = width,
913
preserve = arg_match0(preserve, c("total", "single")),
1014
padding = padding,
11-
reverse = reverse
15+
reverse = reverse,
16+
group_row = arg_match0(group.row, c("single", "many"))
1217
)
1318
}
1419

@@ -20,6 +25,7 @@ PositionDodge2 <- ggproto("PositionDodge2", PositionDodge,
2025
preserve = "total",
2126
padding = 0.1,
2227
reverse = FALSE,
28+
group_row = "single",
2329

2430
setup_params = function(self, data) {
2531
flipped_aes <- has_flipped_aes(data)
@@ -48,14 +54,22 @@ PositionDodge2 <- ggproto("PositionDodge2", PositionDodge,
4854
n = n,
4955
padding = self$padding,
5056
reverse = self$reverse,
51-
flipped_aes = flipped_aes
57+
flipped_aes = flipped_aes,
58+
group_row = self$group_row
5259
)
5360
},
5461

5562
compute_panel = function(data, params, scales) {
5663
data <- flip_data(data, params$flipped_aes)
64+
key <- NULL
65+
columns <- intersect(c("group", "x", "xmin", "xmax"), names(data))
66+
if (isTRUE(params$group_row == "many") && length(columns) > 0) {
67+
# Run-length encode (RLE) relevant variables
68+
key <- vec_unrep(data[columns])
69+
}
70+
5771
collided <- collide2(
58-
data,
72+
key$key %||% data,
5973
params$width,
6074
name = "position_dodge2",
6175
strategy = pos_dodge2,
@@ -64,7 +78,15 @@ PositionDodge2 <- ggproto("PositionDodge2", PositionDodge,
6478
check.width = FALSE,
6579
reverse = params$reverse
6680
)
67-
flip_data(collided, params$flipped_aes)
81+
82+
if (!is.null(key)) {
83+
# Decode RLE to full data
84+
data[columns] <- vec_rep_each(collided[columns], key$times)
85+
} else {
86+
data <- collided
87+
}
88+
89+
flip_data(data, params$flipped_aes)
6890
}
6991
)
7092

man/position_dodge.Rd

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/geom-violin/dodge2.svg

Lines changed: 67 additions & 0 deletions
Loading

tests/testthat/test-geom-violin.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ test_that("geom_violin draws correctly", {
125125
quantile.linewidth = 2
126126
)
127127
)
128+
expect_doppelganger("dodge2", {
129+
ggplot(dat, aes(x = "foo", y = y, fill = x)) +
130+
geom_violin(position = position_dodge2(group.row = "many"))
131+
})
128132

129133
dat2 <- data_frame(x = rep(factor(LETTERS[1:3]), 30), y = rnorm(90), g = rep(factor(letters[5:6]), 45))
130134
expect_doppelganger("grouping on x and fill",

tests/testthat/test-position-dodge2.R

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,34 @@ test_that("rectangles are dodged", {
3535
expect_false(any(duplicated(find_x_overlaps(get_layer_data(p)))))
3636
})
3737

38+
test_that("groups with multiple rows are dodged", {
39+
40+
n_per_group <- function(x, g) {
41+
vapply(split(x, g), vec_unique_count, integer(1), USE.NAMES = FALSE)
42+
}
43+
44+
p <- ggplot(
45+
data_frame(
46+
x = "x",
47+
y = 1:6,
48+
g = rep(LETTERS[1:3], 3:1)
49+
),
50+
aes(x, y, colour = g)
51+
)
52+
53+
singles <- get_layer_data(
54+
p + geom_point(position = position_dodge2(width = 1, group.row = "single"))
55+
)
56+
57+
expect_equal(n_per_group(singles$x, singles$group), 3:1)
58+
59+
multi <- get_layer_data(
60+
p + geom_point(position = position_dodge2(width = 1, group.row = "many"))
61+
)
62+
63+
expect_all_equal(n_per_group(multi$x, multi$group), 1)
64+
})
65+
3866
test_that("cols at the same x position are dodged", {
3967
df <- data_frame(
4068
x = c("a", "a", "b"),

0 commit comments

Comments
 (0)