Skip to content

Commit 00c930d

Browse files
committed
Add position_dodge2nudge_to()
Fix bugs in position_dodgenudge_to() and in position_nudge_to() (in new wrapper)
1 parent e620be0 commit 00c930d

26 files changed

+1416
-27
lines changed

DESCRIPTION

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Collate:
8585
'position-dodge-nudge-to.R'
8686
'position-dodge-nudge.R'
8787
'position-dodge2-nudge.R'
88+
'position-dodge2nudge-to.R'
8889
'position-jitter-nudge.R'
8990
'position-nudge-center.R'
9091
'position-nudge-line.R'

NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export(GeomXMarginPoint)
2525
export(GeomYMarginArrow)
2626
export(GeomYMarginGrob)
2727
export(GeomYMarginPoint)
28+
export(PositionDodge2AndNudgeTo)
2829
export(PositionDodgeNudgeTo)
2930
export(PositionFillAndNudge)
3031
export(PositionNudgeCenter)
@@ -74,6 +75,7 @@ export(geom_y_margin_grob)
7475
export(geom_y_margin_point)
7576
export(position_dodge2_keep)
7677
export(position_dodge2nudge)
78+
export(position_dodge2nudge_to)
7779
export(position_dodge_keep)
7880
export(position_dodgenudge)
7981
export(position_dodgenudge_to)

NEWS.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ editor_options:
88

99
# ggpp 0.5.9
1010

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

1414
# ggpp 0.5.8-1
1515

R/position-dodge-nudge-to.R

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

223227
compute_layer = function(self, data, params, layout) {
224-
# operate on the dodged positions
225-
data = ggplot2::ggproto_parent(ggplot2::PositionDodge, self)$compute_layer(data, params, layout)
226-
227-
x_dodged <- data$x
228-
y_dodged <- data$y
229228
x_orig <- data$x
230229
y_orig <- data$y
230+
if (!is.na(params$width)) {
231+
# operate on the dodged positions
232+
data = ggplot2::ggproto_parent(ggplot2::PositionDodge, self)$compute_layer(data, params, layout)
233+
}
234+
x_dodged <- data$x
235+
y_dodged <- data$y
231236

232237
# compute/convert x nudges
233238
if (!length(params$x)) {
234239
# set default x
235240
if (params$x.action == "none") {
236241
params$x <- rep_len(0, nrow(data))
237242
} else if (params$x.action == "spread") {
238-
params$x <- range(x_orig)
243+
params$x <- range(x_dodged)
239244
}
240245
} else if (is.numeric(params$x)) {
241246
# check user supplied x
@@ -245,9 +250,9 @@ PositionDodgeNudgeTo <-
245250
if (params$x.action == "none") {
246251
# recycle or trim x as needed
247252
if (params$x.reorder) {
248-
params$x <- rep_len(params$x, nrow(data))[order(order(data$x))] - x_orig
253+
params$x <- rep_len(params$x, nrow(data))[order(order(data$x))] - x_dodged
249254
} else {
250-
params$x <- rep_len(params$x, nrow(data)) - x_orig
255+
params$x <- rep_len(params$x, nrow(data)) - x_dodged
251256
}
252257
} else if (params$x.action == "spread") {
253258
params$x <- range(params$x)
@@ -263,7 +268,7 @@ PositionDodgeNudgeTo <-
263268
# evenly spaced sequence of positions ordered as in data
264269
params$x <- seq(from = params$x[1],
265270
to = params$x[2],
266-
length.out = nrow(data))[order(order(data$x))] - x_orig
271+
length.out = nrow(data))[order(order(data$x))] - x_dodged
267272
}
268273
# other strategies to distribute positions could be added here
269274
}
@@ -274,7 +279,7 @@ PositionDodgeNudgeTo <-
274279
if (params$y.action == "none") {
275280
params$y <- rep_len(0, nrow(data))
276281
} else if (params$y.action == "spread") {
277-
params$y <- range(y_orig)
282+
params$y <- range(y_dodged)
278283
}
279284
} else if (is.numeric(params$y)) {
280285
# check user supplied y
@@ -284,9 +289,9 @@ PositionDodgeNudgeTo <-
284289
if (params$y.action == "none") {
285290
# recycle or trim y as needed
286291
if (params$y.reorder) {
287-
params$y <- rep_len(params$y, nrow(data))[order(order(data$y))] - y_orig
292+
params$y <- rep_len(params$y, nrow(data))[order(order(data$y))] - y_dodged
288293
} else {
289-
params$y <- rep_len(params$y, nrow(data)) - y_orig
294+
params$y <- rep_len(params$y, nrow(data)) - y_dodged
290295
}
291296
} else if (params$y.action == "spread") {
292297
params$y <- range(params$y)
@@ -301,7 +306,7 @@ PositionDodgeNudgeTo <-
301306
# evenly spaced sequence ordered as in data
302307
params$y <- seq(from = params$y[1],
303308
to = params$y[2],
304-
length.out = nrow(data))[order(order(data$y))] - y_orig
309+
length.out = nrow(data))[order(order(data$y))] - y_dodged
305310
}
306311
# other strategies could be added here
307312
}
@@ -318,7 +323,7 @@ PositionDodgeNudgeTo <-
318323
data <- transform_position(data, NULL, function(y) y + params$y)
319324
}
320325
# add original position
321-
if (params$kept.origin == "dodged") {
326+
if (params$kept.origin == "dodged" && !is.na(params$width)) {
322327
data$x_orig <- x_dodged
323328
data$y_orig <- y_dodged
324329
} else if (params$kept.origin == "original") {
@@ -350,7 +355,7 @@ position_nudge_to <-
350355
y.expansion = 0,
351356
kept.origin = c("original", "none")) {
352357

353-
position_dodgenudge_to(width = 1,
358+
position_dodgenudge_to(width = NA_real_, # used as flag to disable dodging
354359
preserve = "total",
355360
x = x,
356361
y = y,

R/position-dodge2nudge-to.R

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#' @rdname position_dodgenudge_to
2+
#'
3+
#' @export
4+
#'
5+
position_dodge2nudge_to <-
6+
function(width = 1,
7+
preserve = c("total", "single"),
8+
padding = 0.1,
9+
reverse = FALSE,
10+
x = NULL,
11+
y = NULL,
12+
x.action = c("none", "spread"),
13+
y.action = c("none", "spread"),
14+
x.distance = "equal",
15+
y.distance = "equal",
16+
x.expansion = 0,
17+
y.expansion = 0,
18+
kept.origin = c("dodged", "original", "none")) {
19+
20+
stopifnot("'x' must be NULL or of mode numeric" = length(x) == 0 ||
21+
(!anyNA(x) && mode(x) == "numeric"))
22+
stopifnot("'y' must be NULL or of mode numeric" = length(y) == 0 ||
23+
(!anyNA(y) && mode(y) == "numeric"))
24+
25+
# this works as long as nudge and mapped variable are of the same class
26+
# ggplot2's behaviour has been in the past and seems to be again to expect
27+
# numeric seconds for POSIXct and numeric days for Date time shifts
28+
if (lubridate::is.instant(x)) {
29+
x <- as.numeric(x)
30+
}
31+
if (lubridate::is.instant(y)) {
32+
y <- as.numeric(y)
33+
}
34+
35+
ggplot2::ggproto(NULL, PositionDodge2AndNudgeTo,
36+
x = x,
37+
y = y,
38+
x.action = rlang::arg_match(x.action),
39+
y.action = rlang::arg_match(y.action),
40+
x.distance = x.distance,
41+
y.distance = y.distance,
42+
x.expansion = rep_len(x.expansion, 2),
43+
y.expansion = rep_len(y.expansion, 2),
44+
kept.origin = rlang::arg_match(kept.origin),
45+
width = width,
46+
preserve = rlang::arg_match(preserve),
47+
padding = padding,
48+
reverse = reverse
49+
)
50+
}
51+
52+
#' @rdname ggpp-ggproto
53+
#' @format NULL
54+
#' @usage NULL
55+
#' @export
56+
PositionDodge2AndNudgeTo <-
57+
ggplot2::ggproto(
58+
"PositionDodge2AndNudgeTo",
59+
Position,
60+
x = NULL,
61+
y = NULL,
62+
63+
setup_params = function(self, data) {
64+
list(x = self$x,
65+
y = self$y,
66+
x.action = self$x.action,
67+
y.action = self$y.action,
68+
x.distance = self$x.distance,
69+
y.distance = self$y.distance,
70+
x.expansion = self$x.expansion,
71+
y.expansion = self$y.expansion,
72+
x.reorder = !is.null(self$x) && length(self$x) > 1 && length(self$x) < nrow(data),
73+
y.reorder = !is.null(self$y) && length(self$y) > 1 && length(self$y) < nrow(data),
74+
kept.origin = self$kept.origin,
75+
width = self$width,
76+
preserve = self$preserve,
77+
padding = self$padding,
78+
reverse = self$reverse
79+
)
80+
},
81+
82+
compute_layer = function(self, data, params, layout) {
83+
x_orig <- data$x
84+
y_orig <- data$y
85+
if (!is.na(params$width)) {
86+
# operate on the dodged positions
87+
data = ggplot2::ggproto_parent(ggplot2::PositionDodge2, self)$compute_layer(data, params, layout)
88+
}
89+
x_dodged <- data$x
90+
y_dodged <- data$y
91+
92+
# compute/convert x nudges
93+
if (!length(params$x)) {
94+
# set default x
95+
if (params$x.action == "none") {
96+
params$x <- rep_len(0, nrow(data))
97+
} else if (params$x.action == "spread") {
98+
params$x <- range(x_dodged)
99+
}
100+
} else if (is.numeric(params$x)) {
101+
# check user supplied x
102+
if (length(params$x) > nrow(data)) {
103+
warning("Argument 'x' longer than data: some values dropped!")
104+
}
105+
if (params$x.action == "none") {
106+
# recycle or trim x as needed
107+
if (params$x.reorder) {
108+
params$x <- rep_len(params$x, nrow(data))[order(order(data$x))] - x_dodged
109+
} else {
110+
params$x <- rep_len(params$x, nrow(data)) - x_dodged
111+
}
112+
} else if (params$x.action == "spread") {
113+
params$x <- range(params$x)
114+
}
115+
}
116+
117+
if (params$x.action == "spread") {
118+
# apply x.expansion to x
119+
x.spread <- diff(params$x)
120+
params$x[1] <- params$x[1] - params$x.expansion[1] * x.spread
121+
params$x[2] <- params$x[2] + params$x.expansion[2] * x.spread
122+
if (params$x.distance == "equal") {
123+
# evenly spaced sequence of positions ordered as in data
124+
params$x <- seq(from = params$x[1],
125+
to = params$x[2],
126+
length.out = nrow(data))[order(order(data$x))] - x_dodged
127+
}
128+
# other strategies to distribute positions could be added here
129+
}
130+
131+
# compute/convert y nudges
132+
if (!length(params$y)) {
133+
# set default y
134+
if (params$y.action == "none") {
135+
params$y <- rep_len(0, nrow(data))
136+
} else if (params$y.action == "spread") {
137+
params$y <- range(y_dodged)
138+
}
139+
} else if (is.numeric(params$y)) {
140+
# check user supplied y
141+
if (length(params$y) > nrow(data)) {
142+
warning("Argument 'y' longer than data: some values dropped!")
143+
}
144+
if (params$y.action == "none") {
145+
# recycle or trim y as needed
146+
if (params$y.reorder) {
147+
params$y <- rep_len(params$y, nrow(data))[order(order(data$y))] - y_dodged
148+
} else {
149+
params$y <- rep_len(params$y, nrow(data)) - y_dodged
150+
}
151+
} else if (params$y.action == "spread") {
152+
params$y <- range(params$y)
153+
}
154+
}
155+
156+
if (params$y.action == "spread") {
157+
y.spread <- diff(params$y)
158+
params$y[1] <- params$y[1] - params$y.expansion[1] * y.spread
159+
params$y[2] <- params$y[2] + params$y.expansion[2] * y.spread
160+
if (params$y.distance == "equal") {
161+
# evenly spaced sequence ordered as in data
162+
params$y <- seq(from = params$y[1],
163+
to = params$y[2],
164+
length.out = nrow(data))[order(order(data$y))] - y_dodged
165+
}
166+
# other strategies could be added here
167+
}
168+
169+
# As in 'ggplot2' we apply the nudge to xmin, xmax, xend, ymin, ymax, and yend.
170+
# Transform the dimensions for which not all nudges are zero
171+
if (any(params$x != 0)) {
172+
if (any(params$y != 0)) {
173+
data <- transform_position(data, function(x) x + params$x, function(y) y + params$y)
174+
} else {
175+
data <- transform_position(data, function(x) x + params$x, NULL)
176+
}
177+
} else if (any(params$y != 0)) {
178+
data <- transform_position(data, NULL, function(y) y + params$y)
179+
}
180+
# add original position
181+
if (params$kept.origin == "dodged" && !is.na(params$width)) {
182+
data$x_orig <- x_dodged
183+
data$y_orig <- y_dodged
184+
} else if (params$kept.origin == "original") {
185+
data$x_orig <- x_orig
186+
data$y_orig <- y_orig
187+
}
188+
189+
data
190+
},
191+
192+
compute_panel = function(self, data, params, scales) {
193+
ggplot2::ggproto_parent(PositionDodge2, self)$compute_panel(data, params, scales)
194+
}
195+
)

R/position-jitter-nudge.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ PositionJitterAndNudge <-
235235
x_orig <- data$x
236236
y_orig <- data$y
237237

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

241241
x_jittered <- data$x

man/ggpp-ggproto.Rd

+7-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)