Skip to content

Commit d86969c

Browse files
committed
improve subplot domain calculations
1 parent d01dfe8 commit d86969c

File tree

2 files changed

+45
-45
lines changed

2 files changed

+45
-45
lines changed

R/subplots.R

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
#' @param which_layout adopt the layout of which plot? If the default value of
77
#' "merge" is used, all plot level layout options will be included in the final
88
#' layout. This argument also accepts a numeric vector which will restric
9-
#' @param margin a numeric value between 0 and 1. Corrsepnds to the proportion
10-
#' of plot width/height to attribute to margins between subplots.
9+
#' @param margin either a single value or four values (all between 0 and 1).
10+
#' If four values are provided, the first is used as the left margin, the second
11+
#' is used as the right margin, the third is used as the top margin, and the
12+
#' fourth is used as the bottom margin.
13+
#' If a single value is provided, it will be used as all four margins.
1114
#' @return A plotly object
1215
#' @export
1316
#' @author Carson Sievert
@@ -18,8 +21,8 @@
1821
#' }
1922

2023

21-
## TODO: add warning if geo and non-geo coordinates are used!!!
22-
subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0.1 / nrows) {
24+
## TODO: throw warning if geo and non-geo coordinates are used!!!
25+
subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0) {
2326
# note that dots is a _list of plotlys_
2427
dots <- lapply(list(...), plotly_build)
2528
# put existing plot anchors and domain information into a tidy format
@@ -62,7 +65,6 @@ subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0.1 / nrows
6265
ctr <- ctr + 1
6366
}
6467
}
65-
#browser()
6668
# put p_info into a data.frame()
6769
p_info <- Reduce(rbind, p_info)
6870
row.names(p_info) <- NULL
@@ -77,25 +79,8 @@ subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0.1 / nrows
7779
# Only do domain computations if they are _completely_ missing
7880
# (I don't think it makes sense to support partial specification of domains)
7981
if (all(is.na(with(p_info, c(xstart, xend, ystart, yend))))) {
80-
nplots <- max(p_info$key)
81-
ncols <- ceiling(nplots / nrows)
82-
xdom <- get_domains(nplots, ncols, margin)
83-
ydom <- get_domains(nplots, nrows, margin)
84-
xdf <- cbind(
85-
list2df(xdom, c("xstart", "xend")),
86-
key = seq_len(nplots)
87-
)
88-
ydf <- list2df(ydom, c("ystart", "yend"))
89-
# get_domains() currently assumes plots are drawn from _lower_ left
90-
# corner to _upper_ right, but we need them going from _upper_ left
91-
# to _lower-right_
92-
ydf <- with(ydf, data.frame(ystart = 1 - yend, yend = 1 - ystart))
93-
ydf <- ydf[order(ydf$ystart, decreasing = TRUE), ]
94-
ydf$key <- seq_len(nplots)
95-
# overwrite relevant info
96-
p_info <- p_info[!grepl("start$|end$", names(p_info))]
97-
p_info <- plyr::join(p_info, xdf, by = "key")
98-
p_info <- plyr::join(p_info, ydf, by = "key")
82+
p_info[c("xstart", "xend", "yend", "ystart")] <-
83+
get_domains(max(p_info$key), nrows, margin)
9984
}
10085
# empty plot container that we'll fill up with new info
10186
p <- list(
@@ -105,13 +90,17 @@ subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0.1 / nrows
10590
ls <- if (which_layout == "merge") {
10691
lapply(dots, "[[", "layout")
10792
} else {
108-
# TODO: warning if referencing non-exitant layouts?
93+
if (!is.numeric(which_layout)) warning("which_layout must be numeric")
94+
if (!all(idx <- which_layout %in% seq_along(dots))) {
95+
warning("which_layout is referencing non-existant layouts")
96+
which_layout <- which_layout[idx]
97+
}
10998
lapply(dots[which_layout], "[[", "layout")
11099
}
111100
ls <- ls[!vapply(ls, is.null, logical(1))]
112101
p[["layout"]] <- Reduce(modifyList, ls)
113102

114-
103+
# tack on trace, domain, and anchor information
115104
p_info$plot <- as.numeric(p_info$plot)
116105
p_info$trace <- as.numeric(p_info$trace)
117106
for (i in seq_along(p$data)) {
@@ -136,11 +125,11 @@ subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0.1 / nrows
136125
# (but overwrite domain/anchor info)
137126
l <- dots[[info$plot]]$layout
138127
p$layout[[xaxis]] <- modifyList(
139-
l[names(l) %in% "xaxis"][[1]] %||% list(),
128+
l[names(l) %in% "xaxis"],
140129
list(domain = xdom, anchor = info$yaxis)
141130
)
142131
p$layout[[yaxis]] <- modifyList(
143-
l[names(l) %in% "yaxis"][[1]] %||% list(),
132+
l[names(l) %in% "yaxis"],
144133
list(domain = ydom, anchor = info$xaxis)
145134
)
146135
p$data[[i]]$xaxis <- info$xaxis
@@ -151,23 +140,31 @@ subplot <- function(..., nrows = 1, which_layout = "merge", margin = 0.1 / nrows
151140
}
152141

153142

154-
# margins should shrink as # of plots increase
155-
get_domains <- function(nplots = 1, nsplits = 1, mar = 0.1 / nsplits,
156-
decreasing = FALSE) {
157-
if (nsplits == 1) {
158-
lapply(vector("list", nplots), function(x) c(0, 1))
159-
} else {
160-
domains <- vector("list", nsplits)
161-
for (i in seq_len(nsplits)) {
162-
l <- ((i - 1) / nsplits) + ifelse(i == 1, 0, mar)
163-
u <- (i / nsplits) - ifelse(i == nsplits, 0, mar)
164-
domains[[i]] <- c(l, u)
165-
}
166-
rep_len(domains, nplots)
143+
get_domains <- function(nplots = 1, nrows = 1, margins = 0.01) {
144+
if (length(margins) == 1) margins <- rep(margins, 4)
145+
if (length(margins) != 4) stop("margins must be length 1 or 4", call. = FALSE)
146+
ncols <- ceiling(nplots / nrows)
147+
148+
xs <- vector("list", ncols)
149+
for (i in seq_len(ncols)) {
150+
xs[[i]] <- c(
151+
xstart = ((i - 1) / ncols) + ifelse(i == 1, 0, margins[1]),
152+
xend = (i / ncols) - ifelse(i == ncols, 0, margins[2])
153+
)
167154
}
155+
xz <- rep_len(xs, nplots)
156+
157+
ys <- vector("list", nrows)
158+
for (i in seq_len(nplots)) {
159+
j <- ceiling(i / ncols)
160+
ys[[i]] <- c(
161+
ystart = 1 - ((j - 1) / nrows) - ifelse(j == 1, 0, margins[3]),
162+
yend = 1 - (j / nrows) + ifelse(j == nrows, 0, margins[4])
163+
)
164+
}
165+
list2df(Map(c, xz, ys))
168166
}
169167

170-
171168
list2df <- function(x, nms) {
172169
stopifnot(length(unique(sapply(x, length))) == 1)
173170
m <- Reduce(rbind, x)

man/subplot.Rd

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
\alias{subplot}
55
\title{View multiple plots in a single view}
66
\usage{
7-
subplot(..., nrows = 1, which_layout = "merge", margin = 0.1/nrows)
7+
subplot(..., nrows = 1, which_layout = "merge", margin = 0)
88
}
99
\arguments{
1010
\item{...}{any number of plotly objects}
@@ -16,8 +16,11 @@ Only used if no domain is already specified.}
1616
"merge" is used, all plot level layout options will be included in the final
1717
layout. This argument also accepts a numeric vector which will restric}
1818

19-
\item{margin}{a numeric value between 0 and 1. Corrsepnds to the proportion
20-
of plot width/height to attribute to margins between subplots.}
19+
\item{margin}{either a single value or four values (all between 0 and 1).
20+
If four values are provided, the first is used as the left margin, the second
21+
is used as the right margin, the third is used as the top margin, and the
22+
fourth is used as the bottom margin.
23+
If a single value is provided, it will be used as all four margins.}
2124
}
2225
\value{
2326
A plotly object

0 commit comments

Comments
 (0)