Skip to content

Commit 8b15c00

Browse files
committed
make code a bit more readable; improve axis sharing logic
1 parent 0020348 commit 8b15c00

File tree

1 file changed

+63
-68
lines changed

1 file changed

+63
-68
lines changed

R/subplots.R

Lines changed: 63 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, margin = 0.02
9797
axes <- vapply(x$annotations, function(a) identical(a$annotationType, "axis"), logical(1))
9898
x$annotations[!axes]
9999
})
100-
# collect axis objects
100+
# collect axis objects (note a _single_ geo object counts a both an x and y)
101101
xAxes <- lapply(layouts, function(lay) {
102102
lay[grepl("^xaxis|^geo", names(lay))] %||% list(xaxis = list(domain = c(0, 1)))
103103
})
@@ -115,15 +115,21 @@ subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, margin = 0.02
115115
yAxisN <- vapply(yAxes, length, numeric(1))
116116
# old -> new axis name dictionary
117117
ncols <- ceiling(length(plots) / nrows)
118-
xAxisID <- if (shareX) {
119-
rep(rep(1:ncols, length.out = length(plots)), xAxisN)
120-
} else {
121-
seq_len(sum(xAxisN))
118+
xAxisID <- seq_len(sum(xAxisN))
119+
if (shareX) {
120+
if (length(unique(xAxisN)) > 1) {
121+
warning("Must have a consistent number of axes per 'subplot' to share them.")
122+
} else {
123+
xAxisID <- rep(rep(seq_len(ncols * unique(xAxisN)), length.out = length(plots)), unique(xAxisN))
124+
}
122125
}
123-
yAxisID <- if (shareY) {
124-
rep(rep(1:nrows, each = ncols, length.out = length(plots)), yAxisN)
125-
} else {
126-
seq_len(sum(yAxisN))
126+
yAxisID <- seq_len(sum(yAxisN))
127+
if (shareY) {
128+
if (length(unique(yAxisN)) > 1) {
129+
warning("Must have a consistent number of axes per 'subplot' to share them.")
130+
} else {
131+
yAxisID <- rep(rep(seq_len(nrows * unique(xAxisN)), each = ncols, length.out = length(plots)), unique(yAxisN))
132+
}
127133
}
128134
# current "axis" names
129135
xCurrentNames <- unlist(lapply(xAxes, names))
@@ -145,82 +151,71 @@ subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, margin = 0.02
145151
domainInfo <- get_domains(
146152
length(plots), nrows, margin, widths = widths, heights = heights
147153
)
148-
# reposition shapes and annotations
149-
annotations <- Map(reposition, annotations, split(domainInfo, seq_along(plots)))
150-
shapes <- Map(reposition, shapes, split(domainInfo, seq_along(plots)))
151-
# rename axis objects, anchors, and scale their domains
152154
for (i in seq_along(plots)) {
155+
# map axis object names
153156
xMap <- xAxisMap[[i]]
154157
yMap <- yAxisMap[[i]]
158+
xAxes[[i]] <- setNames(xAxes[[i]], names(xMap))
159+
yAxes[[i]] <- setNames(yAxes[[i]], names(yMap))
160+
# for cartesian, bump corresponding axis anchor
161+
for (j in seq_along(xAxes[[i]])) {
162+
if (grepl("^geo", names(xAxes[[i]][j]))) next
163+
map <- yMap[yMap %in% sub("y", "yaxis", xAxes[[i]][[j]]$anchor %||% "y")]
164+
xAxes[[i]][[j]]$anchor <- sub("axis", "", names(map))
165+
}
166+
for (j in seq_along(yAxes[[i]])) {
167+
if (grepl("^geo", names(yAxes[[i]][j]))) next
168+
map <- xMap[xMap %in% sub("x", "xaxis", yAxes[[i]][[j]]$anchor %||% "x")]
169+
yAxes[[i]][[j]]$anchor <- sub("axis", "", names(map))
170+
}
171+
# map trace xaxis/yaxis/geo attributes
172+
for (key in c("geo", "xaxis", "yaxis")) {
173+
oldAnchors <- unlist(lapply(traces[[i]], "[[", key))
174+
if (!length(oldAnchors)) next
175+
axisMap <- if (key == "yaxis") yMap else xMap
176+
axisMap <- setNames(sub("axis", "", axisMap), sub("axis", "", names(axisMap)))
177+
newAnchors <- names(axisMap)[match(oldAnchors, axisMap)]
178+
traces[[i]] <- Map(function(tr, a) { tr[[key]] <- a; tr }, traces[[i]], newAnchors)
179+
}
180+
# rescale domains according to the tabular layout
155181
xDom <- as.numeric(domainInfo[i, c("xstart", "xend")])
156182
yDom <- as.numeric(domainInfo[i, c("yend", "ystart")])
157-
for (j in seq_along(xAxes[[i]])) {
158-
# TODO: support ternary as well!
159-
isGeo <- grepl("^geo", xMap[[j]])
160-
anchorKey <- if (isGeo) "geo" else "xaxis"
161-
traces[[i]] <- lapply(traces[[i]], function(tr) {
162-
tr[[anchorKey]] <- tr[[anchorKey]] %||% sub("axis", "", anchorKey)
163-
# bump trace anchors, where appropriate
164-
if (sub("axis", "", xMap[[j]]) %in% tr[[anchorKey]]) {
165-
tr[[anchorKey]] <- sub("axis", "", names(xMap[j]))
166-
}
167-
tr
168-
})
169-
if (isGeo) {
170-
xAxes[[i]][[j]]$domain$x <- sort(scales::rescale(
171-
xAxes[[i]][[j]]$domain$x %||% c(0, 1), xDom, from = c(0, 1)
172-
))
173-
xAxes[[i]][[j]]$domain$y <- sort(scales::rescale(
174-
xAxes[[i]][[j]]$domain$y %||% c(0, 1), yDom, from = c(0, 1)
175-
))
183+
reScale <- function(old, new) {
184+
sort(scales::rescale(
185+
old %||% c(0, 1), new, from = c(0, 1)
186+
))
187+
}
188+
xAxes[[i]] <- lapply(xAxes[[i]], function(ax) {
189+
if (all(c("x", "y") %in% names(ax$domain))) {
190+
# geo domains are different from cartesian
191+
ax$domain$x <- reScale(ax$domain$x, xDom)
192+
ax$domain$y <- reScale(ax$domain$y, yDom)
176193
} else {
177-
xAxes[[i]][[j]]$domain <- sort(scales::rescale(
178-
xAxes[[i]][[j]]$domain %||% c(0, 1), xDom, from = c(0, 1)
179-
))
180-
# for cartesian, bump corresponding axis
181-
map <- yMap[yMap %in% sub("y", "yaxis", xAxes[[i]][[j]]$anchor %||% "y")]
182-
xAxes[[i]][[j]]$anchor <- sub("axis", "", names(map))
194+
ax$domain <- reScale(ax$domain, xDom)
183195
}
184-
}
185-
for (j in seq_along(yAxes[[i]])) {
186-
# TODO: support ternary as well!
187-
isGeo <- grepl("^geo", yMap[[j]])
188-
anchorKey <- if (isGeo) "geo" else "yaxis"
189-
traces[[i]] <- lapply(traces[[i]], function(tr) {
190-
tr[[anchorKey]] <- tr[[anchorKey]] %||% sub("axis", "", anchorKey)
191-
# bump trace anchors, where appropriate
192-
if (sub("axis", "", yMap[[j]]) %in% tr[[anchorKey]]) {
193-
tr[[anchorKey]] <- sub("axis", "", names(yMap[j]))
194-
}
195-
tr
196-
})
197-
if (isGeo) {
198-
yAxes[[i]][[j]]$domain$x <- sort(scales::rescale(
199-
yAxes[[i]][[j]]$domain$x %||% c(0, 1), xDom, from = c(0, 1)
200-
))
201-
yAxes[[i]][[j]]$domain$y <- sort(scales::rescale(
202-
yAxes[[i]][[j]]$domain$y %||% c(0, 1), yDom, from = c(0, 1)
203-
))
196+
ax
197+
})
198+
yAxes[[i]] <- lapply(yAxes[[i]], function(ax) {
199+
if (all(c("x", "y") %in% names(ax$domain))) {
200+
# geo domains are different from cartesian
201+
ax$domain$x <- reScale(ax$domain$x, xDom)
202+
ax$domain$y <- reScale(ax$domain$y, yDom)
204203
} else {
205-
yAxes[[i]][[j]]$domain <- sort(scales::rescale(
206-
yAxes[[i]][[j]]$domain %||% c(0, 1), yDom, from = c(0, 1)
207-
))
208-
# for cartesian, bump corresponding axis
209-
map <- xMap[xMap %in% sub("x", "xaxis", yAxes[[i]][[j]]$anchor %||% "x")]
210-
yAxes[[i]][[j]]$anchor <- sub("axis", "", names(map))
204+
ax$domain <- reScale(ax$domain, yDom)
211205
}
212-
}
213-
xAxes[[i]] <- setNames(xAxes[[i]], names(xMap))
214-
yAxes[[i]] <- setNames(yAxes[[i]], names(yMap))
206+
ax
207+
})
215208
}
216209
# start merging the plots into a single subplot
217210
p <- list(
218211
data = Reduce(c, traces),
219212
layout = Reduce(modifyList, c(xAxes, rev(yAxes)))
220213
)
214+
# reposition shapes and annotations
215+
annotations <- Map(reposition, annotations, split(domainInfo, seq_along(plots)))
216+
shapes <- Map(reposition, shapes, split(domainInfo, seq_along(plots)))
221217
p$layout$annotations <- Reduce(c, annotations)
222218
p$layout$shapes <- Reduce(c, shapes)
223-
224219
# merge non-axis layout stuff
225220
layouts <- lapply(layouts, function(x) x[!grepl("^[x-y]axis|^geo", names(x))] %||% list())
226221
if (which_layout != "merge") {

0 commit comments

Comments
 (0)