I would like to create a reactiveValues object in Shiny whose contents are defined the values contained in input widgets. I managed to do it, but my implementation seems to be unnecessarily clunky:
Create an empty reactiveValues object
Monitor when the value of input widgets changes with observeEvent
Assign values to the reactiveValues object using the non-reactive values from input widgets (isolate)
Here is an example:
ui <- fluidPage(
fluidRow(
column(2, radioButtons("main", label = "Main dish", choices = list("salad", "pasta"))),
column(2, radioButtons("desert", label = "Desert", choices = list("fruit", "cake"))),
column(8, actionButton("extra", "Louder!", style="background-color: #ffdb99"))
),
verbatimTextOutput("myorder")
)
server <- function(input, output, session) {
# 1. Create reactiveValues object
menuR <- reactiveValues()
# 2. Update values whenever widgets change
observeEvent(c(input$main, input$desert),
menuR[["meal"]] <- paste(c(isolate(input$main), isolate(input$desert)), collapse = " & ")
)
# 3. Perform operations on object values
observeEvent(input$extra,
menuR[["meal"]] <- paste0(toupper(menuR[["meal"]]), "!!!")
)
output$myorder <- renderText(menuR[["meal"]])
}
shinyApp(ui, server)
I would very much like to create the reactiveValues object directly like this (greatly simplifies the code above):
# Set values upon creation
menuR <- reactiveValues(meal = paste(c(input$main, input$desert), collapse = " & "))
which does not work because input$main is reactive...
I would have guessed that defining reactiveValues objects with values obtained from widgets would be a common thing to do.
Am I missing something?
Thanks for your help,
Hugo
You can do this by using reactive() instead of reactiveValues(). menuR is a reactive object that depends on the input values of main and desert. You can use it by calling menu() in your server code once it is defined. Also, this saves you from using isolate() as you can set the value of menu as a reactive object inside observeEvent().
library(shiny)
ui <- fluidPage(
fluidRow(
column(2, radioButtons("main", label = "Main dish", choices = list("salad", "pasta"))),
column(2, radioButtons("desert", label = "Desert", choices = list("fruit", "cake"))),
column(8, actionButton("extra", "Louder!", style="background-color: #ffdb99"))
),
verbatimTextOutput("myorder")
)
server <- function(input, output, session) {
menuR <- reactive({
paste(c(input$main, input$desert), collapse = " & ")
})
observeEvent(
input$extra,
menuR <- reactive({
toupper(paste(c(input$main, input$desert), collapse = " & "))
})
)
output$myorder <- renderText(menuR())
}
shinyApp(ui, server)
Edit
I misunderstood the problem earlier. You can use eventReactive() which monitors a input and changes when the user input changes. I have also added a default value for when the action button is yet to be clicked by the user.
library(shiny)
ui <- fluidPage(
fluidRow(
column(2, radioButtons("main", label = "Main dish", choices = list("salad", "pasta"))),
column(2, radioButtons("desert", label = "Desert", choices = list("fruit", "cake"))),
column(8, actionButton("extra", "Louder!", style="background-color: #ffdb99"))
),
verbatimTextOutput("myorder")
)
server <- function(input, output, session) {
# Set a 'default' value for the output
default.menuR <- reactive({
paste(c(input$main, input$desert), collapse = " & ")
})
menuR <- eventReactive(input$extra, {
toupper(paste(c(input$main, input$desert), collapse = " & "))
})
# Initial state of the button is 0, which displays 'default' value
output$myorder <- renderText({
if (input$extra == 0) {
return(default.menuR())
}
menuR()
})
}
shinyApp(ui, server)
Hope this helps!
I have taken Vishesh's suggestion and made it work. It does not look much different than my original example, and still requires repeating several lines of code.
It works by over-writing the reactive object each time one of the widgets changes.
library(shiny)
ui <- fluidPage(
fluidRow(
column(2, radioButtons("main", label = "Main dish", choices = list("salad", "pasta"))),
column(2, radioButtons("dessert", label = "dessert", choices = list("fruit", "cake"))),
column(8, actionButton("extra", "Louder!", style="background-color: #ffdb99"))
),
verbatimTextOutput("myorder")
)
server <- function(input, output, session) {
menuR <- reactive({
paste(c(input$main, input$dessert), collapse = " & ")
})
# Render text when app loads
output$myorder <- renderText(menuR())
# Update reactive object and re-render text (button 'extra')
observeEvent(
input$extra,{
temp <- toupper(paste(c(input$main, input$dessert), collapse = " & "))
menuR <<- reactive(temp)
output$myorder <- renderText(menuR())
}
)
# Update reactive object and re-render text (button 'main' or 'dessert')
observeEvent(c(input$main, input$dessert),{
menuR <- reactive(paste(c(input$main, input$dessert), collapse = " & "))
output$myorder <- renderText(menuR())
})
}
shinyApp(ui, server)
Related
I am referring to my previous post: while updating the datatable in R shiny, how to make column inputs necessary fields using "validate" and "need" in R shiny
I'm making a R shiny app in which the user can add numerous rows by clicking the "Add" button and saving the data to a .xpt file. As of now, everything is in working order.
I just stuck below:
However, in order to expand this app, I've added the button "clear" to the server function, as well as'reactiveValues', so that when users click it, all previous rows (added rows) are removed (cleared), and the app is ready to accept new rows. I can still add rows, but the clear option doesn't work and doesn't give me any errors. Is there anyone who can assist me?
code
library(shiny)
library(stringr)
library(shinydashboard)
library(tidyverse)
library(DT)
library("SASxport")
ui <- fluidPage(
fluidRow(tabsetPanel(id='tabs',
tabPanel("Tab1",
div(id = "form",
textInput("schoolId", label="SchoolId *" ),
selectInput("userId", label="UserId", choices = c("UserA", "UserB", "UserC"),selected = "UserA"),
textInput("class", label = "class"),
selectInput("result", label="result", choices = c("PASS", "FAIL" )),
#dateInput("resultdate", value = NA, label = "Date of the result / Remarks for fail"
#, format = "yyyy-mm-dd" )
),
actionButton("add", "Add"),
actionButton("clear", "Clear")
),
tabPanel("Tab2",
tabPanel("View",
conditionalPanel("input.add != 0",
DTOutput("DT2"), hr(), downloadButton('downloadData', 'Download'))
)
)
)
)
)
server <- function(input, output, session) {
store <- reactiveValues()
observeEvent(input$add,{
new_entry <- data.frame(SCHOOLID=input$schoolId, USERID=input$userId
, CLASS= input$class
, RESULT=input$result
)
# new_entry <- data.frame(SCHOOLID=input$schoolId, USERID=input$userId
# , CLASS= input$class
#, RESULT=input$result,
#RESULT_DATE = input$resultdate)
if("value" %in% names(store)){
store$value<-bind_rows(store$value, new_entry)
} else {
store$value<-new_entry
}
# If you want to reset the field values after each entry use the following two lines
for(textInputId in c("schoolId", "class")) updateTextInput(session, textInputId, value = "")
updateSelectInput(session, "userId", selected = "UserA")
updateSelectInput(session, "result", selected = "PASS")
# updateDateInput(session, "resultdate")
})
output$DT2 <- renderDT({
store$value
})
output$downloadData <- downloadHandler(
filename = paste0("mydata", ".xpt"),
content = function(file){
write.xport(store$value, file = file)
}
)
new_frame <- reactive({
store$value
})
#function allows to clear the rows
values <- reactiveValues(df_data = new_frame)
observeEvent(input$clear,{
if (!is.null(input$table1_rows_selected)) {
values$df_data <- values$df_data[-as.character(input$table1_rows_selected),]
}
})
output$Tab2 <- renderDataTable({
values$df_data
})
}
shinyApp(ui, server)
The problem here is a slight oversight in how the selected rows are obtained for deletion. Instead of getting selected rows from the DT table, one has to get them straight from the ui element which is DT2
Also, you can work directly on the DT table created in server, instead of storing a new reactive value
Here's the revised (relevant) server code :
#xxxxxxxx this not needed
#values <- reactiveValues(df_data = new_frame)
observeEvent(input$clear,{
if (!length(input$DT2_rows_selected)==0) {
#work directly on store variable
store$value<- store$value[-as.numeric(input$DT2_rows_selected),]
}
})
I've tested this and it works. Can post the whole app code if needed.
My Shiny app suddenly is behaving n a strange manner whereby it opens briefly and then closes itself. There are no errors in the console. On my Mac, the app works fine. However, on Windows, the issue arises.
My complete code can be seen below.
Subsequently, Mac, or Windows, when I am using my full dataset .csv file, (as opposed to my small dummy test dataset), I receive an error input string 1 is invalid UTF-8. I have tried all suggestions here How to identify/delete non-UTF-8 characters in R but without any success. I have also used the CLEAN() function in Excel itself, and also tried read.csv("dummyData.csv, encoding = "UTF-8"), neither of which worked. I'm out of ideas.
Any help on both these issues would be fantastic.
library(shiny)
library(tidyverse)
library(DT)
# Reading the main_data which the shiny app depends on, Please make sure that the column names are same
main_data <- read_csv("dummyData.csv")
ui <- fluidPage(
fluidRow(column(12, tags$h2("Assignment Details"))),
sidebarLayout(
sidebarPanel(
width = 3,
tags$div(
align = "center",
tags$img(src = "logo.png", width = "120", height = "120")
),
fluidRow(
column(12, align = "center", tags$br(), tags$b("Filter data")),
column(12, selectInput("sector_filter", "Sector", unique(main_data$Sector), multiple = TRUE)),
column(12, selectInput("client_filter", "Client", unique(main_data$`Client Name`), multiple = TRUE)),
column(12, selectInput("service_filter", "Service", unique(main_data$Service), multiple = TRUE)),
column(12, selectInput("cost_filter", "Cost", unique(main_data$`Cost (Ex-Vat)`), multiple = TRUE)),
column(12, align = "center", actionLink("reset_filters", "Clear Filters/Reset", style = "color: #962693"))
)
),
mainPanel(
width = 9,
tabsetPanel(
tabPanel(
"Assignment Description",
uiOutput("assignment_description")
),
tabPanel(
"Data Table",
DTOutput("data_table")
)
)
)
)
)
server <- function(input, output, session) {
# Creating a new empty tibble (which is basically a data.frame) for filtering based on the filters selected
filtered_data <- tibble()
observeEvent(input$reset_filters, {
updateSelectInput(session, "sector_filter", selected = "")
updateSelectInput(session, "client_filter", selected = "")
updateSelectInput(session, "service_filter", selected = "")
updateSelectInput(session, "cost_filter", selected = "")
})
# The observe code block will be triggered everytime any reactive object from the UI is changed (In this case out filters)
observe({
# If all the inputs are empty, We will just send the whole data without the filters. Else we filter
print(input$sector_filter)
print(input$client_filter)
print(input$service_filter)
print(input$cost_filter)
print(unique(main_data$Sector))
sector_filter_values <- input$sector_filter
client_filter_values <- input$client_filter
service_filter_values <- input$service_filter
cost_filter_values <- input$cost_filter
if (is.null(input$sector_filter)) {
sector_filter_values <- unique(main_data$Sector)
}
if (is.null(input$client_filter)) {
client_filter_values <- unique(main_data$`Client Name`)
}
if (is.null(input$service_filter)) {
service_filter_values <- unique(main_data$Service)
}
if (is.null(input$cost_filter)) {
cost_filter_values <- unique(main_data$`Cost (Ex-Vat)`)
}
filtered_data <<- main_data %>%
filter(Sector %in% sector_filter_values, `Client Name` %in% client_filter_values,
Service %in% service_filter_values, `Cost (Ex-Vat)` %in% cost_filter_values)
# This is where the assignment description will be rendered
output$assignment_description <- renderUI({
filtered_data$title <- paste0(filtered_data$`Client Name`, " - ", filtered_data$`Assignment Name`)
HTML(
paste0(
"<br><span style='color: #962693'>", filtered_data$title,
"</span><br>", filtered_data$`Assignment Description`, "<br>"
)
)
})
# This is where the table is rendered. To customise the table visit here https://rstudio.github.io/DT/
output$data_table <- renderDT({
datatable(
filtered_data %>% select(`Client Name`, `Assignment Name`, `Sector`, `Service`, `Cost (Ex-Vat)`)
)
})
})
# Whenever a row from the table is selected the Assignment Description must change regardless the filters selected
observeEvent(input$data_table_rows_selected, {
print(input$data_table_rows_selected)
filtered_data_from_table <- filtered_data[input$data_table_rows_selected, ]
print(filtered_data_from_table)
output$assignment_description <- renderUI({
filtered_data_from_table$title <- paste0(filtered_data_from_table$`Client Name`, " - ", filtered_data_from_table$`Assignment Name`)
HTML(
paste0(
"<br><span style='color: #962693'>", filtered_data_from_table$title,
"</span><br>", filtered_data_from_table$`Assignment Description`, "<br>"
)
)
})
})
}
shinyApp(ui = ui, server = server)
I took a small modification of the top answer here:
How to identify/delete non-UTF-8 characters in R
Simply converting my columns via the below code fixed my issues.
df$`Column Name`<- iconv(df$`Column Name`, to = "UTF-8")
I am creating an R Shiny app where I have an extremely long list of options for selectInput. Depending on the option you select, the value is going to change. I know that for a small list of options you can set the values yourself in the server function like so:
server <- function(input, output) {
output$graph <- renderPlot({
player <- switch(input$var,
"LeBron James" = 23,
"Kobe Bryant" = 24,
"DeMar DeRozan" = 10,
"Kyle Lowry" = 7)
plotGraph(player)
})
}
But my list has at least 100 options and it's certainly not clean nor efficient to set the values like this for all 100 options. Is there a way to set the values depending on the option selected without having to do it manually?
Below is my code in my ui function
ui <- fluidPage(
titlePanel(h1("Fantasy Dashboard")),
sidebarLayout(
sidebarPanel(h2("Player Name Goes Here"),
selectInput("playername",
label = "Choose a player",
choices = player_choices,
selected = NULL),
),
mainPanel(plotOutput("graph"))
)
)
The choices will be stored in player_choices. These choices are read from a txt file. And depending on the option selected, the variable player should be set to the corresponding value. Thanks in advance!
Try:
library(shiny)
playernames <- list("Smith","Johnston","Andrew")
shinyApp(
ui = fluidPage(
uiOutput("selectname"),
textOutput("result")
),
server = function(input, output) {
output$selectname <- renderUI( {
selectInput("playername", "Choose player",playernames)})
output$result <- renderText({
paste("You chose", input$playername)
})
}
)
The playernames list can also be reactive and be modified by other inputs.
I am trying to create a shiny code that is able to filter a table non pre-determined number of times. When the user uploads a different (new) table, unfortunately the code breaks as I need to restart a lapply loop somehow, throwing out the previously stored column names.
I would like to create an non pre-defined filtering options for a table within Shiny. The user can select a column and filter a table choosing different categorical variables within that column. It is possible to add additional selection fields by pressing the 'Add' button.
the UI:
library(shiny)
library(shinydashboard)
library(dplyr)
ui <- shinyUI(
pageWithSidebar(
headerPanel("testing of dynamic number of selection"),
sidebarPanel(
uiOutput("buttons")),
mainPanel(
uiOutput("drops")
,tableOutput("table")
)
))
The server:
A table (test.csv) is automatically stored in a reactive values and a first searching field appears with 3 buttons (Add = to add a new searching field by reading in the colnames and a multiselect that stores the unique variables from that columns. The filtering function is activated by the Calculate button)
server<-function(input, output, session) {
###### read in test file
values<-reactiveValues(number = 1,
upload = NULL,
input = NULL)
values$upload<-read.csv("test.csv")
#just the "add" button, in this instance it shouldn't be a uiOutput
output$buttons <- renderUI({
div(
actionButton(inputId = "add", label = "Add"), actionButton(inputId = "calc", label = "Calculate"),
actionButton(inputId = "new", label = "new table")
)
})
#pressing the add button
observeEvent(input$add, {
cat("i adding a new record\n")
values$number <- values$number + 1L })
daStuff <- function(i){
inputName<-paste0("drop", i)
inputName2<-paste0("select", i)
inputText<-if(values$number>0){input[[paste0("drop",i)]]}else{F} # previously selected value for dropdown
inputSelect <- if(values$number>1){input[[paste0("select",i)]]}else{F} # previously selected value for dropdown
fluidRow(
column(6,selectInput(inputName, inputName, c(colnames(values$upload)), selected = inputText)),
column(6,selectInput(inputName2, inputName2,
na.omit(unique(as.vector(values$upload[,input[[paste0("drop",i)]]]))),
multiple=TRUE, selectize=TRUE, selected=inputSelect)) )}
output$drops<- renderUI({
lapply(seq_len(values$number), daStuff)})
By pressing the Calculate button, the uploaded table is subjected to filtering, depending on the selected unique values and shown in the output$table
observeEvent(input$calc, {
values$input<-NULL
for (i in 1:values$number){
if(!is.null(input[[paste0("select",i)]])){
if(is.null(values$input)){
values$input<- filter(values$upload,values$upload[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])}
else{
values$input<- filter(values$input,values$input[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])}
} }
if (is.null(values$input)){values$input<-values$upload}
output$table <- renderTable({values$input})
})
My problem is when I upload a new table (test2.csv), I don't know how to erase the previously stored selections (drop* and select* values) and gives back an error message.
observeEvent(input$new,{
values$upload<-read.csv("test2.csv")
})
}
shinyApp(ui=ui, server = server)
I suppose I should stop somehow the lapply loop and restart it over, so the previously stored values are replaced depending on the new selection, but I am a bit stuck on how I could achieve that.
Just in case you might still be looking for solutions, I wanted to share something that was similar and could potentially be adapted for your needs.
This uses observeEvent for all select inputs. If it detects any changes, it will update all inputs, including the possibilities for select based on drop.
In addition, when a new file is read, the selectInput for drop and select are reset to first value.
Edit: I forgot to keep selected = input[[paste0("drop",i)]] in place for the dropdown (see revised code). It seems to keep the values now when new filters are added - let me know if this is what you had in mind.
library(shiny)
library(shinydashboard)
library(dplyr)
myDataFrame <- read.csv("test.csv")
ui <- shinyUI(
pageWithSidebar(
headerPanel("Testing of dynamic number of selection"),
sidebarPanel(
fileInput("file1", "Choose file to upload", accept = ".csv"),
uiOutput("buttons")
),
mainPanel(
uiOutput("inputs"),
tableOutput("table")
)
)
)
server <- function(input, output, session) {
myInputs <- reactiveValues(rendered = c(1))
myData <- reactive({
inFile <- input$file1
if (is.null(inFile)) {
d <- myDataFrame
} else {
d <- read.csv(inFile$datapath)
}
d
})
observeEvent(lapply(paste0("drop", myInputs$rendered), function(x) input[[x]]), {
for (i in myInputs$rendered) {
updateSelectInput(session,
paste0('select', i),
choices = myData()[input[[paste0('drop', i)]]],
selected = input[[paste0("select",i)]])
}
})
output$buttons <- renderUI({
div(
actionButton(inputId = "add", label = "Add"),
actionButton(inputId = "calc", label = "Calculate")
)
})
observeEvent(input$add, {
myInputs$rendered <- c(myInputs$rendered, max(myInputs$rendered)+1)
})
observeEvent(input$calc, {
showData <- NULL
for (i in 1:length(myInputs$rendered)) {
if(!is.null(input[[paste0("select",i)]])) {
if(is.null(showData)) {
showData <- filter(myData(), myData()[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])
}
else {
showData <- filter(showData, showData[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])
}
}
}
if (is.null(showData)) { showData <- myData() }
output$table <- renderTable({showData})
})
observe({
output$inputs <- renderUI({
rows <- lapply(myInputs$rendered, function(i){
fluidRow(
column(6, selectInput(paste0('drop',i),
label = "",
choices = colnames(myData()),
selected = input[[paste0("drop",i)]])),
column(6, selectInput(paste0('select',i),
label = "",
choices = myData()[1],
multiple = TRUE,
selectize = TRUE))
)
})
do.call(shiny::tagList, rows)
})
})
}
shinyApp(ui, server)
I am working on a simple app that is supposed to ask for 1 (or more) unstructured text files, given by the user with fileInput. These files have all the same structure.
The idea is to make the cleaning/extraction on the background and give back the clean data to the user (ideally into a table).
I am fairly new using Shiny and the examples I have found basically indicate how to proceed when the file input is already in a clean and structured way.
Here is a simplified code that illustrates what I have done so far:
library(shiny)
shinyUI(fluidPage(
titlePanel(title = h2("Title", align = "left")),
sidebarLayout(position = "left",
sidebarPanel(h3("Data management window", align = "center"),
fileInput(inputId = "file_1",
label = "Select file 1")
),
mainPanel(
uiOutput(outputId = "tb")
)
)))
And here the server side:
shinyServer(function(input, output) {
input_file_1 <- reactive({
if(is.null(input$file_1)){
return("!! No data loaded !!")
}
readLines(input$file_1$datapath)
})
output$data_1 <- renderText({
fileText_1 <- paste(input_file_1(), collapse = "\n")
})
output$tb <- renderUI({
tabsetPanel(
tabPanel("Window 1",
br(),
tags$div(
tags$p("Summary infos : "),
tags$ul(
tags$li("Date calculation : ", Sys.Date()),
tags$li("Info 1: "),
tags$li("Info 2 : "),
tags$li("Info 3 : "),
br(),
verbatimTextOutput("data_1"))
))
)
})
})
At this stage I have managed to render the text of the file into the app. What I would like to do is to show in tabPanel some results obtained from the data extracted from the text file, like in the case of Sys.Date() but using values from the input file.
Do you have any ideas on how to proceed? Hope my question makes sense.
Your question is still too general, please more specific. But here is something to get you started. Using your server file and the iris dataset to make it easier, you can use the renderText() expression to extract the summary of one of your variable. And then you just add textOutput() expression in your tags$li("Info 1: ") argument.
Following a comment from OP, you could add a selectInput() that could help you pick one variable you want to summarise or extract information from. The code work as it is. Just replace iris by the file you will be loading.
ui = fluidPage(
titlePanel(title = h2("Title", align = "left")),
sidebarLayout(position = "left",
sidebarPanel(h3("Data management window", align = "center"),
fileInput(inputId = "file_1",
label = "Select file 1"),
uiOutput("Variable")
),
mainPanel(
uiOutput(outputId = "tb")
)
))
server = function(input, output) {
input_file_1 <- reactive({
if(is.null(input$file_1)){
return("!! No data loaded !!")
}
readLines(input$file_1$datapath)
})
output$data_1 <- renderText({
fileText_1 <- paste(input_file_1(), collapse = "\n")
})
output$Variable <- renderUI({
obj2 <- iris #replace by input_file_1()
selectInput("Variable", "Choose a variable", as.list(colnames(obj2)), multiple = FALSE)
})
output$summary1 <- renderPrint({
sub <- iris %>% select(input$Variable) #replace iris
a <- max(sub)
a
})
output$tb <- renderUI({
tabsetPanel(
tabPanel("Window 1",
br(),
tags$div(
tags$p("Summary infos : "),
tags$ul(
tags$li("Date calculation : ", Sys.Date()),
tags$li("Info 1: ", textOutput("summary1")),
tags$li("Info 2 : "),
tags$li("Info 3 : "),
br(),
verbatimTextOutput("data_1"))
))
)
})
}