|
20 | 20 | #' subplot(p1, p2, p1, p2, nrows = 2)
|
21 | 21 | #' }
|
22 | 22 |
|
23 |
| - |
24 | 23 | 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 | + )) |
65 | 72 | }
|
| 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)) |
66 | 88 | }
|
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 |
86 | 91 | p <- list(
|
87 |
| - data = vector("list", nrow(p_info)) |
| 92 | + data = Reduce(c, traces), |
| 93 | + layout = Reduce(c, c(xAxes, yAxes)) |
88 | 94 | )
|
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") { |
93 | 101 | 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))) { |
95 | 103 | warning("which_layout is referencing non-existant layouts")
|
96 | 104 | which_layout <- which_layout[idx]
|
97 | 105 | }
|
98 |
| - lapply(dots[which_layout], "[[", "layout") |
| 106 | + layouts <- layouts[which_layout] |
99 | 107 | }
|
100 |
| - ls <- ls[!vapply(ls, is.null, logical(1))] |
101 |
| - p[["layout"]] <- Reduce(modifyList, ls) |
| 108 | + p$layout <- c(p$layout, Reduce(modifyList, layouts)) |
102 | 109 |
|
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 |
| - } |
139 | 110 | hash_plot(data.frame(), p)
|
140 | 111 | }
|
141 | 112 |
|
|
0 commit comments