1#' Nudge points a fixed distance
2#'
3#' `position_nudge()` is generally useful for adjusting the position of
4#' items on discrete scales by a small amount. Nudging is built in to
5#' [geom_text()] because it's so useful for moving labels a small
6#' distance from what they're labelling.
7#'
8#' @family position adjustments
9#' @param x,y Amount of vertical and horizontal distance to move.
10#' @export
11#' @examples
12#' df <- data.frame(
13#'   x = c(1,3,2,5),
14#'   y = c("a","c","d","c")
15#' )
16#'
17#' ggplot(df, aes(x, y)) +
18#'   geom_point() +
19#'   geom_text(aes(label = y))
20#'
21#' ggplot(df, aes(x, y)) +
22#'   geom_point() +
23#'   geom_text(aes(label = y), position = position_nudge(y = -0.1))
24#'
25#' # Or, in brief
26#' ggplot(df, aes(x, y)) +
27#'   geom_point() +
28#'   geom_text(aes(label = y), nudge_y = -0.1)
29position_nudge <- function(x = 0, y = 0) {
30  ggproto(NULL, PositionNudge,
31    x = x,
32    y = y
33  )
34}
35
36#' @rdname ggplot2-ggproto
37#' @format NULL
38#' @usage NULL
39#' @export
40PositionNudge <- ggproto("PositionNudge", Position,
41  x = 0,
42  y = 0,
43
44  setup_params = function(self, data) {
45    list(x = self$x, y = self$y)
46  },
47
48  compute_layer = function(self, data, params, layout) {
49    # transform only the dimensions for which non-zero nudging is requested
50    if (any(params$x != 0)) {
51      if (any(params$y != 0)) {
52        transform_position(data, function(x) x + params$x, function(y) y + params$y)
53      } else {
54        transform_position(data, function(x) x + params$x, NULL)
55      }
56    } else if (any(params$y != 0)) {
57      transform_position(data, NULL, function(y) y + params$y)
58    } else {
59      data # if both x and y are 0 we don't need to transform
60    }
61  }
62)
63