Skip to content

Commit

Permalink
Add position_dodge2nudge_to()
Browse files Browse the repository at this point in the history
Fix bugs in  position_dodgenudge_to() and in position_nudge_to() (in new wrapper)
  • Loading branch information
aphalo committed Feb 21, 2025
1 parent e620be0 commit 00c930d
Show file tree
Hide file tree
Showing 26 changed files with 1,416 additions and 27 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Collate:
'position-dodge-nudge-to.R'
'position-dodge-nudge.R'
'position-dodge2-nudge.R'
'position-dodge2nudge-to.R'
'position-jitter-nudge.R'
'position-nudge-center.R'
'position-nudge-line.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export(GeomXMarginPoint)
export(GeomYMarginArrow)
export(GeomYMarginGrob)
export(GeomYMarginPoint)
export(PositionDodge2AndNudgeTo)
export(PositionDodgeNudgeTo)
export(PositionFillAndNudge)
export(PositionNudgeCenter)
Expand Down Expand Up @@ -74,6 +75,7 @@ export(geom_y_margin_grob)
export(geom_y_margin_point)
export(position_dodge2_keep)
export(position_dodge2nudge)
export(position_dodge2nudge_to)
export(position_dodge_keep)
export(position_dodgenudge)
export(position_dodgenudge_to)
Expand Down
4 changes: 2 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ editor_options:

# ggpp 0.5.9

- Add `position_dodgenudge_to()` that allows the action of `position_nudge_to()`
to preceded by dodging.
- Add `position_dodgenudge_to()` and `position_dodge2nudge_to()` that allow the
action of `position_nudge_to()` to be combined with dodging.

# ggpp 0.5.8-1

Expand Down
37 changes: 21 additions & 16 deletions R/position-dodge-nudge-to.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#' Nudge labels to new positions
#' Nudge or dodge plus nudge labels to new positions
#'
#' \code{position_dodgenudge_to()} is generally useful for adjusting the
#' position of labels or text, both on a discrete or continuous scale.
Expand All @@ -18,6 +18,10 @@
#' geoms. See the examples.
#' @param preserve Should dodging preserve the total width of all elements at a
#' position, or the width of a single element?.
#' @param padding Padding between elements at the same position. Elements are
#' shrunk by this proportion to allow space between them. Defaults to 0.1.
#' @param reverse If TRUE, will reverse the default stacking order. This is
#' useful if you're rotating both the plot and legend.
#' @param x,y Coordinates of the destination position. A vector of mode
#' \code{numeric}, that is extended if needed, to the same length as rows
#' there are in \code{data}. The default, \code{NULL}, leaves the original
Expand Down Expand Up @@ -221,21 +225,22 @@ PositionDodgeNudgeTo <-
},

compute_layer = function(self, data, params, layout) {
# operate on the dodged positions
data = ggplot2::ggproto_parent(ggplot2::PositionDodge, self)$compute_layer(data, params, layout)

x_dodged <- data$x
y_dodged <- data$y
x_orig <- data$x
y_orig <- data$y
if (!is.na(params$width)) {
# operate on the dodged positions
data = ggplot2::ggproto_parent(ggplot2::PositionDodge, self)$compute_layer(data, params, layout)
}
x_dodged <- data$x
y_dodged <- data$y

# compute/convert x nudges
if (!length(params$x)) {
# set default x
if (params$x.action == "none") {
params$x <- rep_len(0, nrow(data))
} else if (params$x.action == "spread") {
params$x <- range(x_orig)
params$x <- range(x_dodged)
}
} else if (is.numeric(params$x)) {
# check user supplied x
Expand All @@ -245,9 +250,9 @@ PositionDodgeNudgeTo <-
if (params$x.action == "none") {
# recycle or trim x as needed
if (params$x.reorder) {
params$x <- rep_len(params$x, nrow(data))[order(order(data$x))] - x_orig
params$x <- rep_len(params$x, nrow(data))[order(order(data$x))] - x_dodged
} else {
params$x <- rep_len(params$x, nrow(data)) - x_orig
params$x <- rep_len(params$x, nrow(data)) - x_dodged
}
} else if (params$x.action == "spread") {
params$x <- range(params$x)
Expand All @@ -263,7 +268,7 @@ PositionDodgeNudgeTo <-
# evenly spaced sequence of positions ordered as in data
params$x <- seq(from = params$x[1],
to = params$x[2],
length.out = nrow(data))[order(order(data$x))] - x_orig
length.out = nrow(data))[order(order(data$x))] - x_dodged
}
# other strategies to distribute positions could be added here
}
Expand All @@ -274,7 +279,7 @@ PositionDodgeNudgeTo <-
if (params$y.action == "none") {
params$y <- rep_len(0, nrow(data))
} else if (params$y.action == "spread") {
params$y <- range(y_orig)
params$y <- range(y_dodged)
}
} else if (is.numeric(params$y)) {
# check user supplied y
Expand All @@ -284,9 +289,9 @@ PositionDodgeNudgeTo <-
if (params$y.action == "none") {
# recycle or trim y as needed
if (params$y.reorder) {
params$y <- rep_len(params$y, nrow(data))[order(order(data$y))] - y_orig
params$y <- rep_len(params$y, nrow(data))[order(order(data$y))] - y_dodged
} else {
params$y <- rep_len(params$y, nrow(data)) - y_orig
params$y <- rep_len(params$y, nrow(data)) - y_dodged
}
} else if (params$y.action == "spread") {
params$y <- range(params$y)
Expand All @@ -301,7 +306,7 @@ PositionDodgeNudgeTo <-
# evenly spaced sequence ordered as in data
params$y <- seq(from = params$y[1],
to = params$y[2],
length.out = nrow(data))[order(order(data$y))] - y_orig
length.out = nrow(data))[order(order(data$y))] - y_dodged
}
# other strategies could be added here
}
Expand All @@ -318,7 +323,7 @@ PositionDodgeNudgeTo <-
data <- transform_position(data, NULL, function(y) y + params$y)
}
# add original position
if (params$kept.origin == "dodged") {
if (params$kept.origin == "dodged" && !is.na(params$width)) {
data$x_orig <- x_dodged
data$y_orig <- y_dodged
} else if (params$kept.origin == "original") {
Expand Down Expand Up @@ -350,7 +355,7 @@ position_nudge_to <-
y.expansion = 0,
kept.origin = c("original", "none")) {

position_dodgenudge_to(width = 1,
position_dodgenudge_to(width = NA_real_, # used as flag to disable dodging
preserve = "total",
x = x,
y = y,
Expand Down
195 changes: 195 additions & 0 deletions R/position-dodge2nudge-to.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#' @rdname position_dodgenudge_to
#'
#' @export
#'
position_dodge2nudge_to <-
function(width = 1,
preserve = c("total", "single"),
padding = 0.1,
reverse = FALSE,
x = NULL,
y = NULL,
x.action = c("none", "spread"),
y.action = c("none", "spread"),
x.distance = "equal",
y.distance = "equal",
x.expansion = 0,
y.expansion = 0,
kept.origin = c("dodged", "original", "none")) {

stopifnot("'x' must be NULL or of mode numeric" = length(x) == 0 ||
(!anyNA(x) && mode(x) == "numeric"))
stopifnot("'y' must be NULL or of mode numeric" = length(y) == 0 ||
(!anyNA(y) && mode(y) == "numeric"))

# this works as long as nudge and mapped variable are of the same class
# ggplot2's behaviour has been in the past and seems to be again to expect
# numeric seconds for POSIXct and numeric days for Date time shifts
if (lubridate::is.instant(x)) {
x <- as.numeric(x)
}
if (lubridate::is.instant(y)) {
y <- as.numeric(y)
}

ggplot2::ggproto(NULL, PositionDodge2AndNudgeTo,
x = x,
y = y,
x.action = rlang::arg_match(x.action),
y.action = rlang::arg_match(y.action),
x.distance = x.distance,
y.distance = y.distance,
x.expansion = rep_len(x.expansion, 2),
y.expansion = rep_len(y.expansion, 2),
kept.origin = rlang::arg_match(kept.origin),
width = width,
preserve = rlang::arg_match(preserve),
padding = padding,
reverse = reverse
)
}

#' @rdname ggpp-ggproto
#' @format NULL
#' @usage NULL
#' @export
PositionDodge2AndNudgeTo <-
ggplot2::ggproto(
"PositionDodge2AndNudgeTo",
Position,
x = NULL,
y = NULL,

setup_params = function(self, data) {
list(x = self$x,
y = self$y,
x.action = self$x.action,
y.action = self$y.action,
x.distance = self$x.distance,
y.distance = self$y.distance,
x.expansion = self$x.expansion,
y.expansion = self$y.expansion,
x.reorder = !is.null(self$x) && length(self$x) > 1 && length(self$x) < nrow(data),
y.reorder = !is.null(self$y) && length(self$y) > 1 && length(self$y) < nrow(data),
kept.origin = self$kept.origin,
width = self$width,
preserve = self$preserve,
padding = self$padding,
reverse = self$reverse
)
},

compute_layer = function(self, data, params, layout) {
x_orig <- data$x
y_orig <- data$y
if (!is.na(params$width)) {
# operate on the dodged positions
data = ggplot2::ggproto_parent(ggplot2::PositionDodge2, self)$compute_layer(data, params, layout)
}
x_dodged <- data$x
y_dodged <- data$y

# compute/convert x nudges
if (!length(params$x)) {
# set default x
if (params$x.action == "none") {
params$x <- rep_len(0, nrow(data))
} else if (params$x.action == "spread") {
params$x <- range(x_dodged)
}
} else if (is.numeric(params$x)) {
# check user supplied x
if (length(params$x) > nrow(data)) {
warning("Argument 'x' longer than data: some values dropped!")
}
if (params$x.action == "none") {
# recycle or trim x as needed
if (params$x.reorder) {
params$x <- rep_len(params$x, nrow(data))[order(order(data$x))] - x_dodged
} else {
params$x <- rep_len(params$x, nrow(data)) - x_dodged
}
} else if (params$x.action == "spread") {
params$x <- range(params$x)
}
}

if (params$x.action == "spread") {
# apply x.expansion to x
x.spread <- diff(params$x)
params$x[1] <- params$x[1] - params$x.expansion[1] * x.spread
params$x[2] <- params$x[2] + params$x.expansion[2] * x.spread
if (params$x.distance == "equal") {
# evenly spaced sequence of positions ordered as in data
params$x <- seq(from = params$x[1],
to = params$x[2],
length.out = nrow(data))[order(order(data$x))] - x_dodged
}
# other strategies to distribute positions could be added here
}

# compute/convert y nudges
if (!length(params$y)) {
# set default y
if (params$y.action == "none") {
params$y <- rep_len(0, nrow(data))
} else if (params$y.action == "spread") {
params$y <- range(y_dodged)
}
} else if (is.numeric(params$y)) {
# check user supplied y
if (length(params$y) > nrow(data)) {
warning("Argument 'y' longer than data: some values dropped!")
}
if (params$y.action == "none") {
# recycle or trim y as needed
if (params$y.reorder) {
params$y <- rep_len(params$y, nrow(data))[order(order(data$y))] - y_dodged
} else {
params$y <- rep_len(params$y, nrow(data)) - y_dodged
}
} else if (params$y.action == "spread") {
params$y <- range(params$y)
}
}

if (params$y.action == "spread") {
y.spread <- diff(params$y)
params$y[1] <- params$y[1] - params$y.expansion[1] * y.spread
params$y[2] <- params$y[2] + params$y.expansion[2] * y.spread
if (params$y.distance == "equal") {
# evenly spaced sequence ordered as in data
params$y <- seq(from = params$y[1],
to = params$y[2],
length.out = nrow(data))[order(order(data$y))] - y_dodged
}
# other strategies could be added here
}

# As in 'ggplot2' we apply the nudge to xmin, xmax, xend, ymin, ymax, and yend.
# Transform the dimensions for which not all nudges are zero
if (any(params$x != 0)) {
if (any(params$y != 0)) {
data <- transform_position(data, function(x) x + params$x, function(y) y + params$y)
} else {
data <- transform_position(data, function(x) x + params$x, NULL)
}
} else if (any(params$y != 0)) {
data <- transform_position(data, NULL, function(y) y + params$y)
}
# add original position
if (params$kept.origin == "dodged" && !is.na(params$width)) {
data$x_orig <- x_dodged
data$y_orig <- y_dodged
} else if (params$kept.origin == "original") {
data$x_orig <- x_orig
data$y_orig <- y_orig
}

data
},

compute_panel = function(self, data, params, scales) {
ggplot2::ggproto_parent(PositionDodge2, self)$compute_panel(data, params, scales)
}
)
2 changes: 1 addition & 1 deletion R/position-jitter-nudge.R
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ PositionJitterAndNudge <-
x_orig <- data$x
y_orig <- data$y

# operate on the dodged positions
# operate on the jittered positions
data = ggplot2::ggproto_parent(ggplot2::PositionJitter, self)$compute_layer(data, params, layout)

x_jittered <- data$x
Expand Down
12 changes: 7 additions & 5 deletions man/ggpp-ggproto.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 00c930d

Please sign in to comment.