diff --git a/NEWS.md b/NEWS.md index d3d9ceb105..8b15b1bdba 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # ggplot2 (development version) +* `geom_col()` and `geom_bar()` gain a new `just` argument. This is set to `0.5` + by default; use `just = 0`/`just = 1` to place columns on the left/right + of the axis breaks. + (@wurli, #4899) + * Fix a bug in `position_jitter()` where infinity values were dropped (@javlon, #4790). @@ -14,6 +19,7 @@ columns were dropped and warns about this. If stats intend to drop data columns they can declare them in the new field `dropped_aes`. (@clauswilke, #3250) + * Added `stat_align()` to align data without common x-coordinates prior to stacking. This is now the default stat for `geom_area()` (@thomasp85, #4850) diff --git a/R/geom-bar.r b/R/geom-bar.r index 4f5b3f9aa6..91e3ebce65 100644 --- a/R/geom-bar.r +++ b/R/geom-bar.r @@ -40,7 +40,13 @@ #' automatically determines the orientation from the aesthetic mapping. In the #' rare event that this fails it can be given explicitly by setting `orientation` #' to either `"x"` or `"y"`. See the *Orientation* section for more detail. -#' @param width Bar width. By default, set to 90% of the resolution of the data. +#' @param just Adjustment for column placement. Set to `0.5` by default, meaning +#' that columns will be centered about axis breaks. Set to `0` or `1` to place +#' columns to the left/right of axis breaks. Note that this argument may have +#' unintended behaviour when used with alternative positions, e.g. +#' `position_dodge()`. +#' @param width Bar width. By default, set to 90% of the [resolution()] of the +#' data. #' @param geom,stat Override the default connection between `geom_bar()` and #' `stat_count()`. #' @examples @@ -80,9 +86,17 @@ #' ggplot(df, aes(x)) + geom_bar() #' # cf. a histogram of the same data #' ggplot(df, aes(x)) + geom_histogram(binwidth = 0.5) +#' +#' # Use `just` to control how columns are aligned with axis breaks: +#' df <- data.frame(x = as.Date(c("2020-01-01", "2020-02-01")), y = 1:2) +#' # Columns centered on the first day of the month +#' ggplot(df, aes(x, y)) + geom_col(just = 0.5) +#' # Columns begin on the first day of the month +#' ggplot(df, aes(x, y)) + geom_col(just = 1) geom_bar <- function(mapping = NULL, data = NULL, stat = "count", position = "stack", ..., + just = 0.5, width = NULL, na.rm = FALSE, orientation = NA, @@ -97,6 +111,7 @@ geom_bar <- function(mapping = NULL, data = NULL, show.legend = show.legend, inherit.aes = inherit.aes, params = list2( + just = just, width = width, na.rm = na.rm, orientation = orientation, @@ -123,16 +138,18 @@ GeomBar <- ggproto("GeomBar", GeomRect, params }, - extra_params = c("na.rm", "orientation"), + extra_params = c("just", "na.rm", "orientation"), setup_data = function(data, params) { data$flipped_aes <- params$flipped_aes data <- flip_data(data, params$flipped_aes) data$width <- data$width %||% params$width %||% (resolution(data$x, FALSE) * 0.9) + data$just <- params$just %||% 0.5 data <- transform(data, ymin = pmin(y, 0), ymax = pmax(y, 0), - xmin = x - width / 2, xmax = x + width / 2, width = NULL + xmin = x - width * (1 - just), xmax = x + width * just, + width = NULL, just = NULL ) flip_data(data, params$flipped_aes) }, diff --git a/R/geom-col.r b/R/geom-col.r index b75aa34e18..bb3872b76c 100644 --- a/R/geom-col.r +++ b/R/geom-col.r @@ -3,6 +3,7 @@ geom_col <- function(mapping = NULL, data = NULL, position = "stack", ..., + just = 0.5, width = NULL, na.rm = FALSE, show.legend = NA, @@ -17,6 +18,7 @@ geom_col <- function(mapping = NULL, data = NULL, show.legend = show.legend, inherit.aes = inherit.aes, params = list2( + just = just, width = width, na.rm = na.rm, ... @@ -42,16 +44,19 @@ GeomCol <- ggproto("GeomCol", GeomRect, params }, - extra_params = c("na.rm", "orientation"), + extra_params = c("just", "na.rm", "orientation"), setup_data = function(data, params) { data$flipped_aes <- params$flipped_aes data <- flip_data(data, params$flipped_aes) data$width <- data$width %||% params$width %||% (resolution(data$x, FALSE) * 0.9) - data <- transform(data, - ymin = pmin(y, 0), ymax = pmax(y, 0), - xmin = x - width / 2, xmax = x + width / 2, width = NULL + data$just <- params$just %||% 0.5 + data <- transform( + data, + ymin = pmin(y, 0), ymax = pmax(y, 0), + xmin = x - width * (1 - just), xmax = x + width * just, + width = NULL, just = NULL ) flip_data(data, params$flipped_aes) }, diff --git a/man/geom_bar.Rd b/man/geom_bar.Rd index 3b35d724fe..416267f0e6 100644 --- a/man/geom_bar.Rd +++ b/man/geom_bar.Rd @@ -12,6 +12,7 @@ geom_bar( stat = "count", position = "stack", ..., + just = 0.5, width = NULL, na.rm = FALSE, orientation = NA, @@ -24,6 +25,7 @@ geom_col( data = NULL, position = "stack", ..., + just = 0.5, width = NULL, na.rm = FALSE, show.legend = NA, @@ -74,7 +76,14 @@ often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} -\item{width}{Bar width. By default, set to 90\% of the resolution of the data.} +\item{just}{Adjustment for column placement. Set to \code{0.5} by default, meaning +that columns will be centered about axis breaks. Set to \code{0} or \code{1} to place +columns to the left/right of axis breaks. Note that this argument may have +unintended behaviour when used with alternative positions, e.g. +\code{position_dodge()}.} + +\item{width}{Bar width. By default, set to 90\% of the \code{\link[=resolution]{resolution()}} of the +data.} \item{na.rm}{If \code{FALSE}, the default, missing values are removed with a warning. If \code{TRUE}, missing values are silently removed.} @@ -213,6 +222,13 @@ df <- data.frame(x = rep(c(2.9, 3.1, 4.5), c(5, 10, 4))) ggplot(df, aes(x)) + geom_bar() # cf. a histogram of the same data ggplot(df, aes(x)) + geom_histogram(binwidth = 0.5) + +# Use `just` to control how columns are aligned with axis breaks: +df <- data.frame(x = as.Date(c("2020-01-01", "2020-02-01")), y = 1:2) +# Columns centered on the first day of the month +ggplot(df, aes(x, y)) + geom_col(just = 0.5) +# Columns begin on the first day of the month +ggplot(df, aes(x, y)) + geom_col(just = 1) } \seealso{ \code{\link[=geom_histogram]{geom_histogram()}} for continuous data, diff --git a/tests/testthat/test-geom-col.R b/tests/testthat/test-geom-col.R index d4e158ccce..4421ba54a2 100644 --- a/tests/testthat/test-geom-col.R +++ b/tests/testthat/test-geom-col.R @@ -28,3 +28,22 @@ test_that("geom_col works in both directions", { y$flipped_aes <- NULL expect_identical(x, flip_data(y, TRUE)[,names(x)]) }) + +test_that("geom_col supports alignment of columns", { + dat <- data_frame(x = c("a", "b"), y = c(1.2, 2.5)) + + p <- ggplot(dat, aes(x, y)) + geom_col(just = 0.5) + y <- layer_data(p) + expect_equal(as.numeric(y$xmin), c(0.55, 1.55)) + expect_equal(as.numeric(y$xmax), c(1.45, 2.45)) + + p <- ggplot(dat, aes(x, y)) + geom_col(just = 0.0) + y <- layer_data(p) + expect_equal(as.numeric(y$xmin), c(0.1, 1.1)) + expect_equal(as.numeric(y$xmax), c(1.0, 2.0)) + + p <- ggplot(dat, aes(x, y)) + geom_col(just = 1.0) + y <- layer_data(p) + expect_equal(as.numeric(y$xmin), c(1.0, 2.0)) + expect_equal(as.numeric(y$xmax), c(1.9, 2.9)) +})