3.2 Merging plotly objects

The subplot() function provides a flexible interface for merging multiple plotly objects into a single object (i.e., view). It is more flexible than most trellis display frameworks (e.g., ggplot2’s facet_wrap()) as you don’t have to condition on a value of common variable in each display (Richard A. Becker 1996). Its capabilities and interface is similar to the grid.arrange() function from the gridExtra package, which allows you to arrange multiple grid grobs in a single view, effectively providing a way to arrange (possibly unrelated) ggplot2 and/or lattice plots in a single view (R Core Team 2016); (Auguie 2016); (Sarkar 2008). Figure 3.4 shows the most simple way to use subplot() which is to directly supply plotly objects.

p1 <- plot_ly(economics, x = ~date, y = ~unemploy) %>% 
  add_lines(name = "unemploy")
p2 <- plot_ly(economics, x = ~date, y = ~uempmed) %>% 
  add_lines(name = "uempmed")
subplot(p1, p2)

Figure 3.4: The most basic use of subplot() to merge multiple plotly objects into a single plotly object.

Although subplot() accepts an arbitrary number of plot objects, passing a list of plots can save typing and redundant code when dealing with a large number of plots. Figure 3.5 shows one time series for each variable in the economics dataset and share the x-axis so that zoom/pan events are synchronized across each series:

vars <- setdiff(names(economics), "date")
plots <- lapply(vars, function(var) {
  plot_ly(economics, x = ~date, y = as.formula(paste0("~", var))) %>%
    add_lines(name = var)
subplot(plots, nrows = length(plots), shareX = TRUE, titleX = FALSE)

Figure 3.5: Five different economic variables on different y scales and a common x scale. Zoom and pan events in the x-direction are synchronized across plots.

A plotly subplot is a single plotly graph with multiple traces anchored on different axes. If you pre-specify an axis ID for each trace, subplot() will respect that ID. Figure 3.6 uses this fact in correspondence with the fact that mapping a discrete variable to color creates one trace per value. In addition to providing more control over trace placement, this provides a convenient way to control coloring (we could have symbol/linetype to achieve the same effect).

economics %>%
  tidyr::gather(variable, value, -date) %>%
  transform(id = as.integer(factor(variable))) %>%
  plot_ly(x = ~date, y = ~value, color = ~variable, colors = "Dark2",
          yaxis = ~paste0("y", id)) %>%
  add_lines() %>%
  subplot(nrows = 5, shareX = TRUE)

Figure 3.6: Pre-populating y axis IDs.

Conceptually, subplot() provides a way to place a collection of plots into a table with a given number of rows and columns. The number of rows (and, by consequence, the number of columns) is specified via the nrows argument. By default each row/column shares an equal proportion of the overall height/width, but as shown in Figure 3.7 the default can be changed via the heights and widths arguments.

A visual diagram of controling the `heights` of rows and `widths` of columns.

Figure 3.7: A visual diagram of controling the heights of rows and widths of columns.

This flexibility is quite useful for a number of visualizations, for example, as shown in Figure 3.8, a joint density plot is really of subplot of joint and marginal densities. The heatmaply package is great example of leveraging subplot() in a similar way to create interactive dendrograms (Galili 2016).

x <- rnorm(100)
y <- rnorm(100)
s <- subplot(
  plot_ly(x = x, color = I("black")), 
  plot_ly(x = x, y = y, color = I("black")), 
  plot_ly(y = y, color = I("black")),
  nrows = 2, heights = c(0.2, 0.8), widths = c(0.8, 0.2), 
  shareX = TRUE, shareY = TRUE, titleX = FALSE, titleY = FALSE
layout(s, showlegend = FALSE)

Figure 3.8: A joint density plot with synchronized axes.

3.2.1 Recursive subplots

The subplot() function returns a plotly object so it can be modified like any other plotly object. This effectively means that subplots work recursively (i.e., you can have subplots within subplots). This idea is useful when your desired layout doesn’t conform to the table structure described in the previous section. In fact, you can think of a subplot of subplots like a spreadsheet with merged cells. Figure 3.9 gives a basic example where each row of the outer-most subplot contains a different number of columns.

plotList <- function(nplots) {
  lapply(seq_len(nplots), function(x) plot_ly())
s1 <- subplot(plotList(6), nrows = 2, shareX = TRUE, shareY = TRUE)
s2 <- subplot(plotList(2), shareY = TRUE)
  s1, s2, plot_ly(), nrows = 3, 
  margin = 0.04, heights = c(0.6, 0.3, 0.1)

Figure 3.9: Recursive subplots.

The concept is particularly useful when you want plot(s) in a given row to have different widths from plot(s) in another row. Figure 3.10 uses this recursive behavior to place many bar charts in the first row, and a single choropleth in the second row.

# specify some map projection/options
g <- list(
  scope = 'usa',
  projection = list(type = 'albers usa'),
  lakecolor = toRGB('white')
# create a map of population density
density <- state.x77[, "Population"] / state.x77[, "Area"]
map <- plot_geo(z = ~density, text = state.name, 
                locations = state.abb, locationmode = 'USA-states') %>%
  layout(geo = g)
# create a bunch of horizontal bar charts 
vars <- colnames(state.x77)
barcharts <- lapply(vars, function(var) {
  plot_ly(x = state.x77[, var], y = state.name) %>%
    add_bars(orientation = "h", name = var) %>%
    layout(showlegend = FALSE, hovermode = "y",
           yaxis = list(showticklabels = FALSE))
  subplot(barcharts, margin = 0.01), map, 
  nrows = 2, heights = c(0.3, 0.7), margin = 0.1

Figure 3.10: Multiple bar charts of US statistics by state in a subplot with a choropleth of population density

3.2.2 ggplot2 subplots

Underneath the hood, ggplot2 facets are implemented as subplots, which enables the synchronized zoom events on shared axes. Since subplots work recursively, it is also possible to have a subplot of ggplot2 faceted plots, as Figure 3.11 shows. Moreover, subplot() can understand ggplot objects, so there is no need to translate them to plotly object via ggplotly() (unless you want to leverage some of the ggplotly() arguments, such as tooltip for customizing information displayed on hover).

e <- tidyr::gather(economics, variable, value, -date)
gg1 <- ggplot(e, aes(date, value)) + geom_line() +
  facet_wrap(~variable, scales = "free_y", ncol = 1)
gg2 <- ggplot(e, aes(factor(1), value)) + geom_violin() +
  facet_wrap(~variable, scales = "free_y", ncol = 1) + 
  theme(axis.text = element_blank(), axis.ticks = element_blank())
subplot(gg1, gg2) %>% layout(margin = list(l = 50))

Figure 3.11: Arranging multiple faceted ggplot2 plots into a plotly subplot.


Richard A. Becker, Ming-Jen Shyu, William S. Cleveland. 1996. “The Visual Design and Control of Trellis Display.” Journal of Computational and Graphical Statistics 5 (2): 123–55. http://www.jstor.org/stable/1390777.

R Core Team. 2016. R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org/.

Auguie, Baptiste. 2016. GridExtra: Miscellaneous Functions for "Grid" Graphics. https://CRAN.R-project.org/package=gridExtra.

Sarkar, Deepayan. 2008. Lattice: Multivariate Data Visualization with R. New York: Springer. http://lmdvr.r-forge.r-project.org.

Galili, Tal. 2016. Heatmaply: Interactive Heat Maps Using ’Plotly’. https://CRAN.R-project.org/package=heatmaply.