make R shiny to show multiple plots - r

I have the following code, which produces a plot based on the user inputs. if, for example, the user selects three x variables, three plots shall be produced in the output. However, at the moment, only the plot relevant to the last selection is only produced.
library(dplyr)
library(ggplot2)
library(shiny)
plt_func <- function(x,y){
plt_list <- list()
for (X_var in x){
plt_list[[X_var]] <- mtcars %>% ggplot(aes(get(X_var), get(y)))+
geom_point() +
labs(x = X_var, y = y)
}
return(plt_list)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(selectizeInput(inputId = "x",label = "X", choices = names(mtcars), multiple = T),
selectInput(inputId = "y",label = "Y", choices = names(mtcars),multiple = F),
actionButton("plot", label = "Plot")),
mainPanel(
plotOutput("finalplot")
)
)
)
server <- function(input, output, session) {
plt <- eventReactive(input$plot, {
req(input$x, input$y)
x <- input$x
y <- input$y
do.call(plt_func, list(x,y))
})
output$finalplot <- renderPlot({
plt()
})
}
shinyApp(ui, server)
Here is a screenshot of the output:
I wonder how I should tackle this issue.

To me, the easiest way to solve this problem is to create a module that will manage a single plot and then create the required number of instances of the module in the main server function. You can read more about Shiny modules here.
A Shiny module consists of two functions, a UI function and a server function. These are paired by the fact that they share a common ID. The ID is used to distinguish different instances of the same module. Namespacing (the ns function) is used to distinguish instances of the same widget in different instances of the module.
The module UI function is straightforward. It simply creates a plotOutput:
plotUI <- function(id) {
ns <- NS(id)
plotOutput(ns("plot"))
}
The module server function takes three parameters: an id and the names of the x and y variables to plot.
plotServer <- function(id, Xvar, Yvar) {
moduleServer(
id,
function(input, output, session) {
output$plot <- renderPlot({
req(Xvar)
mtcars %>%
ggplot(aes(get(Xvar), get(Yvar))) +
geom_point() +
labs(x = Xvar, y = Yvar)
})
}
)
}
The main UI function creates the sidebar menu (there's no need for a Plot actionButton as Shiny's reactivity makes sure everything gets updated at the correct time) and a main panel that consists only of a uiOutput.
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "x",label = "X", choices = names(mtcars), multiple = T),
selectInput(inputId = "y",label = "Y", choices = names(mtcars), multiple = F)
),
mainPanel(
uiOutput("plotUI")
)
)
)
The main server function is where the magic happens. Every time there's a change to input$x or input$y, new instances of the module UI and server functions are created. One for each selection in input$x. The id for each module is simply an integer. The appropriate column names are passed to each instance of the module server function. A call to renderUI creates the UI for each instance of the module.
server <- function(input, output, session) {
output$plotUI <- renderUI({
ns <- session$ns
tagList(
lapply(1:length(input$x),
function(i) {
plotUI(paste0("plot", i))
}
)
)
})
observeEvent(c(input$x, input$y), {
plotServerList <- lapply(
1:length(input$x),
function(i) {
plotServer(paste0("plot", i), input$x[i], input$y)
}
)
})
}
Putting it all together:
library(dplyr)
library(ggplot2)
library(shiny)
# Plot module UI function
plotUI <- function(id) {
ns <- NS(id)
plotOutput(ns("plot"))
}
# Plot module server function
plotServer <- function(id, Xvar, Yvar) {
moduleServer(
id,
function(input, output, session) {
output$plot <- renderPlot({
req(Xvar)
mtcars %>%
ggplot(aes(get(Xvar), get(Yvar))) +
geom_point() +
labs(x = Xvar, y = Yvar)
})
}
)
}
# Main UI
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "x",label = "X", choices = names(mtcars), multiple = T),
selectInput(inputId = "y",label = "Y", choices = names(mtcars), multiple = F)
),
mainPanel(
uiOutput("plotUI")
)
)
)
# Main server
server <- function(input, output, session) {
output$plotUI <- renderUI({
ns <- session$ns
tagList(
lapply(1:length(input$x),
function(i) {
plotUI(paste0("plot", i))
}
)
)
})
observeEvent(c(input$x, input$y), {
plotServerList <- lapply(
1:length(input$x),
function(i) {
plotServer(paste0("plot", i), input$x[i], input$y)
}
)
})
}
shinyApp(ui, server)

Related

How can I import 2 datasets, have them persist in my environment, and pass one of the column names as a parameter in my modular plot function?

The Questions
I have revised my code to one file as opposed to being organized in multiple files. I believe that by calling my dataframes I am unable to call them again in another module for some reason, I am unsure why. In addition I am trying to get an already known before importing column name hardcoded as a parameter when calling my plotFactorOfValue_server module.
I have revised the ggplot inside of this module
to work with the mtcars dataframe (using weight factor as the y variable)
1. My mod_plotFactorOfValue_server function does not recognize my
dataset and does not see my parameter (which is a column name in
the dataset)
2. Are my datasetComparables <- mod_import_server("import_1") and
datasetWholeHood <- mod_import_server("import_2") reactive objects
when called like this? Or will they only exist while being called?
3. Is there just a better way to do this? I don't want to have the user selecting the x
variable (that would mean many selectors for each plot(calling plot module 7 times for
different column names). I want to keep this modular, I have tried this without modules,
and the code is way too long and cumbersome.
The Code - Modules - UI - Server
Modules in order for importing data, exporting data table,
and plotting with ggplot (which is where I am having trouble).
mod_import_ui <- function(id){
ns <- NS(id)
tagList(
fileInput(ns("file1"), label = "Choose CSV File", accept = ".csv")
#, checkboxInput(ns("header"), label = "Header", TRUE)
)
}
mod_import_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
dtreact <- reactive({
file <- input$file1
if(is.null(file))
return(NULL)
read.csv(file$datapath,
# header = input$header
)
})
# Return the reactive that yields the data frame
return(dtreact)
})
}
```
Module for displaying imported data as a table, this used the dataframe datasetComparables or datasetWholeHood when called.
```
mod_importedDataTable_ui <- function(id){
ns <- NS(id)
tagList(
DTOutput(ns("contents"))
)
}
#' importedDataTable Server Functions----
#'
#' #noRd
mod_importedDataTable_server <- function(id, dataset){
moduleServer( id, function(input, output, session){
ns <- session$ns
output$contents <- renderDT({
req(dataset())
df1 <- dataset()
return(datatable(df1))
})
})
}
```
A shiny Module that uses ggplot to plot a parameter(factorOfValue) from an imported
dataset.The user should NOT be selecting the factor to be plotted.
```
mod_plotFactorOfValue_ui <- function(id){
ns <- NS(id)
tagList(
plotOutput(ns("plotFactorOfValue"))
)
}
NEED HELP HERE CREATE THE FACTOROFVALUE VARIABLE TO PASS THROUGH AS PARAMETER IN THIS
FUNCTION
mod_plotFactorOfValue_server <- function(id, dataset, factorOfValue){
moduleServer( id, function(input, output, session){
ns <- session$ns
output$plotFactorOfValue <- renderPlot({
req(dataset())
mtdf <- dataset()
x <- mtdf[[factorOfValue]]
df2 <- dataset() %>%
ggplot(aes(x, mpg))+
geom_point(aes(color = mpg, size = 1,))+
geom_smooth(method = lm, se = F)+
theme( axis.line = element_line(colour = "darkblue",
size = 1, linetype = "solid"))
return(plot(df2))
})
})
}
```
UI and Server Sections of App
==============
```
ui <- fluidPage(theme = shinytheme("darkly"),
navbarPage(
theme = "cerulean",
"Market Analysis Tool",
# Import Tab----
tabPanel("Import",
sidebarPanel(
tags$h3("Input Comparables Data:"),
mod_import_ui("import_1"),
tags$h3("Input Whole Hood Data:"),
mod_import_ui("import_2")
),
mainPanel(
mod_importedDataTable_ui("importedDataTable_1"),
mod_importedDataTable_ui("importedDataTable_2")
), #main panel Import
), #tab panel import
# Comparables Graphs Tab----
tabPanel("Comparables Graphs",
sidebarPanel(
tags$h3("Check out these trends!"),
),
mainPanel(
mod_plotFactorOfValue_ui("plotFactorOfValue_1")
), #main panel Comparables Graphs
)
) #navbar page
) #fluid page
server <- function(input, output, session) {
####Import the Data----
datasetComparables <- mod_import_server("import_1")
datasetWholeHood <- mod_import_server("import_2")
#### Output the Data Tables----
mod_importedDataTable_server("importedDataTable_1", dataset = dtreact)
mod_importedDataTable_server("importedDataTable_2", dataset = datasetWholeHood)
######## STARTING THE PLOTS HERE----
```
#I am unable to get the dataframe to be recognized, I am also unable to get the
xvariable(factorOfValue) hardcoded as a parameter in my call function.
# Can you please help with this? THis is still part of the server section.
```
mod_plotFactorOfValue_server("plotFactorOfValue_1", dataset = datasetComparables,
factorOfValue = "SqFtTotal")
}
shinyApp(ui = ui, server = server)
```
You don't need to plot a ggplot object. Try this
library(shinythemes)
library(DT)
mod_import_ui <- function(id){
ns <- NS(id)
tagList(
fileInput(ns("file1"), label = "Choose CSV File", accept = ".csv")
#, checkboxInput(ns("header"), label = "Header", TRUE)
)
}
mod_import_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
dtreact <- reactive({
file <- input$file1
if(is.null(file))
return(NULL)
read.csv(file$datapath
# header = input$header
)
})
# Return the reactive that yields the data frame
return(dtreact)
})
}
### Module for displaying imported data as a table, this used the dataframe datasetComparables or datasetWholeHood when called.
mod_importedDataTable_ui <- function(id){
ns <- NS(id)
tagList(
DTOutput(ns("contents"))
)
}
mod_importedDataTable_server <- function(id, dataset){
moduleServer( id, function(input, output, session){
ns <- session$ns
output$contents <- renderDT({
req(dataset())
df1 <- dataset()
return(datatable(df1))
})
})
}
# A shiny Module that uses ggplot to plot a parameter(factorOfValue) from an imported
# dataset.The user should NOT be selecting the factor to be plotted.
mod_plotFactorOfValue_ui <- function(id){
ns <- NS(id)
tagList(
plotOutput(ns("plotFactorOfValue"))
)
}
### NEED HELP HERE CREATE THE FACTOROFVALUE VARIABLE TO PASS THROUGH AS PARAMETER IN THIS FUNCTION
mod_plotFactorOfValue_server <- function(id, dataset, factorOfValue){
moduleServer( id, function(input, output, session){
ns <- session$ns
output$plotFactorOfValue <- renderPlot({
req(dataset())
mtdf <- dataset()
x <- mtdf[[factorOfValue]]
df2 <- dataset() %>%
ggplot(aes(x, mpg)) +
geom_point(aes(color = mpg, size = 1))+
geom_smooth(method = lm, se = F)+
theme( axis.line = element_line(colour = "darkblue", size = 1, linetype = "solid"))
return(df2)
})
})
}
# UI and Server Sections of App
ui <- fluidPage(theme = shinytheme("darkly"),
navbarPage(
theme = "cerulean",
"Market Analysis Tool",
# Import Tab----
tabPanel("Import",
sidebarPanel(
tags$h3("Input Comparables Data:"),
mod_import_ui("import_1"),
tags$h3("Input Whole Hood Data:"),
mod_import_ui("import_2")
),
mainPanel(
mod_importedDataTable_ui("importedDataTable_1"),
mod_importedDataTable_ui("importedDataTable_2")
), #main panel Import
), #tab panel import
# Comparables Graphs Tab----
tabPanel("Comparables Graphs",
sidebarPanel(
tags$h3("Check out these trends!")
),
mainPanel(
mod_plotFactorOfValue_ui("plotFactorOfValue_1")
), #main panel Comparables Graphs
)
) #navbar page
) #fluid page
server <- function(input, output, session) {
####Import the Data----
datasetComparables <- mod_import_server("import_1")
datasetWholeHood <- mod_import_server("import_2")
#### Output the Data Tables----
mod_importedDataTable_server("importedDataTable_1", dataset = datasetComparables)
mod_importedDataTable_server("importedDataTable_2", dataset = datasetWholeHood)
######## STARTING THE PLOTS HERE----
# I am unable to get the dataframe to be recognized, I am also unable to get the
# xvariable(factorOfValue) hardcoded as a parameter in my call function.
# Can you please help with this? THis is still part of the server section.
mod_plotFactorOfValue_server("plotFactorOfValue_1", dataset = datasetComparables,
factorOfValue = "cyl" ) ## "SqFtTotal"
}
shinyApp(ui = ui, server = server)

Shiny Modules: Use SliderInput in multiple Elements

I am new to Shiny Modules, and I want to use the input from the sliderInput in (at least) two different elements. Therefore I created a little reprex. I want to have a histogram with a vertical line to display the slider value and a table in the main panel, which should be filtered based on the same slider value.
Because in practice I have a lot of sliders, I thought Shiny Modules would be a good thing way to structure and reduce the amount of code.
Unfortunately, I have a bug, already tried various things but couldn't find a way how to resolve it. I cannot access the slider value in the table and the histogram. Thanks in advance for your help.
library(shiny)
library(tidyverse)
ui_slider <- function(id, height = 140, label = "My Label") {
sliderInput(inputId = NS(id, "slider"), label = label, min = 0, max = 5, value = 1)
}
server_slider <- function(id) {
moduleServer(id, function(input, output, session) {
reactive(get(input$slider))
})
}
ui_hist <- function(id, height = 140) {
plotOutput(outputId = NS(id, "hist_plot"), height = height)
}
server_hist <- function(id, df, col, slider_value) {
stopifnot(is.reactive(slider_value))
moduleServer(id, function(input, output, session) {
output$hist_plot <- renderPlot({
df %>%
ggplot(aes_string(x = col)) +
geom_histogram() +
geom_vline(aes(xintercept = slider_value()))
})
})
}
ui <- fluidPage(
titlePanel("My Dashboard"),
sidebarLayout(
sidebarPanel(
ui_hist("gear"),
ui_slider("gear", label = "Gear"),
ui_hist("carb"),
ui_slider("carb", label = "Carb")
),
mainPanel(
tableOutput("table")
)
)
)
server <- function(input, output, session) {
gear_val <- server_slider("gear")
carb_val <- server_slider("carb")
server_hist(
id = "gear",
df = tibble(mtcars),
col = "gear",
slider_value = gear_val
)
server_hist(
id = "carb",
df = tibble(mtcars),
col = "carb",
slider_value = carb_val
)
output$table <- renderTable({
tibble(mtcars) %>%
filter(gear > gear_val()) %>%
filter(carb > carb_val())
})
}
# Run the application
shinyApp(ui = ui, server = server)
Created on 2022-04-22 by the reprex package (v2.0.1)
You're using get() unnecessarily in your slider module server function. Removing it should resolve the issue.
server_slider <- function(id) {
moduleServer(id, function(input, output, session) {
reactive(input$slider)
})
}

How to update shiny module with reactive dataframe from another module

The goal of this module is create a reactive barplot that changes based on the output of a data selector module. Unfortunately the barplot does not update. It's stuck at the first variable that's selected.
I've tried creating observer functions to update the barplot, to no avail. I've also tried nesting the selector server module within the barplot module, but I get the error: Warning: Error in UseMethod: no applicable method for 'mutate' applied to an object of class "c('reactiveExpr', 'reactive', 'function')"
I just need some way to tell the barplot module to update whenever the data it's fed changes.
Barplot Module:
#UI
barplotUI <- function(id) {
tagList(plotlyOutput(NS(id, "barplot"), height = "300px"))
}
#Server
#' #param data Reactive element from another module: reactive(dplyr::filter(austin_map, var == input$var))
barplotServer <- function(id, data) {
moduleServer(id, function(input, output, session) {
#Data Manipulation
bardata <- reactive({
bar <-
data |>
mutate(
`> 50% People of Color` = if_else(`% people of color` >= 0.5, 1, 0),
`> 50% Low Income` = if_else(`% low-income` >= 0.5, 1, 0)
)
total_av <- mean(bar$value)
poc <- bar |> filter(`> 50% People of Color` == 1)
poc_av <- mean(poc$value)
lowincome <- bar |> filter(`> 50% Low Income` == 1)
lowincome_av <- mean(lowincome$value)
bar_to_plotly <-
data.frame(
y = c(total_av, poc_av, lowincome_av),
x = c("Austin Average",
"> 50% People of Color",
"> 50% Low Income")
)
return(bar_to_plotly)
})
#Plotly Barplot
output$barplot <- renderPlotly({
plot_ly(
x = bardata()$x,
y = bardata()$y,
color = I("#00a65a"),
type = 'bar'
) |>
config(displayModeBar = FALSE)
})
})
}
EDIT :
Data Selector Module
dataInput <- function(id) {
tagList(
pickerInput(
NS(id, "var"),
label = NULL,
width = '100%',
inline = FALSE,
options = list(`actions-box` = TRUE,
size = 10),
choices =list(
"O3",
"Ozone - CAPCOG",
"Percentile for Ozone level in air",
"PM2.5",
"PM2.5 - CAPCOG",
"Percentile for PM2.5 level in air")
)
)
}
dataServer <- function(id) {
moduleServer(id, function(input, output, session) {
austin_map <- readRDS("./data/austin_composite.rds")
austin_map <- as.data.frame(austin_map)
austin_map$value <- as.numeric(austin_map$value)
list(
var = reactive(input$var),
df = reactive(austin_map |> dplyr::filter(var == input$var))
)
})
}
Simplified App
library(shiny)
library(tidyverse)
library(plotly)
source("barplot.r")
source("datamod.r")
ui = fluidPage(
fluidRow(
dataInput("data"),
barplotUI("barplot")
)
)
server <- function(input, output, session) {
data <- dataServer("data")
variable <- data$df
barplotServer("barplot", data = variable())
}
shinyApp(ui, server)
As I wrote in my comment, passing a reactive dataset as an argument to a module server is no different to passing an argument of any other type.
Here's a MWE that illustrates the concept, passing either mtcars or a data frame of random values between a selection module and a display module.
The critical point is that the selection module returns the reactive [data], not the reactive's value [data()] to the main server function and, in turn, the reactive, not the reactive's value is passed as a parameter to the plot module.
library(shiny)
library(ggplot2)
# Select module
selectUI <- function(id) {
ns <- NS(id)
selectInput(ns("select"), "Select a dataset", c("mtcars", "random"))
}
selectServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
data <- reactive({
if (input$select == "mtcars") {
mtcars
} else {
tibble(x=runif(10), y=rnorm(10), z=rbinom(n=10, size=20, prob=0.3))
}
})
return(data)
}
)
}
# Barplot module
barplotUI <- function(id) {
ns <- NS(id)
tagList(
selectInput(ns("variable"), "Select variable:", choices=c()),
plotOutput(ns("plot"))
)
}
barplotServer <- function(id, plotData) {
moduleServer(
id,
function(input, output, session) {
ns <- NS(id)
observeEvent(plotData(), {
updateSelectInput(
session,
"variable",
choices=names(plotData()),
selected=names(plotData()[1])
)
})
output$plot <- renderPlot({
# There's an irritating transient error as the dataset
# changes, but handling it would
# detract from the purpose of this answer
plotData() %>%
ggplot() + geom_bar(aes_string(x=input$variable))
})
}
)
}
# Main UI
ui <- fluidPage(
selectUI("select"),
barplotUI("plot")
)
# Main server
server <- function(input, output, session) {
selectedData <- selectServer("select")
barplotServer <- barplotServer("plot", plotData=selectedData)
}
# Run the application
shinyApp(ui = ui, server = server)

How to save and load state with insertUI modules?

I'm trying to save and load state of a shiny app using bookmarks. However, it doesn't work and I wonder whether it is because of inserting dynamic UI. If there are other ways to save and load dynamically rendered ui and resulting output, that would be great too. I don't know where to start and this is as far as I could come.
Simple example
library(shiny)
ui <- function(request){fluidPage(
actionButton("add", "Add UI"),
bookmarkButton()
)}
# Server logic
server <- function(input, output, session) {
observeEvent(input$add, {
insertUI(
selector = "#add",
where = "afterEnd",
ui = textInput(paste0("txt", input$add),
"Insert some text")
)
})
}
# Complete app with UI and server components
shinyApp(ui, server, enableBookmarking = "server")
Complex example
library(shiny)
one_plotUI <- function(id) {
ns <- NS(id)
plotOutput(ns("plot"))
}
one_plot <- function(id, x, y, type, breaks, break_counts) {
moduleServer(
id,
function(input, output, session) {
output$plot <- renderPlot({
if (type == "scatter") {
plot(x, y)
} else {
if (breaks == "custom") {
breaks <- break_counts
}
hist(x, breaks = breaks)
}
})
}
)
}
ui <- fluidPage(
sidebarPanel(
bookmarkButton(),
selectInput("plotType", "Plot Type",
c(Scatter = "scatter", Histogram = "hist")
),
# Only show this panel if the plot type is a histogram
conditionalPanel(
condition = "input.plotType == 'hist'",
selectInput(
"breaks", "Breaks",
c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
),
# Only show this panel if Custom is selected
conditionalPanel(
condition = "input.breaks == 'custom'",
sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
)
),
actionButton("make_plot", "Insert new plot")
),
mainPanel(
div(id = "add_here")
)
)
server <- function(input, output) {
x <- rnorm(100)
y <- rnorm(100)
counter_plots <- 1
observeEvent(input$make_plot, {
current_id <- paste0("plot_", counter_plots)
# call the logic for one plot
one_plot(id = current_id,
x = x,
y = y,
type = input$plotType,
breaks = input$breaks,
break_counts = input$breakCount)
# show the plot
insertUI(selector = "#add_here",
ui = one_plotUI(current_id))
# update the counter
counter_plots <<- counter_plots + 1
})
}
shinyApp(ui, server, enableBookmarking = "server")
edit: Found another solution emulating what insertUI does but with renderUI:
library(shiny)
library(purrr)
ui <- function(request){fluidPage(
actionButton("add", "Add UI"),
uiOutput('dynamic_ui'),
bookmarkButton()
)}
# Server logic
server <- function(input, output, session) {
input_contents <- reactive({reactiveValuesToList(input)})
observeEvent(input$add, {
# a new ui will be rendered with one extra input each time add button is pressed
output$dynamic_ui <- renderUI({
map(1:input$add, ~textInput(inputId = paste0("txt", .x), label = paste0("txt", .x) ))
})
#add the old values, otherwise all the inputs will be empty agin.
input_contents() %>%
names() %>%
map(~ updateTextInput(session = session, inputId = .x, label = .x, value = input_contents()[[.x]]))
})
}
# Complete app with UI and server components
shinyApp(ui, server, enableBookmarking = "server")
insertUI might be broken. The only way i could "fix" it was to drop function(request) of the ui, that caused that all the values in the inputs have to be saved between stances (in state$values$input_restore). Also a warning is showed in the console, but it doesn't affect the functionality.
library(shiny)
library(tidyverse)
library(stringr)
ui <- fluidPage(
actionButton("add", "Add UI"),
uiOutput('restored_ui'), #this is very important
bookmarkButton())
# Server logic
server <- function(input, output, session) {
counter <- reactiveValues()
counter$n <- c(0) #This value is only used to initialize the object.
total_ui_count <- reactiveValues()
total_ui_count$info <- 0 #because input$add will reset to zero this will count the number of uis to remember.
#When bookmark button is pressed
onBookmark(function(state) {
state$values$currentCounter <- counter$n
state$values$input_restore <- reactiveValuesToList(input)
print(names(input) %>% str_subset('^txt'))
state$values$total_uis_to_restore <- counter$n[[length(counter$n)]]
})
#rerender the previous outputs and their values
onRestore(function(state) {
#restore values from previous state
counter$n <- state$values$currentCounter
vals <- state$values$input_restore
print(str_subset(names(vals), '^txt.*$')) #for debugging
total_ui_count$info <- state$values$total_uis_to_restore
print(total_ui_count$info)
#render back a ui with the previous values.
output$restored_ui <- renderUI({
str_subset(names(vals), '^txt.*$') %>%
sort(decreasing = TRUE) %>% #to avoid order reversal of the inputs
map(~ textInput(.x, label = .x, value = vals[[.x]])) #render the last inputs
})
})
observeEvent(input$add, {
#input$add starts as 1 in the next state (because ui is not wrapped in function(request)) that's why total_ui_count is present
counter$n <- c(counter$n, input$add + total_ui_count$info)
print(counter$n) #for debugging
insertUI(
selector = "#add",
where = "afterEnd",
ui = textInput(inputId = paste0("txt", counter$n[[length(counter$n)]]),
label = "Insert some text")
)})
}
# Complete app with UI and server components
shinyApp(ui, server, enableBookmarking = "server")

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
Create dynamic x and y dropdowns based on the column names within the CSV THIS WORKS
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"))
)
}
We need to manually specify the name space within a renderUI function using session$ns
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)
}

Resources