0

EDIT: I found a solution, but I don't understand why it works. Instead of calling renderPlot(plot) in that tibble with mutate and map, I passed the plot and called that on the right hand side of assignment in the for loop. I tried it on the 'real' app I had the problem with and everything just worked. It seems like something about renderPlot or map changed over time, but I don't know what.

I'm trying to make a Shiny module that renders some plots from inside a module. In the past I've done this with the sequence of actions: plot() > renderPlot() > assign to output > plotOutput > tagList > renderUI > uiOutput. I had this working in the past, but that code no longer works (I'm moving from a deprecated Shiny Server Pro running very old packages to Posit Connect with the latest of everything).

Instead of plots, I get errors that say "attempt to select less than one element in integerOneIndex".

The good news is I get one error for each plot, so it's making the correct number of plotOutputs inside the tagList, they're just referencing the wrong thing. I've tried inspecting the elements to see if the namespaces look correct, and they're at least close enough that I can't see a problem.

My best guess is that the problem is in assigning those individual plots to the output list and retrieving them from that list and is probably related to it being inside a module, but I'm namespacing inside renderUI in the module like you're supposed to. I haven't been able to figure out how to examine the output object when I jump inside the app with browser().

Here's a reproducible example. There are 3 modules, the outer module makes a navbarMenu populated by the two inner modules which make the input widgets that go in the sidebar and the plots that go in the main panel.

library(tidyverse)
library(shiny)

# MODULES ----
# UI for number of plots ----
dynPlotsInputUI <- function(id) {
  ns <- NS(id)
  
  sliderInput(
    inputId = ns("n_plots"), 
    label = "Number of plots", 
    value = 1, min = 1, max = 5
  )
}

dynPlotsInput <- function(id) {
  moduleServer(
    id,
    function(input, output, session) {
      return(
        list(
          n_plots = reactive({ input$n_plots })
        )
      )
    }
  )
}

# plots themselves ----
dynPlotsUI <- function(id) {
  ns <- NS(id)
  uiOutput(ns("all_plots"))
  # plotOutput(ns("test_plot"))
}

dynPlots <- function(id, plot_inputs) {
  moduleServer(
    id, 
    function(input, output, session) {
      ns <- session$ns
      # plot > renderPlot > assign > plotOutput (> tagList > renderUI > uiOutput)
      
      # make outputs, put in tagList, and renderUI
      output$all_plots <- renderUI({
        plot_stuff <- 
          tibble(
            x = rep(seq(from = 0, to = 10, length = 100), times = plot_inputs$n_plots()),
            name = rep(1:plot_inputs$n_plots(), each = 100),
            y = x^(1/name)
          ) %>%
          group_by(name) %>%
          nest() %>%
          ungroup() %>%
          mutate(obj_name = paste0("plot_", name),
                 plot = map(.x = data,
                            .f = \(x){
                              x %>% 
                                ggplot(aes(x = x, y = y)) +
                                geom_line(linewidth = 1.5) +
                                scale_color_discrete(guide = "none")
                            }),
                 render = map(.x = plot,
                              .f = renderPlot))
        
        # assign to output list
        for (i in 1:nrow(plot_stuff)) {
          output[[ plot_stuff$obj_name[[i]] ]] <- plot_stuff$render[[i]]
        }
        
        # retrieve from output list and plotOutput > tagList
        lapply(X = plot_stuff %>% pull(obj_name) %>% ns(), 
               FUN = plotOutput) %>%
          do.call(what = tagList,
                  args = .)
      })
    }
  )
}

# menu with input sidebar and plot main ----
dynPlotsMenuUI <- function(id) {
  ns <- NS(id)
  
  navbarMenu(
    title = id,
    tabPanel(
      title = "dynamic # plots", 
      value = id,
      sidebarPanel(
        dynPlotsInputUI(ns("plot_inputs"))
      ),
      mainPanel(
        dynPlotsUI(ns("plots"))
      )
    )
  )
}

dynPlotsMenu <- function(id) {
  moduleServer(
    id,
    function(input, output, session) {
      plot_inputs <- dynPlotsInput("plot_inputs")
      
      dynPlots("plots", plot_inputs)
    }
  )
}

# ACTUAL APP ----
ui <- 
  navbarPage(
    title = "test dynamic plots",
    id = "tab_selected",
    
    dynPlotsMenuUI("test1"),
    dynPlotsMenuUI("test2")
  )

# plot > renderPlot > assign to output obj > plotOutput > tagList > renderUI > uiOutput

server <- function(input, output) {
  # my modules ----
  dynPlotsMenu("test1")
  dynPlotsMenu("test2")
}

shinyApp(ui, server)
Quasar
  • 101
  • 1
  • 1
    Change `linewidth` to `size` in geom_line, and it works fine for me. Also, changed `for` loop to `lapply` in the renderUI. – YBS Mar 28 '23 at 19:02
  • I made those changes and have the same error. What version of shiny are you on? That might help me track down the change that's causing this. My understanding is you're supposed to use linewidth instead of size now in ggplot2, and that might indicate that we're using different versions of several packages. Which is one of the causes I identified (but can't easily fix). My shiny is v1.7.1 – Quasar Mar 28 '23 at 19:32
  • I am also using v1.7.1 Shiny. Perhaps you should restart your RStudio and/or laptop. – YBS Mar 28 '23 at 19:44
  • That didn't work. I can also reproduce the problem on a Posit Connect deployment server. – Quasar Mar 29 '23 at 04:09

0 Answers0