Skip to content

Commit 1c1cdc3

Browse files
authored
Reverse dodging (#5923)
* `reverse` plumbing for `position_dodge() * `reverse` plumbing in `position_jitterdodge()` * do not internally sort groups in `position_dodge()` * add test * add news bullet * Align interpretation with `position_dodge2()` * document
1 parent e0bb6a3 commit 1c1cdc3

7 files changed

+58
-15
lines changed

NEWS.md

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

3+
* `position_dodge()` and `position_jitterdodge()` now have a `reverse` argument
4+
(@teunbrand, #3610)
35
* `coord_radial(r.axis.inside)` can now take a numeric value to control
46
placement of internally placed radius axes (@teunbrand, #5805).
57
* (internal) default labels are derived in `ggplot_build()` rather than

R/position-dodge.R

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#' @param orientation Fallback orientation when the layer or the data does not
1717
#' indicate an explicit orientation, like `geom_point()`. Can be `"x"`
1818
#' (default) or `"y"`.
19+
#' @param reverse If `TRUE`, will reverse the default stacking order.
20+
#' This is useful if you're rotating both the plot and legend.
1921
#' @family position adjustments
2022
#' @export
2123
#' @examples
@@ -82,11 +84,14 @@
8284
#'
8385
#' ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
8486
#' geom_bar(position = position_dodge2(preserve = "total"))
85-
position_dodge <- function(width = NULL, preserve = "total", orientation = "x") {
87+
position_dodge <- function(width = NULL, preserve = "total", orientation = "x",
88+
reverse = FALSE) {
89+
check_bool(reverse)
8690
ggproto(NULL, PositionDodge,
8791
width = width,
8892
preserve = arg_match0(preserve, c("total", "single")),
89-
orientation = arg_match0(orientation, c("x", "y"))
93+
orientation = arg_match0(orientation, c("x", "y")),
94+
reverse = reverse
9095
)
9196
}
9297

@@ -98,6 +103,7 @@ PositionDodge <- ggproto("PositionDodge", Position,
98103
width = NULL,
99104
preserve = "total",
100105
orientation = "x",
106+
reverse = NULL,
101107
setup_params = function(self, data) {
102108
flipped_aes <- has_flipped_aes(data, default = self$orientation == "y")
103109
data <- flip_data(data, flipped_aes)
@@ -119,7 +125,8 @@ PositionDodge <- ggproto("PositionDodge", Position,
119125
list(
120126
width = self$width,
121127
n = n,
122-
flipped_aes = flipped_aes
128+
flipped_aes = flipped_aes,
129+
reverse = self$reverse %||% FALSE
123130
)
124131
},
125132

@@ -139,7 +146,8 @@ PositionDodge <- ggproto("PositionDodge", Position,
139146
name = "position_dodge",
140147
strategy = pos_dodge,
141148
n = params$n,
142-
check.width = FALSE
149+
check.width = FALSE,
150+
reverse = !params$reverse # for consistency with `position_dodge2()`
143151
)
144152
flip_data(collided, params$flipped_aes)
145153
}
@@ -164,7 +172,7 @@ pos_dodge <- function(df, width, n = NULL) {
164172

165173
# Have a new group index from 1 to number of groups.
166174
# This might be needed if the group numbers in this set don't include all of 1:n
167-
groupidx <- match(df$group, sort(unique0(df$group)))
175+
groupidx <- match(df$group, unique0(df$group))
168176

169177
# Find the center for each group, then use that to calculate xmin and xmax
170178
df$x <- df$x + width * ((groupidx - 0.5) / n - .5)

R/position-dodge2.R

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
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 reverse If `TRUE`, will reverse the default stacking order.
6-
#' This is useful if you're rotating both the plot and legend.
75
position_dodge2 <- function(width = NULL, preserve = "total",
86
padding = 0.1, reverse = FALSE) {
97
ggproto(NULL, PositionDodge2,

R/position-jitterdodge.R

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#' @param dodge.width the amount to dodge in the x direction. Defaults to 0.75,
1212
#' the default `position_dodge()` width.
1313
#' @inheritParams position_jitter
14+
#' @inheritParams position_dodge
1415
#' @export
1516
#' @examples
1617
#' set.seed(596)
@@ -19,15 +20,18 @@
1920
#' geom_boxplot(outlier.size = 0) +
2021
#' geom_point(pch = 21, position = position_jitterdodge())
2122
position_jitterdodge <- function(jitter.width = NULL, jitter.height = 0,
22-
dodge.width = 0.75, seed = NA) {
23+
dodge.width = 0.75, reverse = FALSE,
24+
seed = NA) {
2325
if (!is.null(seed) && is.na(seed)) {
2426
seed <- sample.int(.Machine$integer.max, 1L)
2527
}
28+
check_bool(reverse)
2629

2730
ggproto(NULL, PositionJitterdodge,
2831
jitter.width = jitter.width,
2932
jitter.height = jitter.height,
3033
dodge.width = dodge.width,
34+
reverse = reverse,
3135
seed = seed
3236
)
3337
}
@@ -40,6 +44,7 @@ PositionJitterdodge <- ggproto("PositionJitterdodge", Position,
4044
jitter.width = NULL,
4145
jitter.height = NULL,
4246
dodge.width = NULL,
47+
reverse = NULL,
4348

4449
required_aes = c("x", "y"),
4550

@@ -57,14 +62,21 @@ PositionJitterdodge <- ggproto("PositionJitterdodge", Position,
5762
jitter.height = self$jitter.height %||% 0,
5863
jitter.width = width / (ndodge + 2),
5964
seed = self$seed,
60-
flipped_aes = flipped_aes
65+
flipped_aes = flipped_aes,
66+
reverse = self$reverse %||% FALSE
6167
)
6268
},
6369

6470
compute_panel = function(data, params, scales) {
6571
data <- flip_data(data, params$flipped_aes)
66-
data <- collide(data, params$dodge.width, "position_jitterdodge", pos_dodge,
67-
check.width = FALSE)
72+
data <- collide(
73+
data,
74+
params$dodge.width,
75+
"position_jitterdodge",
76+
strategy = pos_dodge,
77+
check.width = FALSE,
78+
reverse = !params$reverse # for consistency with `position_dodge2()`
79+
)
6880

6981
trans_x <- if (params$jitter.width > 0) function(x) jitter(x, amount = params$jitter.width)
7082
trans_y <- if (params$jitter.height > 0) function(x) jitter(x, amount = params$jitter.height)

man/position_dodge.Rd

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

man/position_jitterdodge.Rd

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

tests/testthat/test-position_dodge.R

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,17 @@ test_that("position_dodge() can dodge points vertically", {
2323
expect_equal(layer_data(vertical)$y, c(0.75, 1.25, 1.75, 2.25), ignore_attr = "class")
2424

2525
})
26+
27+
test_that("position_dodge() can reverse the dodge order", {
28+
29+
df <- data.frame(x = c(1, 2, 2, 3, 3), group = c("A", "A", "B", "B", "C"))
30+
31+
# Use label as easy to track identifier
32+
p <- ggplot(df, aes(x, y = 1, fill = group, label = group))
33+
34+
ld <- get_layer_data(p + geom_col(position = position_dodge(reverse = TRUE)))
35+
expect_equal(ld$label[order(ld$x)], c("A", "B", "A", "C", "B"))
36+
37+
ld <- get_layer_data(p + geom_col(position = position_dodge(reverse = FALSE)))
38+
expect_equal(ld$label[order(ld$x)], c("A", "A", "B", "B", "C"))
39+
})

0 commit comments

Comments
 (0)