From bbcff4d51621194016797c0b433fec9248de87e5 Mon Sep 17 00:00:00 2001 From: szkabel Date: Thu, 1 Jun 2023 12:24:03 +0300 Subject: [PATCH 1/8] Dodge-stack with tidyverse. --- R/position-dodge.R | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/R/position-dodge.R b/R/position-dodge.R index 35b426a4e7..804663ad19 100644 --- a/R/position-dodge.R +++ b/R/position-dodge.R @@ -79,10 +79,11 @@ #' #' ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + #' geom_bar(position = position_dodge2(preserve = "total")) -position_dodge <- function(width = NULL, preserve = "total") { +position_dodge <- function(width = NULL, preserve = "total", stackOverlap = "no") { ggproto(NULL, PositionDodge, width = width, - preserve = arg_match0(preserve, c("total", "single")) + preserve = arg_match0(preserve, c("total", "single")), + stackOverlap = arg_match0(stackOverlap, c("no","byExtent","byCenter")) ) } @@ -93,6 +94,7 @@ position_dodge <- function(width = NULL, preserve = "total") { PositionDodge <- ggproto("PositionDodge", Position, width = NULL, preserve = "total", + stackOverlap = "no", setup_params = function(self, data) { flipped_aes <- has_flipped_aes(data) data <- flip_data(data, flipped_aes) @@ -113,6 +115,7 @@ PositionDodge <- ggproto("PositionDodge", Position, list( width = self$width, + stackOverlap = self$stackOverlap, n = n, flipped_aes = flipped_aes ) @@ -134,6 +137,7 @@ PositionDodge <- ggproto("PositionDodge", Position, name = "position_dodge", strategy = pos_dodge, n = params$n, + stackOverlap = params$stackOverlap, check.width = FALSE ) flip_data(collided, params$flipped_aes) @@ -142,7 +146,7 @@ PositionDodge <- ggproto("PositionDodge", Position, # Dodge overlapping interval. # Assumes that each set has the same horizontal position. -pos_dodge <- function(df, width, n = NULL) { +pos_dodge <- function(df, width, n = NULL, stackOverlap = "no") { if (is.null(n)) { n <- vec_unique_count(df$group) } @@ -165,6 +169,16 @@ pos_dodge <- function(df, width, n = NULL) { df$x <- df$x + width * ((groupidx - 0.5) / n - .5) df$xmin <- df$x - d_width / n / 2 df$xmax <- df$x + d_width / n / 2 + + if (stackOverlap == "byExtent") { + tmp = df %>% group_by(group) %>% mutate(ymaxx = cumsum(ymax)) %>% mutate(ymin = ymaxx-ymax, ymax = ymaxx) + df$ymin = tmp$ymin + df$ymax = tmp$ymax + } else if (stackOverlap == "byCenter") { + tmp = df %>% group_by(group) %>% mutate(extent = ymax-ymin, ymaxx = cumsum((ymax+ymin)/2)) %>% mutate(ymin = ymaxx-extent/2, ymax = ymaxx+extent/2) + df$ymin = tmp$ymin + df$ymax = tmp$ymax + } df } From eed61568fbeca6c4143b400e500cf97f44ae3300 Mon Sep 17 00:00:00 2001 From: szkabel Date: Thu, 3 Aug 2023 08:08:35 +0300 Subject: [PATCH 2/8] Quick fix for single group dodge-stacking. --- R/position-dodge.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/position-dodge.R b/R/position-dodge.R index 804663ad19..66c8e827b5 100644 --- a/R/position-dodge.R +++ b/R/position-dodge.R @@ -151,8 +151,9 @@ pos_dodge <- function(df, width, n = NULL, stackOverlap = "no") { n <- vec_unique_count(df$group) } - if (n == 1) - return(df) + # even if it's a single group we might need to dodge stack + #if (n == 1) + # return(df) if (!all(c("xmin", "xmax") %in% names(df))) { df$xmin <- df$x From cc532253e92f65081d152dad8004d8ac2d6e73ab Mon Sep 17 00:00:00 2001 From: szkabel Date: Mon, 27 May 2024 12:30:48 +0300 Subject: [PATCH 3/8] Version error handling --- R/backports.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/backports.R b/R/backports.R index 7dea54cd3a..549a80f2c9 100644 --- a/R/backports.R +++ b/R/backports.R @@ -1,6 +1,6 @@ # Backport fix from R 3.3: # https://github.com/wch/r-source/commit/4efc81c98d262f93de9e7911aaa910f5c63cd00f -if (getRversion() < 3.3) { +if (getRversion() < "3.3") { absolute.units <- getFromNamespace("absolute.units", "grid") absolute.units.unit <- getFromNamespace("absolute.units.unit", "grid") absolute.units.unit.list <- getFromNamespace("absolute.units.unit.list", "grid") @@ -18,6 +18,6 @@ if (getRversion() < 3.3) { on_load(backport_unit_methods()) # isFALSE() is available on R (>=3.5) -if (getRversion() < 3.5) { +if (getRversion() < "3.5") { isFALSE <- function(x) is.logical(x) && length(x) == 1L && !is.na(x) && !x } From cdcde918756ba6455241da9984ecb0adc6d65950 Mon Sep 17 00:00:00 2001 From: szkabel Date: Tue, 11 Feb 2025 17:00:41 +0200 Subject: [PATCH 4/8] Transforming the functionality to use only base R --- R/position-dodge.R | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/R/position-dodge.R b/R/position-dodge.R index e126a0aad5..22da5f536f 100644 --- a/R/position-dodge.R +++ b/R/position-dodge.R @@ -178,13 +178,37 @@ pos_dodge <- function(df, width, n = NULL, stackOverlap = "no") { df$xmax <- df$x + d_width / n / 2 if (stackOverlap == "byExtent") { - tmp = df %>% group_by(group) %>% mutate(ymaxx = cumsum(ymax)) %>% mutate(ymin = ymaxx-ymax, ymax = ymaxx) - df$ymin = tmp$ymin - df$ymax = tmp$ymax + # The code chunk below is just to implement the following line without tidyverse functions, as ggplot2 can be imported without that + # df %>% group_by(group) %>% mutate(ymaxx = cumsum(ymax)) %>% mutate(ymin = ymaxx-ymax, ymax = ymaxx) + + df$ymaxx = NA # Initialize the variable. This will store the desired top of the group + groupIDs = unique(df$group) # Collect the unique groupIDs. Thi + for (gid in groupIDs) { + df$ymaxx[df$group == gid] = cumsum(df$ymax[df$group == gid]) + } + # Create the new y placements + df$ymin = df$ymaxx-df$ymax + df$ymax = df$ymaxx + + df$ymaxx = NULL # Remove the extra variable + } else if (stackOverlap == "byCenter") { - tmp = df %>% group_by(group) %>% mutate(extent = ymax-ymin, ymaxx = cumsum((ymax+ymin)/2)) %>% mutate(ymin = ymaxx-extent/2, ymax = ymaxx+extent/2) - df$ymin = tmp$ymin - df$ymax = tmp$ymax + # Similarly to above, the complicated code below is just to do the next line without tidyverse + # df %>% group_by(group) %>% mutate(extent = ymax-ymin, ymaxx = cumsum((ymax+ymin)/2)) %>% mutate(ymin = ymaxx-extent/2, ymax = ymaxx+extent/2) + + df$ymaxx = NA # Initialize the variable. This will store the desired top of the group + df$extent = NA # Initialize the variable storing the extent of the geom + groupIDs = unique(df$group) # Collect the unique groupIDs. Thi + for (gid in groupIDs) { + df$ymaxx[df$group == gid] = cumsum((df$ymax[df$group == gid] + df$ymin[df$group == gid])/2) + } + df$extent = df$ymax - df$ymin + # Create the new y placements + df$ymin = df$ymaxx-df$extent/2 + df$ymax = df$ymaxx+df$extent/2 + + df$ymaxx = NULL # Remove the extra variable + df$extent = NULL # Remove the extra variable } df From 8de19078cbba89e263c8c3307a212f0ef092bfe5 Mon Sep 17 00:00:00 2001 From: szkabel Date: Tue, 11 Feb 2025 17:52:05 +0200 Subject: [PATCH 5/8] Change to snake_case --- R/position-dodge.R | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/R/position-dodge.R b/R/position-dodge.R index 4e870fb576..fca0f31cc6 100644 --- a/R/position-dodge.R +++ b/R/position-dodge.R @@ -87,13 +87,13 @@ #' ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + #' geom_bar(position = position_dodge2(preserve = "total")) position_dodge <- function(width = NULL, preserve = "total", orientation = "x", - reverse = FALSE, stackOverlap = "no") { + reverse = FALSE, stack_overlap = "no") { check_bool(reverse) ggproto(NULL, PositionDodge, width = width, preserve = arg_match0(preserve, c("total", "single")), orientation = arg_match0(orientation, c("x", "y")), - stackOverlap = arg_match0(stackOverlap, c("no","byExtent","byCenter")), + stack_overlap = arg_match0(stack_overlap, c("no","by_extent","by_center")), reverse = reverse ) } @@ -105,7 +105,7 @@ position_dodge <- function(width = NULL, preserve = "total", orientation = "x", PositionDodge <- ggproto("PositionDodge", Position, width = NULL, preserve = "total", - stackOverlap = "no", + stack_overlap = "no", orientation = "x", reverse = NULL, default_aes = aes(order = NULL), @@ -138,7 +138,7 @@ PositionDodge <- ggproto("PositionDodge", Position, list( width = self$width, - stackOverlap = self$stackOverlap, + stack_overlap = self$stack_overlap, n = n, flipped_aes = flipped_aes, reverse = self$reverse %||% FALSE @@ -183,7 +183,7 @@ PositionDodge <- ggproto("PositionDodge", Position, # Dodge overlapping interval. # Assumes that each set has the same horizontal position. -pos_dodge <- function(df, width, n = NULL, stackOverlap = "no") { +pos_dodge <- function(df, width, n = NULL, stack_overlap = "no") { if (is.null(n)) { n <- vec_unique_count(df$group) } @@ -208,13 +208,13 @@ pos_dodge <- function(df, width, n = NULL, stackOverlap = "no") { df$xmin <- df$x - d_width / n / 2 df$xmax <- df$x + d_width / n / 2 - if (stackOverlap == "byExtent") { + if (stack_overlap == "by_extent") { # The code chunk below is just to implement the following line without tidyverse functions, as ggplot2 can be imported without that # df %>% group_by(group) %>% mutate(ymaxx = cumsum(ymax)) %>% mutate(ymin = ymaxx-ymax, ymax = ymaxx) df$ymaxx = NA # Initialize the variable. This will store the desired top of the group - groupIDs = unique(df$group) # Collect the unique groupIDs. Thi - for (gid in groupIDs) { + group_ids = unique(df$group) # Collect the unique groupIDs. Thi + for (gid in group_ids) { df$ymaxx[df$group == gid] = cumsum(df$ymax[df$group == gid]) } # Create the new y placements @@ -223,14 +223,14 @@ pos_dodge <- function(df, width, n = NULL, stackOverlap = "no") { df$ymaxx = NULL # Remove the extra variable - } else if (stackOverlap == "byCenter") { + } else if (stack_overlap == "by_center") { # Similarly to above, the complicated code below is just to do the next line without tidyverse # df %>% group_by(group) %>% mutate(extent = ymax-ymin, ymaxx = cumsum((ymax+ymin)/2)) %>% mutate(ymin = ymaxx-extent/2, ymax = ymaxx+extent/2) df$ymaxx = NA # Initialize the variable. This will store the desired top of the group df$extent = NA # Initialize the variable storing the extent of the geom - groupIDs = unique(df$group) # Collect the unique groupIDs. Thi - for (gid in groupIDs) { + group_ids = unique(df$group) # Collect the unique groupIDs. Thi + for (gid in group_ids) { df$ymaxx[df$group == gid] = cumsum((df$ymax[df$group == gid] + df$ymin[df$group == gid])/2) } df$extent = df$ymax - df$ymin From 40322c7a321b2b5a20ff79ab7db987b36df3705c Mon Sep 17 00:00:00 2001 From: szkabel Date: Tue, 11 Feb 2025 18:35:18 +0200 Subject: [PATCH 6/8] Adding documentation and bugfix from the merge --- R/position-dodge.R | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/R/position-dodge.R b/R/position-dodge.R index fca0f31cc6..353dfcc9a0 100644 --- a/R/position-dodge.R +++ b/R/position-dodge.R @@ -18,6 +18,10 @@ #' (default) or `"y"`. #' @param reverse If `TRUE`, will reverse the default stacking order. #' This is useful if you're rotating both the plot and legend. +#' @param stack_overlap Specifies if and how to stack the dodged geoms. Possible +#' values are `"no"` (default), `"by_extent"` or `"by_center"`. This parameter +#' implements the dodge and stack functionality together. Use `"by_extent"` for +#' columns and `"by_center"` for errorbars. #' @family position adjustments #' @eval rd_aesthetics("position", "dodge") #' @@ -175,6 +179,7 @@ PositionDodge <- ggproto("PositionDodge", Position, strategy = pos_dodge, n = params$n, check.width = FALSE, + stack_overlap = params$stack_overlap, reverse = !params$reverse # for consistency with `position_dodge2()` ) flip_data(collided, params$flipped_aes) From 2a373a6810ceebb51d05aa106c0f398313b2647e Mon Sep 17 00:00:00 2001 From: szkabel Date: Tue, 11 Feb 2025 23:04:10 +0200 Subject: [PATCH 7/8] Doc update --- man/position_dodge.Rd | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/man/position_dodge.Rd b/man/position_dodge.Rd index 5706e93e02..b498903e50 100644 --- a/man/position_dodge.Rd +++ b/man/position_dodge.Rd @@ -9,7 +9,8 @@ position_dodge( width = NULL, preserve = "total", orientation = "x", - reverse = FALSE + reverse = FALSE, + stack_overlap = "no" ) position_dodge2( @@ -34,6 +35,11 @@ indicate an explicit orientation, like \code{geom_point()}. Can be \code{"x"} \item{reverse}{If \code{TRUE}, will reverse the default stacking order. This is useful if you're rotating both the plot and legend.} +\item{stack_overlap}{Specifies if and how to stack the dodged geoms. Possible +values are \code{"no"} (default), \code{"by_extent"} or \code{"by_center"}. This parameter +implements the dodge and stack functionality together. Use \code{"by_extent"} for +columns and \code{"by_center"} for errorbars.} + \item{padding}{Padding between elements at the same position. Elements are shrunk by this proportion to allow space between them. Defaults to 0.1.} } From c15e38b5b998662414fb485272a9c553380abaf2 Mon Sep 17 00:00:00 2001 From: szkabel Date: Wed, 12 Feb 2025 00:15:29 +0200 Subject: [PATCH 8/8] Fixing argument name nomenclature --- R/position-dodge.R | 18 +++++++++--------- man/position_dodge.Rd | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/R/position-dodge.R b/R/position-dodge.R index 353dfcc9a0..f1b37d3ff0 100644 --- a/R/position-dodge.R +++ b/R/position-dodge.R @@ -18,7 +18,7 @@ #' (default) or `"y"`. #' @param reverse If `TRUE`, will reverse the default stacking order. #' This is useful if you're rotating both the plot and legend. -#' @param stack_overlap Specifies if and how to stack the dodged geoms. Possible +#' @param stack.overlap Specifies if and how to stack the dodged geoms. Possible #' values are `"no"` (default), `"by_extent"` or `"by_center"`. This parameter #' implements the dodge and stack functionality together. Use `"by_extent"` for #' columns and `"by_center"` for errorbars. @@ -91,13 +91,13 @@ #' ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + #' geom_bar(position = position_dodge2(preserve = "total")) position_dodge <- function(width = NULL, preserve = "total", orientation = "x", - reverse = FALSE, stack_overlap = "no") { + reverse = FALSE, stack.overlap = "no") { check_bool(reverse) ggproto(NULL, PositionDodge, width = width, preserve = arg_match0(preserve, c("total", "single")), orientation = arg_match0(orientation, c("x", "y")), - stack_overlap = arg_match0(stack_overlap, c("no","by_extent","by_center")), + stack.overlap = arg_match0(stack.overlap, c("no","by_extent","by_center")), reverse = reverse ) } @@ -109,7 +109,7 @@ position_dodge <- function(width = NULL, preserve = "total", orientation = "x", PositionDodge <- ggproto("PositionDodge", Position, width = NULL, preserve = "total", - stack_overlap = "no", + stack.overlap = "no", orientation = "x", reverse = NULL, default_aes = aes(order = NULL), @@ -142,7 +142,7 @@ PositionDodge <- ggproto("PositionDodge", Position, list( width = self$width, - stack_overlap = self$stack_overlap, + stack.overlap = self$stack.overlap, n = n, flipped_aes = flipped_aes, reverse = self$reverse %||% FALSE @@ -179,7 +179,7 @@ PositionDodge <- ggproto("PositionDodge", Position, strategy = pos_dodge, n = params$n, check.width = FALSE, - stack_overlap = params$stack_overlap, + stack.overlap = params$stack.overlap, reverse = !params$reverse # for consistency with `position_dodge2()` ) flip_data(collided, params$flipped_aes) @@ -188,7 +188,7 @@ PositionDodge <- ggproto("PositionDodge", Position, # Dodge overlapping interval. # Assumes that each set has the same horizontal position. -pos_dodge <- function(df, width, n = NULL, stack_overlap = "no") { +pos_dodge <- function(df, width, n = NULL, stack.overlap = "no") { if (is.null(n)) { n <- vec_unique_count(df$group) } @@ -213,7 +213,7 @@ pos_dodge <- function(df, width, n = NULL, stack_overlap = "no") { df$xmin <- df$x - d_width / n / 2 df$xmax <- df$x + d_width / n / 2 - if (stack_overlap == "by_extent") { + if (stack.overlap == "by_extent") { # The code chunk below is just to implement the following line without tidyverse functions, as ggplot2 can be imported without that # df %>% group_by(group) %>% mutate(ymaxx = cumsum(ymax)) %>% mutate(ymin = ymaxx-ymax, ymax = ymaxx) @@ -228,7 +228,7 @@ pos_dodge <- function(df, width, n = NULL, stack_overlap = "no") { df$ymaxx = NULL # Remove the extra variable - } else if (stack_overlap == "by_center") { + } else if (stack.overlap == "by_center") { # Similarly to above, the complicated code below is just to do the next line without tidyverse # df %>% group_by(group) %>% mutate(extent = ymax-ymin, ymaxx = cumsum((ymax+ymin)/2)) %>% mutate(ymin = ymaxx-extent/2, ymax = ymaxx+extent/2) diff --git a/man/position_dodge.Rd b/man/position_dodge.Rd index b498903e50..7481d9538d 100644 --- a/man/position_dodge.Rd +++ b/man/position_dodge.Rd @@ -10,7 +10,7 @@ position_dodge( preserve = "total", orientation = "x", reverse = FALSE, - stack_overlap = "no" + stack.overlap = "no" ) position_dodge2( @@ -35,7 +35,7 @@ indicate an explicit orientation, like \code{geom_point()}. Can be \code{"x"} \item{reverse}{If \code{TRUE}, will reverse the default stacking order. This is useful if you're rotating both the plot and legend.} -\item{stack_overlap}{Specifies if and how to stack the dodged geoms. Possible +\item{stack.overlap}{Specifies if and how to stack the dodged geoms. Possible values are \code{"no"} (default), \code{"by_extent"} or \code{"by_center"}. This parameter implements the dodge and stack functionality together. Use \code{"by_extent"} for columns and \code{"by_center"} for errorbars.}