Skip to content

Commit a8c1628

Browse files
committed
add shareX/shareY arguments
1 parent a45749c commit a8c1628

File tree

4 files changed

+86
-14
lines changed

4 files changed

+86
-14
lines changed

R/ggplotly.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ ggplotly.ggmatrix <- function(p = ggplot2::last_plot(), width = NULL,
4444
plotList <- c(plotList, list(p[i, j]))
4545
}
4646
}
47-
# TODO: how to show x/y titles? Should these be arguments in subplot?
47+
# TODO:
48+
# (1) how to show x/y titles? Should these be arguments in subplot?
49+
# (2) it only makes since to share axes on the lower diagonal
4850
l <- get_plot(do.call(subplot, c(plotList, list(nrows = p$nrow))))
4951
l$layout$title <- p$title
5052
hash_plot(p$data, l)

R/subplots.R

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77
#' columns have an equal relative width.
88
#' @param heights relative height of each row on a 0-1 scale. By default all
99
#' rows have an equal relative height.
10-
#' @param share determines whether x/y/both axes are shared.
11-
#' @param which_layout adopt the layout of which plot? If the default value of
12-
#' "merge" is used, all plot level layout options will be included in the final
13-
#' layout. This argument also accepts a numeric vector which will restric
10+
#' @param shareX should the x-axis be shared amongst the subplots?
11+
#' @param shareY should the y-axis be shared amongst the subplots?
1412
#' @param margin either a single value or four values (all between 0 and 1).
1513
#' If four values are provided, the first is used as the left margin, the second
1614
#' is used as the right margin, the third is used as the top margin, and the
1715
#' fourth is used as the bottom margin.
1816
#' If a single value is provided, it will be used as all four margins.
17+
#' @param which_layout adopt the layout of which plot? If the default value of
18+
#' "merge" is used, all plot level layout options will be included in the final
19+
#' layout. This argument also accepts a numeric vector specifying
1920
#' @return A plotly object
2021
#' @export
2122
#' @author Carson Sievert
@@ -25,8 +26,8 @@
2526
#' subplot(p1, p2, p1, p2, nrows = 2)
2627
#' }
2728

28-
subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, share = NULL,
29-
which_layout = "merge", margin = 0.02) {
29+
subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, shareX = FALSE,
30+
shareY = FALSE, margin = 0.02, which_layout = "merge") {
3031
# build each plot and collect relevant info
3132
plots <- lapply(list(...), plotly_build)
3233
traces <- lapply(plots, "[[", "data")
@@ -51,13 +52,24 @@ subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, share = NULL,
5152
xAxisN <- vapply(xAxes, length, numeric(1))
5253
yAxisN <- vapply(yAxes, length, numeric(1))
5354
# old -> new axis name dictionary
55+
ncols <- ceiling(length(plots) / nrows)
56+
xAxisID <- if (shareX) {
57+
rep(rep(1:ncols, length.out = length(plots)), xAxisN)
58+
} else {
59+
seq_len(sum(xAxisN))
60+
}
61+
yAxisID <- if (shareY) {
62+
rep(rep(1:nrows, each = ncols, length.out = length(plots)), yAxisN)
63+
} else {
64+
seq_len(sum(yAxisN))
65+
}
5466
xAxisMap <- setNames(
5567
unlist(lapply(xAxes, names)),
56-
paste0("xaxis", sub("^1$", "", seq_len(sum(xAxisN))))
68+
paste0("xaxis", sub("^1$", "", xAxisID))
5769
)
5870
yAxisMap <- setNames(
5971
unlist(lapply(yAxes, names)),
60-
paste0("yaxis", sub("^1$", "", seq_len(sum(yAxisN))))
72+
paste0("yaxis", sub("^1$", "", yAxisID))
6173
)
6274
# split the map by plot ID
6375
xAxisMap <- split(xAxisMap, rep(seq_along(plots), xAxisN))
@@ -109,7 +121,7 @@ subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, share = NULL,
109121
# start merging the plots into a single subplot
110122
p <- list(
111123
data = Reduce(c, traces),
112-
layout = Reduce(c, c(xAxes, yAxes))
124+
layout = Reduce(modifyList, c(xAxes, rev(yAxes)))
113125
)
114126
p$layout$annotations <- Reduce(c, annotations)
115127
p$layout$shapes <- Reduce(c, shapes)

man/subplot.Rd

Lines changed: 15 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-plotly-subplot.R

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,50 @@ test_that("group + [x/y]axis works", {
4444
expect_true(all(2/3 > xdom[[2]] & xdom[[2]] > 1/3))
4545
expect_true(all(1 >= xdom[[3]] & xdom[[3]] > 2/3))
4646
})
47+
48+
test_that("shareX produces one x-axis", {
49+
s <- subplot(plot_ly(x = 1), plot_ly(x = 1), nrows = 2, shareX = TRUE)
50+
l <- expect_traces(s, 2, "shareX")
51+
expect_true(sum(grepl("^xaxis", names(l$layout))) == 1)
52+
})
53+
54+
test_that("shareY produces one y-axis", {
55+
s <- subplot(plot_ly(x = 1), plot_ly(x = 1), shareY = TRUE)
56+
l <- expect_traces(s, 2, "shareY")
57+
expect_true(sum(grepl("^yaxis", names(l$layout))) == 1)
58+
})
59+
60+
test_that("share both axes", {
61+
s <- subplot(
62+
plot_ly(x = 1), plot_ly(x = 1), plot_ly(x = 1), plot_ly(x = 1),
63+
nrows = 2, shareX = TRUE, shareY = TRUE
64+
)
65+
l <- expect_traces(s, 4, "shareBoth")
66+
expect_true(sum(grepl("^yaxis", names(l$layout))) == 2)
67+
expect_true(sum(grepl("^xaxis", names(l$layout))) == 2)
68+
})
69+
70+
# https://github.com/ropensci/plotly/issues/376
71+
library(plotly)
72+
d <- data.frame(
73+
x = rnorm(100),
74+
y = rnorm(100)
75+
)
76+
hist_top <- ggplot(d) + geom_histogram(aes(x = x))
77+
empty <- ggplot() + geom_blank()
78+
scatter <- ggplot(d) + geom_point(aes(x = x, y = y))
79+
hist_right <- ggplot(d) + geom_histogram(aes(x = y)) + coord_flip()
80+
s <- subplot(
81+
hist_top, empty, scatter, hist_right,
82+
nrows = 2, widths = c(0.8, 0.2), heights = c(0.2, 0.8),
83+
margin = 0.005, shareX = TRUE, shareY = TRUE
84+
)
85+
86+
test_that("Row/column height/width", {
87+
l <- expect_traces(s, 3, "width-height")
88+
expect_equal(diff(l$layout$xaxis$domain), 0.8 - 0.005)
89+
expect_equal(diff(l$layout$xaxis2$domain), 0.2 - 0.005)
90+
expect_equal(diff(l$layout$yaxis$domain), 0.2 - 0.005)
91+
expect_equal(diff(l$layout$yaxis2$domain), 0.8 - 0.005)
92+
})
93+

0 commit comments

Comments
 (0)