Skip to content

Commit 8e82de3

Browse files
committed
experimental weave-swarm hybrid
1 parent 17c4698 commit 8e82de3

File tree

1 file changed

+109
-11
lines changed

1 file changed

+109
-11
lines changed

R/binning_methods.R

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
bin_dots = function(x, y, binwidth,
6767
heightratio = 1,
6868
stackratio = 1,
69-
layout = c("bin", "weave", "hex", "swarm", "bar"),
69+
layout = c("bin", "weave", "hex", "swarm", "swarm2", "bar"),
7070
side = c("topright", "top", "right", "bottomleft", "bottom", "left", "topleft", "bottomright", "both"),
7171
orientation = c("horizontal", "vertical", "y", "x"),
7272
overlaps = "nudge"
@@ -131,15 +131,21 @@ bin_dots = function(x, y, binwidth,
131131

132132
d$row = NULL
133133
},
134-
swarm = {
135-
stop_if_not_installed("beeswarm", '{.help ggdist::geom_dots}(layout = "swarm")')
136-
137-
swarm_xy = beeswarm::swarmy(d[[x]], d[[y]],
138-
xsize = h$binwidth, ysize = h$y_spacing,
139-
log = "", cex = 1,
140-
side = switch_side(side, orientation, topright = 1, bottomleft = -1, both = 0),
141-
compact = TRUE
142-
)
134+
swarm2 = , swarm = {
135+
swarm_xy = if (layout == "swarm") {
136+
stop_if_not_installed("beeswarm", '{.help ggdist::geom_dots}(layout = "swarm")')
137+
beeswarm::swarmy(d[[x]], d[[y]],
138+
xsize = h$binwidth, ysize = h$y_spacing,
139+
log = "", cex = 1,
140+
side = switch_side(side, orientation, topright = 1, bottomleft = -1, both = 0),
141+
compact = TRUE
142+
)
143+
} else {
144+
weave_swarm(d[[x]], d[[y]],
145+
xsize = h$binwidth, ysize = h$y_spacing,
146+
side = switch_side(side, orientation, topright = 1, bottomleft = -1, both = 0)
147+
)
148+
}
143149

144150
y_origin = d[[y]]
145151
d[[x]] = swarm_xy[["x"]]
@@ -260,7 +266,7 @@ find_dotplot_binwidth = function(
260266
maxheight,
261267
heightratio = 1,
262268
stackratio = 1,
263-
layout = c("bin", "weave", "hex", "swarm", "bar")
269+
layout = c("bin", "weave", "hex", "swarm", "swarm2", "bar")
264270
) {
265271
layout = match.arg(layout)
266272
x = sort(as.numeric(x), na.last = TRUE)
@@ -618,6 +624,98 @@ wilkinson_bin_from_center = function(x, width) {
618624
}
619625

620626

627+
# weave swarm -------------------------------------------------------------
628+
629+
#' Weave/swarm hybrid
630+
#'
631+
#' @param x sorted x values
632+
#' @param y y values (must be constant)
633+
#' @noRd
634+
weave_swarm = function(x, y, xsize, ysize = xsize, side = 1) {
635+
y_grid = 4
636+
637+
can_place_candidate = function(candidate, last_placed, last_rows) {
638+
candidate >= last_placed + xsize &&
639+
all(map_lgl(seq_len(y_grid - 1), function(i) {
640+
y_offset = i / y_grid
641+
candidate >= (tail(last_rows[[i]][last_rows[[i]] <= candidate], 1) + sqrt(1 - y_offset^2) * xsize) &&
642+
candidate <= (head(last_rows[[i]][candidate < last_rows[[i]]], 1) - sqrt(1 - y_offset^2) * xsize)
643+
}))
644+
}
645+
646+
place_row = function(reverse = FALSE, both = side == 0) {
647+
if (length(remaining) == 0) return()
648+
649+
kth_last_row = function(k, rows) c(-Inf, rows[max(length(rows) + 1 - k, 0)][1][[1]] %||% numeric(), Inf)
650+
last_rows = lapply(seq_len(y_grid), kth_last_row, rows)
651+
if (both) last_rows_bottom = lapply(seq_len(y_grid), kth_last_row, rows_bottom)
652+
candidates = remaining
653+
if (reverse) {
654+
last_rows = lapply(last_rows, function(r) rev(-r))
655+
if (both) last_rows_bottom = lapply(last_rows_bottom, function(r) rev(-r))
656+
candidates = rev(-candidates)
657+
}
658+
659+
row = numeric()
660+
if (both) row_bottom = numeric()
661+
next_remaining = numeric()
662+
last_placed = -Inf
663+
if (both) last_placed_bottom = -Inf
664+
665+
for (candidate in candidates) {
666+
if (can_place_candidate(candidate, last_placed, last_rows)) {
667+
row = c(row, candidate)
668+
last_placed = candidate
669+
} else if (both && can_place_candidate(candidate, last_placed_bottom, last_rows_bottom)) {
670+
row_bottom = c(row_bottom, candidate)
671+
last_placed_bottom = candidate
672+
} else {
673+
next_remaining = c(next_remaining, candidate)
674+
}
675+
}
676+
677+
if (reverse) {
678+
row = rev(-row)
679+
if (both) row_bottom = rev(-row_bottom)
680+
next_remaining = rev(-next_remaining)
681+
}
682+
rows <<- c(rows, list(row))
683+
if (both) rows_bottom <<- c(rows_bottom, list(row_bottom))
684+
remaining <<- next_remaining
685+
}
686+
687+
remaining = x
688+
rows = list()
689+
both = side == 0
690+
691+
place_row(both = FALSE)
692+
rows_bottom = rows
693+
694+
while (length(remaining) > 0) {
695+
for (i in seq_len(y_grid - 1)) place_row()
696+
for (i in seq_len(y_grid)) place_row(reverse = TRUE)
697+
place_row()
698+
}
699+
700+
row_y = function(rows, side) (seq_along(rows) - 1) / y_grid * ysize * side
701+
df = data.frame(
702+
x = unlist(rows),
703+
y = rep(row_y(rows, side = if (both) 1 else side), lengths(rows))
704+
)
705+
if (both) {
706+
df = rbind(
707+
df,
708+
data.frame(
709+
x = unlist(rows_bottom[-1]),
710+
y = rep(row_y(rows_bottom, side = -1)[-1], lengths(rows_bottom[-1]))
711+
)
712+
)
713+
}
714+
df = df[order(df$x), ]
715+
df$y = df$y + y
716+
df
717+
}
718+
621719
# dynamic binning method selection ----------------------------------------
622720

623721
automatic_bin = function(x, width, layout = "bin") {

0 commit comments

Comments
 (0)