@@ -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