modularize Shiny app: CSV and Chart modules

I want to create a modularized Shiny app where one module, dataUpload , is used to import a CSV and another module, chart , is used to

  1. Create dynamic x and y dropdowns based on the column names within the CSV THIS WORKS
  2. Create a plot based on the selected input$xaxis, input$yaxis This produces the error invalid type/length (symbol/0) in vector allocation

I think the issue is with my reactive ggplot in chart.R and I'd love any help - I added all the info here but I also have a github repo if that's easier I think this could be a really great demo into the world of interacting modules so I'd really appreciate any help!!

App.R

library(shiny)
library(shinyjs)
library(tidyverse)

source("global.R")

ui <- 
  tagList(
    navbarPage(
      "TWO MODULES",
      tabPanel(
        title = "Data",
          dataUploadUI("datafile", "Import CSV")
      ),
      tabPanel(
        title = "Charts",
          chartUI("my_chart")
      )
    )
  )

server <- function(input, output, session) {

  datafile <- callModule(dataUpload, "datafile", stringsAsFactors = FALSE)
  output$table <- renderTable({ datafile() })

  # PASS datafile WITHOUT () INTO THE MODULE 
  my_chart <- callModule(chart, "my_chart", datafile = datafile)
  output$plot <- renderPlot({ my_chart() })

}

shinyApp(ui, server)

dataUpload.R

dataUpload <- function(input, output, session, stringsAsFactors) {
  # The selected file, if any
  userFile <- reactive({
    # If no file is selected, don't do anything
    # input$file == ns("file")
    validate(need(input$file, message = FALSE))
    input$file
  })

  # The user's data, parsed into a data frame
  dataframe <- reactive({
    read.csv(userFile()$datapath,
             stringsAsFactors = stringsAsFactors)
  })

  # We can run observers in here if we want to
  observe({
    msg <- sprintf("File %s was uploaded", userFile()$name)
    cat(msg, "\n")
  })

  # Return the reactive that yields the data frame
  return(dataframe)

}

dataUploadUI.R

# The first argument is the id -- the namespace for the module
dataUploadUI <- function(id, label = "CSV file") {
  # Create a namespace function using the provided id
  #ALL UI FUNCTION BODIES MUST BEGIN WITH THIS
  ns <- NS(id)
  # Rather than fluidPage use a taglist
  # If you're just returning a div you can skip the taglist
  tagList(
  sidebarPanel(
    fileInput(ns("file"), label)),

  mainPanel(tableOutput("table"))
  )
}

chart.R

I believe this is the file that needs some minor changing in order to have the plot properly render?

chart <- function(input, output, session, datafile = reactive(NULL)) {

  # SINCE DATAFILE IS A REACTIVE WE ADD THE PRERENTHESIS HERE
  # WHERE/HOW CAN I ACCESS input$xaxis?
  # Do I need to use ns? Can you do that in the server side of a module?
  output$XAXIS <- renderUI(selectInput("xaxis", "X Axis", choices = colnames(datafile())))
  output$YAXIS <- renderUI(selectInput("yaxis", "Y Axis", choices = colnames(datafile())))

  # NOT WORKING
  # Use the selectInput x and y to plot
  p <- reactive({
    req(datafile)
    # WORKS: ggplot(datafile(), aes(x = Sepal_Length, y = Sepal_Width))
    # DOES NOT WORK:
    ggplot(datafile(), aes_(x = as.name(input$xaxis), y = as.name(input$yaxis))) +
      geom_point()
  })

  return(p)
}

chartUI.R

chartUI <- function(id, label = "Create Chart") {

  ns <- NS(id)
  tagList(
    sidebarPanel(
      uiOutput(ns("XAXIS")),
      uiOutput(ns("YAXIS"))
    ),
    mainPanel(plotOutput("plot"))
  )
}

Hi @MayaGans. The problem come from the module function chart. In module UI, we usually use NS function to add the namespace in front of the output id. But in module server, the main server use callModule to the call the module server and the function will automatically add the namespace to all input and output id. But for renderUI in chart, the selectInput behave like the module UI, you need to add the namespace by yourself, so use session$ns to add the namespace to the newly added input id in order to match with the input xaxis and yaxis.

chart <- function(input, output, session, datafile = reactive(NULL)) {
  
  # SINCE DATAFILE IS A REACTIVE WE ADD THE PRERENTHESIS HERE
  # WHERE/HOW CAN I ACCESS input$xaxis?
  # Do I need to use ns? Can you do that in the server side of a module?
  output$XAXIS <- renderUI(selectInput(session$ns("xaxis"), "X Axis", choices = colnames(datafile())))
  output$YAXIS <- renderUI(selectInput(session$ns("yaxis"), "Y Axis", choices = colnames(datafile())))
  
  # NOT WORKING
  # Use the selectInput x and y to plot
  p <- reactive({
    req(datafile)
    # WORKS: ggplot(datafile(), aes(x = Sepal_Length, y = Sepal_Width))
    # DOES NOT WORK:
    ggplot(datafile(), aes_(x = as.name(input$xaxis), y = as.name(input$yaxis))) +
      geom_point()
  })
  
  return(p)
}
1 Like

This is exactly what I was looking for! I was a little confused about how to add NS to the module function since it's not on the server side, thank you so much!!!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.