Search code examples
rshinyplotlyggplotly

R Shiny: the legend splits when renderPlotly is used


I noticed a strange phenomenon that renderPlotly can split the legend of the same variable.

For example, when renderPlot() is used, the produced graph looks like this:enter image description here However, if I tried to convert it into a plotly object, it changed into this: enter image description here

Does anyone know what is going on and how to fix this problem? Thank you! ggplot version:

library(shiny)
library(plotly)

ColorblindnessFriendlyValues <- c("Same" = "#648FFF", "Alt" = "#FFB000")

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput("crossloadings", "Number Crossloadings",
                  min = 1, max = 10, value = 5),
      sliderInput("group1", "Group 1",
                  min = 0, max = 1, value = 0.5),
      sliderInput("group2", "Group 2",
                  min = 0, max = 1, value = 0.5)
    ),
    mainPanel(
      plotlyOutput("plot") # Changed here
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlotly({
    number_crossloadings <- seq(1, input$crossloadings)
    group1 <- runif(input$crossloadings, min = 0, max = input$group1)
    group2 <- runif(input$crossloadings, min = 0, max = input$group2)

    results <- data.frame(number_crossloadings, group1, group2)

    plot <- ggplot(data=results, aes(x=number_crossloadings))+
      geom_line(aes(y=group1,
                    color="Same"))+
      geom_line(aes(y=group2,color="Alt"))+
      suppressWarnings(geom_point(aes(y=group1,
                                      color="Same",
                                      shape = "Same",
                                      text = paste0("# of cross Loadings: ", number_crossloadings,
                                                    "<br>SRMR: ", sprintf('%.3f', group1)))))+
      suppressWarnings(geom_point(aes(y=group2,
                                      color="Alt",
                                      shape = "Alt",
                                      text = paste0("# of cross Loadings: ", number_crossloadings,
                                                    "<br>SRMR: ", sprintf('%.3f', group2)))))+
      scale_color_manual(values = ColorblindnessFriendlyValues, labels = c("Same", "Alt")) +
      scale_shape_manual(values = c("Same" = 16, "Alt" = 17), labels = c("Same", "Alt")) +
      geom_abline(color="grey",slope=0, intercept=0.08) +
          labs(color = "Legend", shape = "Legend")  +
      ylim(NA,1)

      ggplotly(plot,tooltip = c("text"))

    })
}

shinyApp(ui, server)

Plotly version:

library(shiny)
library(plotly)

ColorblindnessFriendlyValues <- c("Same" = "#648FFF", "Alt" = "#FFB000")

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput("crossloadings", "Number Crossloadings",
                  min = 1, max = 10, value = 5),
      sliderInput("group1", "Group 1",
                  min = 0, max = 1, value = 0.5),
      sliderInput("group2", "Group 2",
                  min = 0, max = 1, value = 0.5)
    ),
    mainPanel(
      plotOutput("plot")
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    number_crossloadings <- seq(1, input$crossloadings)
    group1 <- runif(input$crossloadings, min = 0, max = input$group1)
    group2 <- runif(input$crossloadings, min = 0, max = input$group2)

    results <- data.frame(number_crossloadings, group1, group2)

    plot <- ggplot(data=results, aes(x=number_crossloadings))+
      geom_line(aes(y=group1, color="Same"))+
      geom_line(aes(y=group2,color="Alt"))+
      suppressWarnings(geom_point(aes(y=group1,
                                      color="Same",
                                      shape = "Same",
                                      text = paste0("# of cross Loadings: ", number_crossloadings,
                                                    "<br>SRMR: ", sprintf('%.3f', group1)))))+
      suppressWarnings(geom_point(aes(y=group2,
                                      color="Alt",
                                      shape = "Alt",
                                      text = paste0("# of cross Loadings: ", number_crossloadings,
                                                    "<br>SRMR: ", sprintf('%.3f', group2)))))+
      scale_color_manual(values = ColorblindnessFriendlyValues, labels = c("Same", "Alt")) +
      scale_shape_manual(values = c("Same" = 16, "Alt" = 17), labels = c("Same", "Alt")) +
      geom_abline(color="grey",slope=0, intercept=0.08) +
      labs(color = "Legend", shape = "Legend")  +
      ylim(NA,1)

    plot
  })
}

shinyApp(ui, server)

Solution

  • Whenever the translation ggplot <-> plotly does not yield the results I want, I use plot_ly directly as it allows for finer control.

    Having said that, you can generate a similar plot with plot_ly like this (N.B. I changed your data structure a bit to avoid some duplication):

    output$plot <- renderPlotly({
       number_crossloadings <- seq(1, input$crossloadings)
       group1 <- runif(input$crossloadings, min = 0, max = input$group1)
       group2 <- runif(input$crossloadings, min = 0, max = input$group2)
       results <- data.frame(x = rep(number_crossloadings, 2),
                             y = c(group1, group2),
                             g = rep(c("Same", "Alt"), each = input$crossloadings))
       plot_ly(results, 
               x = ~ x, 
               y = ~ y, 
               colors = ColorblindnessFriendlyValues, 
               symbols = c("triangle-up",  "circle"))  %>% 
          add_trace(type = "scatter", 
                    mode = "lines", 
                    showlegend = FALSE, 
                    hoverinfo = "none",
                    color = I("gray"), 
                    x = range(results$x) + c(-1, 1) * .2, 
                    y = c(.08, .08)) %>% 
          add_trace(type = "scatter", 
                    mode = "markers+lines", 
                    color = ~ g, 
                    symbol = ~ g, 
                    marker = list(size = 8),
                    hoverinfo = "text",
                    text = ~ paste0("# of cross Loadings: ", number_crossloadings,
                                    "<br>SRMR: ", sprintf("%.3f", y))) %>% 
          layout(legend = list(title = list(text = "Legend")),
                 xaxis = list(title = list(text = "number_crossloadings")),
                 yaxis = list(title = list(text = "y"))
    })
    

    Scatter Plot with a single legend made with plot_ly

    Abline

    I used a separate trace for the abline, another option would be to add a shape to layout like this:

    layout(
       #...
       shapes = list(
          list(
             type = "rect",
             x0 = 0,
             x1 = 1,
             xref = "paper",
             y0 = 0.08,
             y1 = 0.08,
             yref = "y",
             line = list(dash = "solid",
                         color = "grey"),
             layer = "below"
          )
       )
    )