Skip to content

Commit b0a4a40

Browse files
authored
Dodge points vertically (#5845)
* allow custom default in `has_flipped_aes()` * Use fallback orientation * add test * add news bullet
1 parent da60e8f commit b0a4a40

File tree

6 files changed

+38
-8
lines changed

6 files changed

+38
-8
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+
* `geom_point()` can be dodged vertically by using
4+
`position_dodge(..., orientation = "y")` (@teunbrand, #5809).
35
* Fixed bug where `na.value` was incorrectly mapped to non-`NA` values
46
(@teunbrand, #5756).
57
* Fixed bug in `guide_custom()` that would throw error with `theme_void()`

R/position-dodge.R

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#' geoms. See the examples.
1414
#' @param preserve Should dodging preserve the `"total"` width of all elements
1515
#' at a position, or the width of a `"single"` element?
16+
#' @param orientation Fallback orientation when the layer or the data does not
17+
#' indicate an explicit orientation, like `geom_point()`. Can be `"x"`
18+
#' (default) or `"y"`.
1619
#' @family position adjustments
1720
#' @export
1821
#' @examples
@@ -79,10 +82,11 @@
7982
#'
8083
#' ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
8184
#' geom_bar(position = position_dodge2(preserve = "total"))
82-
position_dodge <- function(width = NULL, preserve = "total") {
85+
position_dodge <- function(width = NULL, preserve = "total", orientation = "x") {
8386
ggproto(NULL, PositionDodge,
8487
width = width,
85-
preserve = arg_match0(preserve, c("total", "single"))
88+
preserve = arg_match0(preserve, c("total", "single")),
89+
orientation = arg_match0(orientation, c("x", "y"))
8690
)
8791
}
8892

@@ -93,8 +97,9 @@ position_dodge <- function(width = NULL, preserve = "total") {
9397
PositionDodge <- ggproto("PositionDodge", Position,
9498
width = NULL,
9599
preserve = "total",
100+
orientation = "x",
96101
setup_params = function(self, data) {
97-
flipped_aes <- has_flipped_aes(data)
102+
flipped_aes <- has_flipped_aes(data, default = self$orientation == "y")
98103
data <- flip_data(data, flipped_aes)
99104
if (is.null(data$xmin) && is.null(data$xmax) && is.null(self$width)) {
100105
cli::cli_warn(c(

R/utilities.R

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,8 @@ switch_orientation <- function(aesthetics) {
476476
#' @param main_is_optional Is the main axis aesthetic optional and, if not
477477
#' given, set to `0`
478478
#' @param flip Logical. Is the layer flipped.
479+
#' @param default The logical value to return if no orientation can be discerned
480+
#' from the data.
479481
#'
480482
#' @return `has_flipped_aes()` returns `TRUE` if it detects a layer in the other
481483
#' orientation and `FALSE` otherwise. `flip_data()` will return the input
@@ -492,7 +494,7 @@ switch_orientation <- function(aesthetics) {
492494
has_flipped_aes <- function(data, params = list(), main_is_orthogonal = NA,
493495
range_is_orthogonal = NA, group_has_equal = FALSE,
494496
ambiguous = FALSE, main_is_continuous = FALSE,
495-
main_is_optional = FALSE) {
497+
main_is_optional = FALSE, default = FALSE) {
496498
# Is orientation already encoded in data?
497499
if (!is.null(data$flipped_aes)) {
498500
not_na <- which(!is.na(data$flipped_aes))
@@ -561,8 +563,7 @@ has_flipped_aes <- function(data, params = list(), main_is_orthogonal = NA,
561563
}
562564
}
563565

564-
# default to no
565-
FALSE
566+
isTRUE(default)
566567
}
567568
#' @rdname bidirection
568569
#' @export

man/bidirection.Rd

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

man/position_dodge.Rd

Lines changed: 5 additions & 1 deletion
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
@@ -9,3 +9,17 @@ test_that("can control whether to preserve total or individual width", {
99
expect_equal(layer_data(p_total)$x, new_mapped_discrete(c(1, 1.75, 2.25)))
1010
expect_equal(layer_data(p_single)$x, new_mapped_discrete(c(0.75, 1.75, 2.25)))
1111
})
12+
13+
test_that("position_dodge() can dodge points vertically", {
14+
15+
df <- data.frame(x = c(1, 2, 3, 4), y = c("a", "a", "b", "b"))
16+
17+
horizontal <- ggplot(df, aes(y, x, group = seq_along(x))) +
18+
geom_point(position = position_dodge(width = 1, orientation = "x"))
19+
vertical <- ggplot(df, aes(x, y, group = seq_along(x))) +
20+
geom_point(position = position_dodge(width = 1, orientation = "y"))
21+
22+
expect_equal(layer_data(horizontal)$x, c(0.75, 1.25, 1.75, 2.25), ignore_attr = "class")
23+
expect_equal(layer_data(vertical)$y, c(0.75, 1.25, 1.75, 2.25), ignore_attr = "class")
24+
25+
})

0 commit comments

Comments
 (0)