Skip to content

Commit 4521993

Browse files
committed
first stab at subplot rewrite
1 parent 78e4612 commit 4521993

File tree

1 file changed

+76
-105
lines changed

1 file changed

+76
-105
lines changed

R/subplots.R

Lines changed: 76 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -20,122 +20,93 @@
2020
#' subplot(p1, p2, p1, p2, nrows = 2)
2121
#' }
2222

23-
2423
subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0.02) {
25-
# note that dots is a _list of plotlys_
26-
dots <- lapply(list(...), plotly_build)
27-
# put existing plot anchors and domain information into a tidy format
28-
# (geo, xaxis, or yaxis can be used to anchor traces on different plots)
29-
p_info <- list()
30-
ctr <- 1
31-
for (i in seq_along(dots)) {
32-
dat <- dots[[i]]$data
33-
layout <- dots[[i]]$layout
34-
for (j in seq_along(dat)) {
35-
tr <- dat[[j]]
36-
idx <- if (j == 1) "" else j
37-
geo <- unique(tr$geo) %||% ""
38-
# if a valid geo property exists, use that and ignore x/y axis properties
39-
info <- if (grepl("^geo[0-9]+$", geo)) {
40-
d <- layout[[paste0("geo", idx)]][["domain"]] %||% list(x = NA, y = NA)
41-
c(
42-
geo = sub("^geo1$", "geo", geo),
43-
xaxis = "",
44-
xstart = d$x[1],
45-
xend = d$x[2],
46-
yaxis = "",
47-
ystart = d$y[1],
48-
yend = d$y[2]
49-
)
50-
} else {
51-
dx <- layout[[paste0("xaxis", idx)]][["domain"]] %||% NA
52-
dy <- layout[[paste0("yaxis", idx)]][["domain"]] %||% NA
53-
c(
54-
geo = "",
55-
xaxis = unique(tr$xaxis) %||% "",
56-
xstart = dx[1],
57-
xend = dx[2],
58-
yaxis = unique(tr$yaxis) %||% "",
59-
ystart = dy[1],
60-
yend = dy[2]
61-
)
62-
}
63-
p_info[[ctr]] <- c(info, plot = i, trace = j)
64-
ctr <- ctr + 1
24+
# build each plot
25+
plots <- lapply(list(...), plotly_build)
26+
# rename axes, respecting the fact that each plot could be a subplot itself
27+
layouts <- lapply(plots, "[[", "layout")
28+
traces <- lapply(plots, "[[", "data")
29+
xAxes <- lapply(layouts, function(x) {
30+
x[grepl("^xaxis", names(x))] %||%
31+
list(xaxis = list(domain = c(0, 1), anchor = "y"))
32+
})
33+
yAxes <- lapply(layouts, function(x) {
34+
x[grepl("^yaxis", names(x))] %||%
35+
list(yaxis = list(domain = c(0, 1), anchor = "x"))
36+
})
37+
# number of x/y axes per plot
38+
xAxisN <- vapply(xAxes, length, numeric(1))
39+
yAxisN <- vapply(yAxes, length, numeric(1))
40+
# old -> new axis name dictionary
41+
xAxisMap <- setNames(
42+
unlist(lapply(xAxes, names)),
43+
paste0("xaxis", sub("^1$", "", seq_len(sum(xAxisN))))
44+
)
45+
yAxisMap <- setNames(
46+
unlist(lapply(yAxes, names)),
47+
paste0("yaxis", sub("^1$", "", seq_len(sum(yAxisN))))
48+
)
49+
# split the map by plot ID
50+
xAxisMap <- split(xAxisMap, rep(seq_along(plots), xAxisN))
51+
yAxisMap <- split(yAxisMap, rep(seq_along(plots), yAxisN))
52+
# get the domain of each "viewport"
53+
# TODO: allow control of column width and row height!
54+
domainInfo <- get_domains(length(plots), nrows, margin)
55+
for (i in seq_along(plots)) {
56+
xMap <- xAxisMap[[i]]
57+
yMap <- yAxisMap[[i]]
58+
for (j in seq_along(xAxes[[i]])) {
59+
# before bumping axis anchor, bump trace info, where appropriate
60+
traces[[i]] <- lapply(traces[[i]], function(tr) {
61+
tr$xaxis[tr$xaxis %in% sub("axis", "", xMap[[j]])] <- sub("axis", "", names(xMap[j]))
62+
tr
63+
})
64+
# bump anchors
65+
map <- yMap[yMap %in% sub("y", "yaxis", xAxes[[i]][[j]]$anchor)]
66+
xAxes[[i]][[j]]$anchor <- sub("axis", "", names(map))
67+
xAxes[[i]][[j]]$domain <- sort(scales::rescale(
68+
xAxes[[i]][[j]]$domain,
69+
as.numeric(domainInfo[i, c("xstart", "xend")]),
70+
from = c(0, 1)
71+
))
6572
}
73+
for (j in seq_along(yAxes[[i]])) {
74+
traces[[i]] <- lapply(traces[[i]], function(tr) {
75+
tr$yaxis[tr$yaxis == sub("axis", "", yMap[[j]])] <- sub("axis", "", names(yMap[j]))
76+
tr
77+
})
78+
map <- xMap[xMap %in% sub("x", "xaxis", yAxes[[i]][[j]]$anchor)]
79+
yAxes[[i]][[j]]$anchor <- sub("axis", "", names(map))
80+
yAxes[[i]][[j]]$domain <- sort(scales::rescale(
81+
yAxes[[i]][[j]]$domain,
82+
as.numeric(domainInfo[i, c("yend", "ystart")]),
83+
from = c(0, 1)
84+
))
85+
}
86+
xAxes[[i]] <- setNames(xAxes[[i]], names(xMap))
87+
yAxes[[i]] <- setNames(yAxes[[i]], names(yMap))
6688
}
67-
# put p_info into a data.frame()
68-
p_info <- Reduce(rbind, p_info)
69-
row.names(p_info) <- NULL
70-
p_info <- data.frame(p_info, stringsAsFactors = FALSE)
71-
# obtain the _actual_ plot id
72-
key <- with(p_info, paste0(geo, xaxis, yaxis, plot))
73-
p_info$key <- match(key, unique(key))
74-
# bump x/y axis anchors appropriately
75-
p_info$xaxis <- sub("^x1$", "x", paste0("x", p_info$key))
76-
p_info$yaxis <- sub("^y1$", "y", paste0("y", p_info$key))
77-
# Only do domain computations if they are _completely_ missing
78-
# (I don't think it makes sense to support partial specification of domains)
79-
if (all(is.na(with(p_info, c(xstart, xend, ystart, yend))))) {
80-
doms <- get_domains(max(p_info$key), nrows, margin)
81-
doms$key <- as.character(seq_len(nrow(doms)))
82-
p_info <- p_info[!names(p_info) %in% c("xstart", "xend", "ystart", "yend")]
83-
p_info <- merge(p_info, doms, by = "key", sort = FALSE)
84-
}
85-
# empty plot container that we'll fill up with new info
89+
90+
# start merging the plots into a single subplot
8691
p <- list(
87-
data = vector("list", nrow(p_info))
92+
data = Reduce(c, traces),
93+
layout = Reduce(c, c(xAxes, yAxes))
8894
)
89-
# merge layouts of the subplots
90-
ls <- if (which_layout == "merge") {
91-
lapply(dots, "[[", "layout")
92-
} else {
95+
# TODO: scale shape/annotation coordinates and incorporate them!
96+
# Should we throw warning if [x-y]ref != "paper"?
97+
98+
# merge non-axis layout stuff
99+
layouts <- lapply(layouts, function(x) x[!grepl("^[x-y]axis", names(x))])
100+
if (which_layout != "merge") {
93101
if (!is.numeric(which_layout)) warning("which_layout must be numeric")
94-
if (!all(idx <- which_layout %in% seq_along(dots))) {
102+
if (!all(idx <- which_layout %in% seq_along(plots))) {
95103
warning("which_layout is referencing non-existant layouts")
96104
which_layout <- which_layout[idx]
97105
}
98-
lapply(dots[which_layout], "[[", "layout")
106+
layouts <- layouts[which_layout]
99107
}
100-
ls <- ls[!vapply(ls, is.null, logical(1))]
101-
p[["layout"]] <- Reduce(modifyList, ls)
108+
p$layout <- c(p$layout, Reduce(modifyList, layouts))
102109

103-
# tack on trace, domain, and anchor information
104-
p_info$plot <- as.numeric(p_info$plot)
105-
p_info$trace <- as.numeric(p_info$trace)
106-
for (i in seq_along(p$data)) {
107-
info <- p_info[i, ]
108-
xdom <- sort(c(info$xstart, info$xend))
109-
ydom <- sort(c(info$ystart, info$yend))
110-
p$data[[i]] <- dots[[info$plot]]$data[[info$trace]]
111-
if (grepl("^geo", info$geo)) {
112-
# carry over first geo object if this one is missing
113-
p$layout[[info$geo]] <- p$layout[[info$geo]] %||% p$layout[["geo"]]
114-
# add domains to the layout
115-
p$layout[[info$geo]] <- modifyList(
116-
p$layout[[info$geo]] %||% list(),
117-
list(domain = list(x = xdom, y = ydom))
118-
)
119-
# ensure the geo anchor is a single value
120-
p$data[[i]]$geo <- info$geo
121-
} else {
122-
xaxis <- sub("x", "xaxis", info$xaxis)
123-
yaxis <- sub("y", "yaxis", info$yaxis)
124-
# does this plot contain x/y axis styling? If so, use it
125-
# (but overwrite domain/anchor info)
126-
l <- dots[[info$plot]]$layout
127-
p$layout[[xaxis]] <- modifyList(
128-
if (any(idx <- names(l) %in% "xaxis")) l[idx][[1]] else list(),
129-
list(domain = xdom, anchor = info$yaxis)
130-
)
131-
p$layout[[yaxis]] <- modifyList(
132-
if (any(idx <- names(l) %in% "yaxis")) l[idx][[1]] else list(),
133-
list(domain = ydom, anchor = info$xaxis)
134-
)
135-
p$data[[i]]$xaxis <- info$xaxis
136-
p$data[[i]]$yaxis <- info$yaxis
137-
}
138-
}
139110
hash_plot(data.frame(), p)
140111
}
141112

0 commit comments

Comments
 (0)