So I asked the following, R Shiny Dynamic Input, a couple of days ago and although the answer is correct given the question, I now want to elaborate some more since I am unable to edit the code given to answer my new question. So originally I wanted to be able to ask the user to specify a number, say k, that would then dynamically generate k fields for the user to fill out. Now, the code given assumes that the output is a numeric, however, I want the user to be able to specify a vector of 5 values in each of the 1,...,k fields. Since, after specifying k, the inputs are going to be k vectors of length 5 of numerical values, I want to be able to store these values in a k by 5 matrix. That way I can use those values to conduct data manipulations later. If it helps, here is the code from the original answer:
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
numericInput("numInputs", "How many inputs do you want", 4),
# place to hold dynamic inputs
uiOutput("inputGroup")
),
# this is just a demo to show the input values
mainPanel(textOutput("inputValues"))
)
))
# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
# observe changes in "numInputs", and create corresponding number of inputs
observeEvent(input$numInputs, {
output$inputGroup = renderUI({
input_list <- lapply(1:input$numInputs, function(i) {
# for each dynamically generated input, give a different name
inputName <- paste("input", i, sep = "")
numericInput(inputName, inputName, 1)
})
do.call(tagList, input_list)
})
})
# this is just a demo to display all the input values
output$inputValues <- renderText({
paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
})
})
# Run the application
shinyApp(ui = ui, server = server)
Edit
Here is updated code that still doesn't completely work:
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
numericInput("numInputs", "How many inputs do you want", 4),
# place to hold dynamic inputs
uiOutput("inputGroup")
),
# this is just a demo to show the input values
mainPanel(tableOutput("inputValues"))
)
))
# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
# observe changes in "numInputs", and create corresponding number of inputs
observeEvent(input$numInputs, {
output$inputValues <- renderTable({
all <- paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
matrix <- as.matrix(all, ncol=5)
as.data.frame(matrix)
})
})
# this is just a demo to display all the input values
output$inputValues <- renderText({
paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
})
})
# Run the application
shinyApp(ui = ui, server = server)
You only need to make a few changes:
Change mainPanel(textOutput("inputValues")) to mainPanel(tableOutput("inputValues")) (this is not essential, it just shows the values in a table/matrix format so you can see them)
Change numericInput(inputName, inputName, 1) to textInput(inputName, inputName, "1 2 3 4 5")
Change output$inputValues <- renderText({...... to
output$inputValues <- renderTable({
all <- paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
matrix = as.matrix(read.table(text=all))
data.frame(matrix)
})
matrix is what you want: a k by 5 matrix.
Note that I did not do any input verification. It is assumed that user will enter 5 numbers in each input separated by spaces. If they do not, output might be either wrong or you'll see an error. You may need to implement some input checking here to ensure that it is 5 numbers and not anything else.
Complete code
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
numericInput("numInputs", "How many inputs do you want", 4),
# place to hold dynamic inputs
uiOutput("inputGroup")
),
# this is just a demo to show the input values
mainPanel(tableOutput("inputValues"))
)
))
# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
# observe changes in "numInputs", and create corresponding number of inputs
observeEvent(input$numInputs, {
output$inputGroup = renderUI({
input_list <- lapply(1:input$numInputs, function(i) {
# for each dynamically generated input, give a different name
inputName <- paste("input", i, sep = "")
textInput(inputName, inputName, "1 2 3 4 5")
})
do.call(tagList, input_list)
})
})
# this is just a demo to display all the input values
output$inputValues <- renderTable({
all <- paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
matrix = as.matrix(read.table(text=all))
data.frame(matrix)
})
})
# Run the application
shinyApp(ui = ui, server = server)
Related
I am trying to create a number of tabs based on some list and create similar fields in those tabs with their own identity. Can we create and use reactives like
eval(paste0("Updated_files_list_",Some_List[k], "()"))
I have used following code and one location but that will be different for different tabs it seems that reactives are created for the lists but I am unable to either assign values to it or call it -
Some_List <- list("AGG", "CMP1", "CMP2")
Some_Long_List <- list("Aggregated Things", "Component 1", "Component 2")
sidebar <- dashboardSidebar(
do.call(sidebarMenu, c(lapply(1:length(Some_List), function(i) {
menuItem(Some_Long_List[i], tabName = paste0(Some_List[i],"_tab"))
}))))
uibody <- dashboardBody(
do.call(tabItems, c(lapply(1:length(Some_List), function(i) {
tabItem(tabName = paste0(Some_List[i],"_tab"), h2(Some_Long_List[i]),
fluidPage(fluidRow(column(3,
dateInput(paste0("ME_DATE_output_",Some_List[i]),label=h2("Select a Date"), value="2020-05-29"))
,column(9,column(verbatimTextOutput(paste0("file_path_",Some_List[i])),width=12),hr(),
selectInput(paste0('select_file_',Some_List[i]),'Select a File to display', choices=NULL),hr(),
column(dataTableOutput(paste0('selected_table_',Some_List[i])),width=12)))))
}))))
ui = dashboardPage(dashboardHeader(title = "Results"), sidebar, uibody)
server = function(input, output, session) {
# Location for Source Inputs
Some_loc <- "K:/Outputs"
lapply (1:length(Some_List), function(k){
ME_DATE_GUI <- reactive({input[[paste0("ME_DATE_output_",Some_List[k])]]})
# Files Path
output[[paste0("file_path_",Some_List[k])]] <- renderPrint({ req(Some_loc) })
# Updated Files list
assign(paste0("Updated_files_list_",Some_List[k]), reactive({
lf_some <- list.files(Some_loc,full.names = TRUE)
names(lf_some) <- basename(lf_some)
lf_some
}))
# Fetch selection of file
observeEvent(eval(paste0("Updated_files_list_",Some_List[k], "()")), {
updateSelectInput(session, paste0("select_file_",Some_List[k]), choices=eval(paste0("Updated_files_list_", Some_List[k],"()")))
})
# Display Selected file
output[[paste0("selected_table_",Some_List[k])]] <- renderDataTable({
req(input[[paste0("select_file_", Some_List[k])]])
read.csv(input[[paste0("select_file_", Some_List[k])]], header=T)
})
})
}
shinyApp(ui, server)
In the selectInput field, I can see Update_files_list_AGG(), ("AGG", "CMP1" and "CMP2" in respective tabs) but not the list of files in the assigned location. The error I get is Error: cannot open the connection
I want to collect user inputs, and output them as a table/data frame within Shiny. Inspired by the post here (Collect All user inputs throughout the Shiny App), I was able to do this but with some issues below. Here I have attached the example code, where I intend to collect the user input using the reactive textInput box. The number of textInput boxes is according to the user input for "Number of box". The output table looks fine when I kept increasing the "Number of box", such as I increased the number of boxes from 1 to 2 (see attached screenshot 1 and 2). However, when I changed the number of boxes from 2 back to 1, the output table still showed the previous results "textBox2 the second input" (see screenshot 3). My question is how to remove this previous input ("textBox2 the second input") and only print out the current user input. Thanks!
Example code
library(shiny)
ui <- basicPage(
fluidRow(column(6,
numericInput("nbox", "Number of box", value = 1,
min = 1))),
fluidRow(column(6,style='padding:0px;',
wellPanel(
uiOutput("textInputBox"))),
column(6, style='padding:0px;',
tableOutput('show_inputs')))
)
server <- shinyServer(function(input, output, session){
output$textInputBox <- renderUI({
num <- as.integer(input$nbox)
lapply(1:num, function(i) {
textInput(paste0("textBox", i),
label = paste0("textBox", i))
})
})
AllInputs <- reactive({
myvalues <- NULL
for(i in 1:length(names(input))){
myvalues <- as.data.frame(rbind(myvalues,
(cbind(names(input)[i],input[[names(input)[i]]]))))
}
names(myvalues) <- c("User input variable","User input value")
myvalues
})
output$show_inputs <- renderTable({
AllInputs()
})
})
shinyApp(ui = ui, server = server)
Screen shot (1,2,3 in order)
Created inputs are not deleted when a new renderUI is called, so there is no solution this way.
A workaround is to use a counter (like the one previously used to create the input) and use it to rebuild the names:
AllInputs <- reactive({
num <- as.integer( input$nbox )
my_names <- paste0("textBox", 1:num)
all_values <- stack( reactiveValuesToList(input) )
myvalues <- all_values[ all_values$ind %in% c("nbox", my_names),
c(2, 1)]
names(myvalues) <- c("User input variable","User input value")
myvalues
})
Note the use of reactiveValuesToList to replace the loop.
In the shiny application multiple numeric input widgets are generated dynamically each having an initial value inside which are row-column numbers. The sum of those values are also displayed in main panel.
As the user changes the numeric input values the sum updates accordingly. Currently it is instantly. I want to delay the process of inputs in main panel for all numeric inputs by adding action/submit button till I change more than one numeric Inputs.
But I am getting the following error if action/submit buttons are used inside the render functions.
Warning: Error in <<-: number of items to replace is not a multiple of replacement length.
If not inside render functions where else to place as these widgets are generated inside renderUI only.
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel(title = "Use of action/submit button for multiple inputs"),
sidebarLayout(
sidebarPanel(numericInput("rows","Input No. of rows",value = 3,min=1),
br(),
numericInput("col","Input No. of cols",value = 1,min=1)),
mainPanel(textOutput("display"),
uiOutput("plo")
))))
Server.r
server <- function(input,output){
# creating input widgets dynamically
output$plo <- renderUI({
z <- input$col
lapply(seq(input$col), function(j){
column(width=3,
lapply(seq(input$rows),function(i){
numericInput(inputId = paste0("range",paste0(i,j)),label = j,value = paste0(i,j))
})
)
})
})
# capturing the value of input widgets in a matrix
cm <- reactive({
c <- input$col
r <- input$rows
changed_m <- matrix(nrow = r,ncol = c)
lapply(seq(input$col), function(j){
lapply(seq(input$rows),function(i){
changed_m[i,j] <<- input[[paste0("range",paste0(i,j))]]
})
})
changed_m
})
# display the sum
output$display <- renderText({
paste0("Sum of matrix: ",sum(cm()))
})
}
Here is a working example of a possible solution. You can store the string to display (or just the sum of course) in a reactiveVal, and update this only when the user clicks the button, or display an alternative text when one of the inputs has changed so the user knows the sum is no longer correct.
Hope this helps!
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel(title = "Use of action/submit button for multiple inputs"),
sidebarLayout(
sidebarPanel(numericInput("rows","Input No. of rows",value = 3,min=1),
br(),
numericInput("col","Input No. of cols",value = 1,min=1),
actionButton('update' ,'update!')),
mainPanel(textOutput("display"),
uiOutput("plo")
))))
server <- function(input,output){
# creating input widgets dynamically
output$plo <- renderUI({
z <- input$col
lapply(seq(input$col), function(j){
column(width=3,
lapply(seq(input$rows),function(i){
numericInput(inputId = paste0("range",paste0(i,j)),label = j,value = paste0(i,j))
})
)
})
})
# capturing the value of input widgets in a matrix
cm <- reactive({
c <- input$col
r <- input$rows
changed_m <- matrix(nrow = r,ncol = c)
lapply(seq(input$col), function(j){
lapply(seq(input$rows),function(i){
x=input[[paste0("range",paste0(i,j))]]
changed_m[i,j] <<- ifelse(!is.null(x),x,0)
})
})
changed_m
})
# initialize our reactiveVal with an empty string
my_sum <- reactiveVal('')
# observer that listens to the button click, then updates the sum string.
observeEvent(input$update,{
my_sum(paste0("Sum of matrix: ",sum(cm())))
})
# observer that listens to changes in the input, then updates the sum string.
observeEvent(cm(),ignoreNULL = T,ignoreInit = T, {
isolate(my_sum('invalidated. Press button to update.'))
})
# display the sum string
output$display <- renderText({
my_sum()
})
}
shinyApp(ui,server)
I understand that reactive values notifies any reactive functions that depend on that value as per the description here
based on this I wanted to make use of this property and create a for loop that assigns different values to my reactive values object, and in turn I am expecting another reactive function to re-execute itself as the reactive values are changing inside the for loop. Below is a simplified example of what i am trying to do:
This is the ui.R
library(shiny)
# Define UI
shinyUI(pageWithSidebar(
titlePanel("" ,"For loop with reactive values"),
# Application title
headerPanel(h5(textOutput("Dummy Example"))),
sidebarLayout(
#Sidebar
sidebarPanel(
textInput("URLtext", "Enter csv of urls", value = "", width = NULL, placeholder = "Input csv here"),
br()
),
# Main Panel
mainPanel(
h3(textOutput("caption"))
)
)
))
This is the server file:
library(shiny)
shinyServer(function(input, output) {
values = reactiveValues(a = character())
reactive({
url_df = read.table(input$URLtext)
for (i in 1:5){
values$a = as.character(url_df[i,1])
Sys.sleep(1)
}
})
output$caption <- renderText(values$a)
})
This does not give the expected result. Actually when I checked the content of values$a
it was null. Please help!
Rather than using a for loop, try using invalidateLater() with a step counter. Here's a working example that runs for me with an example csv found with a quick google search (first column is row index 1-100).
library(shiny)
# OP's ui code
ui <- pageWithSidebar(
titlePanel("" ,"For loop with reactive values"),
headerPanel(h5(textOutput("Dummy Example"))),
sidebarLayout(
sidebarPanel(
textInput("URLtext", "Enter csv of urls", value = "", width = NULL, placeholder = "Input csv here"),
br()
),
mainPanel(
h3(textOutput("caption"))
)
)
)
server <- function(input, output, session) {
# Index to count to count through rows
values = reactiveValues(idx = 0)
# Create a reactive data_frame to read in data from URL
url_df <- reactive({
url_df <- read.csv(input$URLtext)
})
# Reset counter (and url_df above) if the URL changes
observeEvent(input$URLtext, {values$idx = 0})
# Render output
output$caption <- renderText({
# If we have an input$URLtext
if (nchar(req(input$URLtext)) > 5) {
# Issue invalidation command and step values$idx
if (isolate(values$idx < nrow(url_df()))) {
invalidateLater(0, session)
isolate(values$idx <- values$idx + 1)
}
}
# Sleep 0.5-s, so OP can see what this is doing
Sys.sleep(0.5)
# Return row values$idx of column 1 of url_df
as.character(url_df()[values$idx, 1])
})
}
shinyApp(ui = ui, server = server)
I have successfully updated UI dynamically through renderUI(). I have a long list of inputs to choose from. The check boxes are used to dynamically add numeric inputs. So, to implement this, I used lapply. However, I have used values of selected check boxes in checkboxgroup itself to populate IDs of the dynamically added numerical input instead of using paste(input, i) in lapply.
ui code snippet :
checkboxGroupInput(inputId = "checkboxgrp", label = "Select types",
choices = list("ELECTAPP","NB W $","PUR","MANUAL LTR","REDEMPTION","NB W TRANSFER","NB WOUT $","OUTPUT")),
...
fluidRow(column(12, verbatimTextOutput("value")))
...
uiOutput("numerics")
server code snippet :
renderUI({
numInputs <- length(input$checkboxgrp)
if(numInputs==0){
wellPanel("No transaction selected")
}
else{
lapply(1:numInputs, function(i){
x[i]=input$checkboxgrp[i]
list(numericInput(input$checkboxgrp[i], min = 0, label = input$checkboxgrp[i],
value= input[[x[i]]] ))
})
}
})
output$value <- renderPrint({
numInputs <- length(input$checkboxgrp)
lapply(1:numInputs, function(i){
print(input[[x[i]]]) ## ERROR
})
})
I have used input[[x[i]]] as to instantiate value to be retained after adding or removing a numeric input. But, I want to extract values from input$x[i] or input[[x[i]]] into a vector for further use which I'm unable to do.
*ERROR:Must use single string to index into reactivevalues
Any help is appreciated.
EDIT
using 3 different ways of extracting values from input generate 3 different errors:
Using print(input$x[i]) # ERROR
NULL
NULL
NULL
NULL
[[1]]
NULL
[[2]]
NULL
[[3]]
NULL
[[4]]
NULL
Using print(input[[x[i]]]) # ERROR
Must use single string to index into reactivevalues
Using print('$'(input, x[i])) # ERROR
invalid subscript type 'language'
If I understand you correctly, you want to access values of dynamically generated widgets and then just print them out.
In my example below, which should be easy to generalise, the choices are the levels of the variable Setosa from the iris dataset.
The IDs of the generated widgets are always given by the selected values in checkboxGroupInput. So, input$checkboxgrp says to shiny for which level of setosa there should be generated a widget. At the same time input$checkboxgrp gives IDs of generated widgets. That's why you don't need to store the IDs of "active" widgets in other variable x (which is probably a reactive value).
To print the values out you can do the following:
output$value <- renderPrint({
activeWidgets <- input$checkboxgrp
for (i in activeWidgets) {
print(paste0(i, " = ", input[[i]]))
}
})
This line print(input[[x[i]]]) ## ERROR yields an error because x[i] (whatever it is) is not a vector with a single value but with multiple values.
Full example:
library(shiny)
ui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
checkboxGroupInput("checkboxgrp", "levels", levels(iris$Species))
),
mainPanel(
fluidRow(
column(6, uiOutput("dynamic")),
column(6, verbatimTextOutput("value"))
)
)
)
)
server <- function(input, output) {
output$dynamic <- renderUI({
numInputs <- length(input$checkboxgrp)
if(numInputs==0){
wellPanel("No transaction selected")
}
else{
lapply(1:numInputs, function(i){
x[i]=input$checkboxgrp[i]
list(numericInput(input$checkboxgrp[i], min = 0, label = input$checkboxgrp[i],
value= input[[x[i]]] ))
})
}
})
output$value <- renderPrint({
activeWidgets <- input$checkboxgrp
for (i in activeWidgets) {
print(paste0(i, " = ", input[[i]]))
}
})
}
shinyApp(ui = ui, server = server)
Edit:
You could tweak the lapply part a little bit (mind <<- operator :) )
else{
activeWidgets <- input$checkboxgrp
val <- 0
lapply(activeWidgets, function(i){
val <<- val + 1
list(numericInput(i, min = 0, label = i,
value = val ))
})
}
Edit 2 In response to a comment:
server <- function(input, output) {
output$dynamic <- renderUI({
numInputs <- length(input$checkboxgrp)
if(numInputs==0){
wellPanel("No transaction selected")
}
else{
activeWidgets <- input$checkboxgrp
val <- 0
lapply(activeWidgets, function(i){
val <<- val + 1
list(numericInput(i, min = 0, label = i,
value = val ))
})
}
})
allChoices <- reactive({
# Require that all input$checkboxgrp and
# the last generated numericInput are available.
# (If the last generated numericInput is available (is not NULL),
# then all previous are available too)
# "eval(parse(text = paste0("input$", input$checkboxgrp))))" yields
# a value of the last generated numericInput.
# In this way we avoid multiple re-evaulation of allChoices()
# and errors
req(input$checkboxgrp, eval(parse(text = paste0("input$", input$checkboxgrp))))
activeWidgets <- input$checkboxgrp
res <- numeric(length(activeWidgets))
names(res) <- activeWidgets
for (i in activeWidgets) {
res[i] <- input[[i]]
}
res
})
output$value <- renderPrint({
print(allChoices())
})
}