diff --git a/NEWS.md b/NEWS.md index 64dc53b67c..5733b1e4ef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -64,6 +64,8 @@ * Updated documentation for `geom_contour()` to correctly reflect argument precedence between `bins` and `binwidth`. (@eliocamp, #4651) +* Dots in `geom_dotplot()` are now correctly aligned to the baseline when + `stackratio != 1` and `stackdir != "up"` (@mjskay, #4614) # ggplot2 3.3.5 This is a very small release focusing on fixing a couple of untenable issues diff --git a/R/geom-dotplot.r b/R/geom-dotplot.r index e866e834a0..a087e6cb4c 100644 --- a/R/geom-dotplot.r +++ b/R/geom-dotplot.r @@ -288,7 +288,7 @@ GeomDotplot <- ggproto("GeomDotplot", Geom, ggname("geom_dotplot", dotstackGrob(stackaxis = stackaxis, x = tdata$x, y = tdata$y, dotdia = dotdianpc, - stackposition = tdata$stackpos, stackratio = stackratio, + stackposition = tdata$stackpos, stackdir = stackdir, stackratio = stackratio, default.units = "npc", gp = gpar(col = alpha(tdata$colour, tdata$alpha), fill = alpha(tdata$fill, tdata$alpha), diff --git a/R/grob-dotstack.r b/R/grob-dotstack.r index dc25dc24eb..05907b751f 100644 --- a/R/grob-dotstack.r +++ b/R/grob-dotstack.r @@ -4,6 +4,7 @@ dotstackGrob <- function( stackaxis = "y", dotdia = unit(1, "npc"), # Dot diameter in the non-stack axis, should be in npc stackposition = 0, # Position of each dot in the stack, relative to origin + stackdir = "up", # Stacking direction ("up", "down", "center", or "centerwhole") stackratio = 1, # Stacking height of dots (.75 means 25% dot overlap) default.units = "npc", name = NULL, gp = gpar(), vp = NULL) { @@ -17,7 +18,7 @@ dotstackGrob <- function( warn("Unit type of dotdia should be 'npc'") grob(x = x, y = y, stackaxis = stackaxis, dotdia = dotdia, - stackposition = stackposition, stackratio = stackratio, + stackposition = stackposition, stackdir = stackdir, stackratio = stackratio, name = name, gp = gp, vp = vp, cl = "dotstackGrob") } # Only cross-version reliable way to check the unit of a unit object @@ -31,14 +32,27 @@ makeContext.dotstackGrob <- function(x, recording = TRUE) { xmm <- convertX(x$x, "mm", valueOnly = TRUE) ymm <- convertY(x$y, "mm", valueOnly = TRUE) + # When stacking up (or down), stackratios != 1 will cause the bottom (top) + # edge of the first dot in a stack to no longer touch the origin, as + # stackpositions are expanded or contracted away from the dotstack's origin. + # The stackoffset corrects that misalignment so that the first dot just + # touches the dotstack's origin. + if (is.null(x$stackdir) || x$stackdir == "up") { + stackoffset <- (1 - x$stackratio) / 2 + } else if (x$stackdir == "down") { + stackoffset <- -(1 - x$stackratio) / 2 + } else { + stackoffset <- 0 + } + if (x$stackaxis == "x") { dotdiamm <- convertY(x$dotdia, "mm", valueOnly = TRUE) - xpos <- xmm + dotdiamm * (x$stackposition * x$stackratio + (1 - x$stackratio) / 2) + xpos <- xmm + dotdiamm * (x$stackposition * x$stackratio + stackoffset) ypos <- ymm } else if (x$stackaxis == "y") { dotdiamm <- convertX(x$dotdia, "mm", valueOnly = TRUE) xpos <- xmm - ypos <- ymm + dotdiamm * (x$stackposition * x$stackratio + (1 - x$stackratio) / 2) + ypos <- ymm + dotdiamm * (x$stackposition * x$stackratio + stackoffset) } circleGrob( diff --git a/tests/testthat/_snaps/geom-dotplot/stackratio-1-5.svg b/tests/testthat/_snaps/geom-dotplot/stackratio-1-5.svg new file mode 100755 index 0000000000..09021c0550 --- /dev/null +++ b/tests/testthat/_snaps/geom-dotplot/stackratio-1-5.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-1.0 +-0.5 +0.0 +0.5 +1.0 + + + + + + + + +3 +6 +9 +x +count +stackratio = 1.5 + + diff --git a/tests/testthat/test-geom-dotplot.R b/tests/testthat/test-geom-dotplot.R index 8049961508..13b5c58787 100644 --- a/tests/testthat/test-geom-dotplot.R +++ b/tests/testthat/test-geom-dotplot.R @@ -226,3 +226,16 @@ test_that("geom_dotplot draws correctly", { ggplot(dat2, aes(0, x)) + geom_dotplot(binwidth = .4, binaxis = "y", stackdir = "center") )) }) + +test_that("stackratio != 1 works", { + df <- data.frame(x = c(rep(1, 3), rep(2, 2))) + + expect_doppelganger("stackratio = 1.5", + ggplot(df) + + geom_hline(yintercept = 0) + + geom_dotplot(aes(x), binwidth = 0.5, stackdir = "down", stackratio = 1.5, fill = NA) + + geom_dotplot(aes(x + 3), binwidth = 0.5, stackdir = "up", stackratio = 1.5, fill = NA) + + geom_dotplot(aes(x + 6), binwidth = 0.5, stackdir = "center", stackratio = 1.5, fill = NA) + + geom_dotplot(aes(x + 9), binwidth = 0.5, stackdir = "centerwhole", stackratio = 1.5, fill = NA) + ) +})