Search code examples
rplotlyr-plotly

How to apply subplot to a list of plots with secondary y axis


I want to prepare a subplot where each facet is a separate dual y-axis plot of one variable against the others. So I make a base plot p and add secondary y-axis variable in a loop:

library(rlang)
library(plotly)
library(tibble)

dual_axis_lines <- function(data, x, y_left, ..., facets = FALSE, axes = NULL){
  x <- rlang::enquo(x)
  y_left <- rlang::enquo(y_left)
  y_right <- rlang::enquos(...)
  
  y_left_axparms <- list(
    title = FALSE,
    tickfont = list(color = "#1f77b4"),
    side = "left")
  y_right_axparms <- list(
    title = FALSE,
    overlaying = "y",
    side = "right",
    zeroline = FALSE)
  
  p <- plotly::plot_ly(data , x = x) %>%
    plotly::add_trace(y = y_left, name = quo_name(y_left),
                      yaxis = "y1", type = 'scatter', mode = 'lines', 
                      line = list(color = "#1f77b4"))
  
  p_facets <- list()
  for(v in y_right){
    p_facets[[quo_name(v)]] <- p %>%
      plotly::add_trace(y = v, name = quo_name(v),
                        yaxis = "y2", type = 'scatter', mode = 'lines') %>%
      plotly::layout(yaxis = y_left_axparms,
                     yaxis2 = y_right_axparms)
  }
  p <- subplot(p_facets, nrows = length(y_right), shareX = TRUE)
  return(p)
}

mtcars %>% 
  rowid_to_column() %>% 
  dual_axis_lines(rowid, mpg, cyl, disp, hp, facets = TRUE)

However, the resulting plots have all the secondary y-axis variables cluttered in the first facet. enter image description here

The issue seems to be absent when I return p_facets lists that goes into subplot as each plot looks like below: enter image description here

How can I fix this issue?


Solution

  • Okay, I followed the ideas given in this github issue about your bug.

    library(rlang)
    library(plotly)
    library(tibble)
    
    dual_axis_lines <- function(data, x, y_left, ..., facets = FALSE, axes = NULL){
     x <- rlang::enquo(x)
     y_left <- rlang::enquo(y_left)
     y_right <- rlang::enquos(...)
     
    ## I removed some things here for simplicity, and because we want overlaying to vary between subplots.
     y_left_axparms <- list(
      tickfont = list(color = "#1f77b4"),
      side = "left")
     y_right_axparms <- list(
      side = "right")
     
     p <- plotly::plot_ly(data , x = x) %>%
      plotly::add_trace(y = y_left, name = quo_name(y_left),
                        yaxis = "y", type = 'scatter', mode = 'lines', 
                        line = list(color = "#1f77b4"))
     
     p_facets <- list()
    ## I needed to change the for loop so that i can have which plot index we are working with
     for(v in 1:length(y_right)){
      p_facets[[quo_name(y_right[[v]])]] <- p %>%
       plotly::add_trace(y = y_right[[v]], x = x, name = quo_name(y_right[[v]]),
                         yaxis = "y2", type = 'scatter', mode = 'lines') %>%
       plotly::layout(yaxis = y_left_axparms,
                     ## here is where you can assign each extra line to a particular subplot. 
                     ## you want overlaying to be: "y", "y3", "y5"... for each subplot
                      yaxis2 = append(y_right_axparms, c(overlaying = paste0(
                       "y", c("", as.character(seq(3,100,by = 2)))[v]))))
     }
     p <- subplot(p_facets, nrows = length(y_right), shareX = TRUE)
     return(p)
    }
    
    mtcars %>% 
     rowid_to_column() %>% 
     dual_axis_lines(rowid, mpg, cyl, disp, hp, facets = TRUE)
    

    enter image description here


    Axis text the same color as the lines.

    For this you would need two things. You would need to give a palette to your function outside of your for-loop:
    color_palette <- colorRampPalette(RColorBrewer::brewer.pal(10,"Spectral"))(length(y_right)) If you don't like the color palette, you'd change it!

    I've cleaned up the for-loop so it's easier to look at. This is what it would now look like now so that lines and axis text share the same color:

     for(v in 1:length(y_right)){
      ## here is where you can assign each extra line to a particular subplot. 
      ## you want overlaying to be: "y", "y3", "y5"... for each subplot
      overlaying_location = paste0("y", c("", as.character(seq(3,100,by = 2)))[v])
      
      trace_name = quo_name(y_right[[v]])
      
      trace_value = y_right[[v]]
      
      trace_color = color_palette[v]
      
      p_facets[[trace_name]] <- p %>%
       plotly::add_trace(y = trace_value, 
                         x = x, 
                         name = trace_name,
                         yaxis = "y2", 
                         type = 'scatter', 
                         mode = 'lines', 
                         line = list(color = trace_color)) %>%
       plotly::layout(yaxis = y_left_axparms,
                      ## We can build the yaxis2 right here.
                      yaxis2 = eval(
                       parse(
                        text = "list(side = 'right', 
                                overlaying = overlaying_location, 
                                tickfont = list(color = trace_color))")
                      )
       )
     }