So I have a complex shiny app with some internal default data. I added the possibility to update these internal data with new files (uploaded with fileInput()). I want that only after I click on a button, the default data and the new data merge together. I find this solution (just add any file in fileInput() to make it works).
library(shiny)
ui <- fluidPage(
fluidRow(
column(3, fileInput("fileupaziendedata", "Carica il file csv")), #just load any file
column(3, actionButton("mergeaziende", "Unisci"))), #the button that merges
hr(),
dataTableOutput("summary_table")
)
server <- function(input,output){
data = reactive({
data.frame(id = c(1:5), lett = c("A", "B", "C", "D", "E"))
})
datafromfile = reactive({
data.frame(id = c(6,7), lett = c("F", "G"))
})
data2 = reactive({
if(!is.null(input$fileupaziendedata) && input$mergeaziende > 0){
rbind(data(), datafromfile())
}else{
data()
}
})
output$summary_table <- renderDataTable({
data2()
})
}
shinyApp(ui=ui, server=server)
Now my problem is that since I used input$mergeaziende > 0 condition, after the first button press, it will be always greater than 0. Is there a way to prevent this?
I've seen this type of question come up before. Basically, how to "reset" the action button value. Here's my general solution. I reset a reactiveVal instead of trying to manipulate the input. Here the input will reset after 5, but it can be changed to something else (like when your data reactive triggers):
library(shiny)
ui <- fluidPage(
actionButton('click' ,'Click'),
textOutput('counter_out')
)
server <- function(session, input, output){
# initialize at - 0
# this is the value to observe (not input$click)
counter <- reactiveVal(0)
output$counter_out <- renderText(counter())
#increment per click
observeEvent(input$click, counter(counter() + 1))
observe({
#WHEN you want the count reset
if(counter() > 5) counter(0)
})
}
shinyApp(ui, server)
Related
I have a Shiny app (please see end for a minimum working example) with a "parent" reactable table and a drilldown table that pops up when a user clicks on a row of the parent table. The information on which row is selected in the parent is obtained via reactable::getReactableState(). However, when the user switches to a different "parent" table, the function returns the row selection for the outdated table, not the updated one.
This occurs event though the output for the new parent table has completed it's calculations and is fully updated by the time the drilldown table starts it's calculations. After the whole systems finished and the app is idle, something (and I'm not sure what) triggers the input to reactable::getReactableState() to be invalidated, and the reactives fire again, but this time using the updated (or "correct" from my perspective) tables, and returns the expected result, which is that now row is selected.
Referring to the reactive graph below, what I want to do is have input$tables-table_parent__reactable__selected set not NULL every time input$tables-data_set changes.
I have tried to do this via the session$sendCustomMessage() and Shiny.addCustomMessageHandler approach found here: Change the input value in shiny from server, but I find that, although I can change input$tables-table_parent__reactable__selected value it doesn't seem to send send the info to the browser until after all the outputs are done caculating when input$tables-data_set is changed.
A minimum working example:
UI module:
drilldownUI <- function(id) {
ns <- NS(id)
tagList(
tags$script("
Shiny.addCustomMessageHandler('tables-table_parent__reactable__selected', function(value) {
Shiny.setInputValue('tables-table_parent__reactable__selected', value);
});
"),
shiny::selectizeInput(
inputId = ns("data_set"),
label = "Data set",
choices = c("iris", "cars"),
selected = "iris"
),
reactable::reactableOutput(outputId = ns("table_parent"),
width = "100%"),
reactable::reactableOutput(
outputId = NS(id, "drilldown_table"),
width = "100%"
)
)
}
Server module:
drilldownServer <- function(id, dat) {
moduleServer(id, function(input, output, session) {
dataset <- reactive({
data_list <-
list(iris = as.data.table(iris), cars = as.data.table(MASS::Cars93))
data_list[[input$data_set]]
})
data_grouped <- reactive({
dataset()[, .N, by = c(grouping_var())]
})
grouping_var <- reactive({
if (input$data_set == "iris") {
return("Species")
}
"Origin"
})
output$table_parent <- reactable::renderReactable({
req(input$data_set)
reactable::reactable(
data_grouped(),
selection = "single",
onClick = "select"
)
})
selected <- reactive({
out <- reactable::getReactableState("table_parent", "selected")
if(is.null(out)||out=="NULL") return(NULL)
out
})
output$drilldown_table <- reactable::renderReactable({
req(selected())
# This should only fire after a new parent table is generated and the row selection is
# reset to NULL, but it fires once the new table is generated and BEFORE the row selection
# is reset to NULL
selected_group <- data_grouped()[selected(), ][[grouping_var()]]
drilldown_data <- dataset()[get(grouping_var()) == selected_group]
reactable::reactable(drilldown_data)
})
observeEvent(input$data_set, {
session$sendCustomMessage("tables-table_parent__reactable__selected", 'NULL')
})
})
App:
library(shiny)
library(reactable)
library(data.table)
# Define UI for application that draws a histogram
ui <- fluidPage(
drilldownUI("tables")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
drilldownServer("tables")
}
# Run the application
shinyApp(ui = ui, server = server)
I found the solution thanks in part to this SO answer https://stackoverflow.com/a/39440482/9474704.
The key was to consider the row selection a state, rather than just reacting to input changes. Then, by using reactiveValues() instead of reactive(), I could update the state in multiple places using observeEvent().
An important additonal piece of information was that observe functions are eager, and you can set a priority, so when the user changes the input$data_set, I could reset the row selection to 0 before the drilldown reactable::renderReactable() section was evaluated.
The updates to the server module below for an example of the working solution:
drilldownServer <- function(id, dat) {
moduleServer(id, function(input, output, session) {
dataset <- reactive({
data_list <-
list(iris = as.data.table(iris), cars = as.data.table(MASS::Cars93))
data_list[[input$data_set]]
})
data_grouped <- reactive({
dataset()[, .N, by = c(grouping_var())]
})
grouping_var <- reactive({
if (input$data_set == "iris") {
return("Species")
}
"Origin"
})
# Create output for parent table
output$table_parent <- reactable::renderReactable({
req(input$data_set)
reactable::reactable(data_grouped(),
selection = "single",
onClick = "select")
})
# Create state variable
selected <- reactiveValues(n = 0)
currentSelected <- reactive({
reactable::getReactableState("table_parent", "selected")
})
observeEvent(currentSelected(), priority = 0, {
selected$n <- currentSelected()
})
# When data set input changes, set the selected number of rows to 0e
observeEvent(input$data_set,
label = "reset_selection",
priority = 9999, {
selected$n <- 0
})
# Create output for drilldown table
output$drilldown_table <- reactable::renderReactable({
req(selected$n > 0)
selected_group <-
data_grouped()[selected$n, ][[grouping_var()]]
drilldown_data <-
dataset()[get(grouping_var()) == selected_group]
reactable::reactable(drilldown_data)
})
})
}
My goal is that user uploads an Excel file. Then, the user selects which sheets wants to be summarised, after the selection has been updated. I have managed to update selectInput with the name of the sheets but I have not been able to find\understand how to summarise based on what the sheet selected by the user. Thanks for any help.
library(shiny)
library(shinythemes)
library(data.table)
library(ggplot2)
library(dplyr)
library(readxl)
not_sel <- "Not Selected"
# Define UI for application that draws a histogram
ui <- fluidPage('MAIN TITLE',
theme = shinytheme('flatly'),
tabsetPanel(
sidebarLayout(
sidebarPanel(
fileInput('files','Import File', accept = c('.csv','.xlsx'),
multiple = F),
actionButton('boton1', 'Load', icon = icon('table')),
br(),
selectInput("indices", "Select SHEET:", choices = c(not_sel))
),
mainPanel(
tabsetPanel(
tabPanel('Data',
tableOutput('tabla'),
tableOutput('cabeza')),
tabPanel('Stats',
# selectInput('var01', 'Variable to summarise', choices = c(not_sel)),
tableOutput('stats')),
)
)
)
)
)
##############
server <- function(input, output, session) {
options(shiny.maxRequestSize=10*1024^2)
df <- eventReactive(input$boton1, {
req(input$files)
if(is.null(input$files))return(NULL)
# else{
read_excel(input$files$datapath)
# }
})
# Sheets of file uploaded
sheets_name <- reactive({
if (!is.null(input$files)) {
return(excel_sheets(path = input$files$datapath))
} else {
return(NULL)
}
})
# Update inputSelector with sheet names
observeEvent(df(),{
choices <- c(sheets_name())
updateSelectInput(inputId = "indices", choices = choices)
})
# DATA Tab
## This will show the name of the file
output$tabla <- renderTable({
input$files$name
})
## This Shows the head() but it is only showing the first sheet
output$cabeza <- renderTable({
tabla <- as_tibble(bind_cols(Date = format(as.Date(df()$Date),"%Y-%m-%d"),
Close.Price = df()$Close))
head(tabla)
})
# HERE is where I do not know how to calculate based on selection
# Table for STATS
output$stats <- renderTable({
datos <- df()
Value <- c(round(mean(datos$Close,na.rm = T),2)
)
Statistic <- c("Mean")
data.table(Statistic, Value)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I want to assume that by knowing how to calculate mean based on the sheet selected, I. can replicate the code for the top rows (head()) shown in the Data Panel.
If I missed a similar question asked, I would appreciate any link and I will try the solution proposed first.
As I cannot share the file, this is how the file would look:
After working with this answer I made my app work. If there is a 'cleaner'/'better' answer, I will be happy to read.
Following the recommendation in the linked answer my server ended up like this:
ui <-fluidPage{
#My UI stayed the same with the exception of adding
uiOutput("dropdownUI") #Whererever I needed to appear
}
server <- function(input, output, session) {
...ANSWER FROM THE LINK...
## STATS Tab
output$stats <- renderTable({
Values <- c(round(mean(Dat()[,2],na.rm = T),2)
)
Statistics <- c("Mean")
data.table(Statistics, Values)
})
}
I have been using R for awhile but R shiny seems like a completely foreign language to me.
I have an RShiny app utilizing the rhandsontable, which takes input from both another column in the rhandsontable and outside the rhandsontable to calculate a new column. Currently, the table does update everything when any entry from within the table is modified, but I'd like to minimize the risk of user error as much as possible, either by making the rhandsontable reactive to both changes inside and outside the table (preferred) or reset when a button is clicked.
The example below is modified from How to reset to default a reactive rhandsontable? but reproduced my issue. As soon as I add an observeEvent for the rhandsontable, even before I call the external input, the reset button no longer works.
To make the observeEvent reactive to both changes in the external input and the table changes, I tried to make a reactive input (e.g., listener <- reactive(c(input$two_by_two$changes$changes, input$reset_input))), put them both in the observeEvent (e.g., input$two_by_two$changes$changes | input$reset_input), which both result in Warning: Error in do.call: second argument must be a list error.
library(shiny)
library(rhandsontable)
server <- shinyServer(function(input, output, session) {
FACTOR <- reactive(input$factor_val)
DF <- data.frame(A = c(1, 2), B = c(2, 4), row.names = c("C", "D"))
DF1 <- reactiveValues(data=DF)
output$two_by_two <- renderRHandsontable({
input$reset_input
rhandsontable(DF1$data)
})
observeEvent(input$two_by_two$changes$changes,
{DF1$data <- hot_to_r(input$two_by_two)
DF1$data[,2] = DF1$data[,1]*FACTOR() }) })
ui <- shinyUI(fluidPage(
actionButton(inputId = "reset_input", label = "Reset"),
br(),
numericInput("factor_val","Multiplier Value",min=0,max=10,value=2),
rHandsontableOutput("two_by_two")
))
shinyApp(ui, server)
Thanks for any help again.
The reason you were not able to reset was that you had changed the reactive dataframe, but were never resetting it back. I added a new observe event to handle that. Please see if this is what you are looking for.
library(shiny)
library(rhandsontable)
server <- shinyServer(function(input, output, session) {
FACTOR <- reactive(input$factor_val)
DF <- data.frame(A = c(1, 2), B = c(2, 4), row.names = c("C", "D"))
DF1 <- reactiveValues(data=DF)
observeEvent(input$reset_input, {
DF1$data <- DF
})
output$two_by_two <- renderRHandsontable({
#input$reset_input
rhandsontable(DF1$data)
})
observeEvent(input$factor_val, {
req(input$factor_val)
DF1$data <- hot_to_r(req({input$two_by_two}))
DF1$data[,2] = DF1$data[,1]*FACTOR()
})
})
ui <- shinyUI(fluidPage(
actionButton(inputId = "reset_input", label = "Reset"),
br(),
numericInput("factor_val","Multiplier Value",min=0,max=10,value=2),
rHandsontableOutput("two_by_two")
))
shinyApp(ui, server)
Here I asked an similar question and got a working answer. But the solution does not work if 'actionButton' of sub segment is replace by 'selectInput'. On each selection of selectInput creates two outputs. Please help.. Thanks....
library(shiny)
ui <- fluidPage(
verbatimTextOutput("txt",placeholder = T), #"It is Created for Testing"
actionButton("addSeg", "Add a Segment"),
uiOutput("myUI")
)
server <- function(input, output, session) {
alld <- reactiveValues()
alld$ui <- list()
# Action to add new Segment
observeEvent(input$addSeg,{
new_id <- length(alld$ui) + 1
sub_name <- paste0("addSub_", new_id)
alld$ui[[new_id]] <- list(selectInput(sub_name,"Add a variable", choices = c("V1","V2"), selected = NULL))
observeEvent(input[[sub_name]], {
new_text_id <- length(alld$ui[[new_id]]) + 1
alld$ui[[new_id]][[new_text_id]] <- HTML(paste0("Variable ",input[[sub_name]]," added<br>"))
}, ignoreInit = TRUE)
})
output$myUI <- renderUI({alld$ui})
output$txt <- renderText({class(alld$ui)})
}
shinyApp(ui, server)
This behaviour occurs because the custom UI element is re-rendered every time a new element is added to the list. Once you click "V2" and the new text element is added, the selectInput itself re-renders and resets to V1, which is noticed by the observer you've created.
The following might be a solution for you:
observeEvent(input$addSeg,{
new_id <- length(alld$ui) + 1
sub_name <- paste0("addSub_", new_id)
alld$ui[[new_id]] <- list(
selectInput(sub_name,
"Add a variable",
choices = c("", "V1","V2"),
selected = "")
)
observeEvent(input[[sub_name]], {
if (input[[sub_name]] == "") return()
new_text_id <- length(alld$ui[[new_id]]) + 1
alld$ui[[new_id]][[new_text_id]] <- HTML(paste0("Variable ",input[[sub_name]]," added<br>"))
}, ignoreInit = TRUE)
})
What I've done here is add an empty option to your selectInputs, and a condition to the corresponding observer that it shouldn't do anything if the input is empty. This way, I'm harnessing the "resetting" behaviour to be useful instead of annoying.
I have an app that has a few dependent selectInputs, so if you choose something in the first, the second should update to a specific value. That works fine. However! Now I want to force a specific combination on the two selects that do not correspond to the update logic, but after I update the two selects, the change of the first triggers an update of the other and I end up with the wrong result. Also after the forced combination has been applied, if a new change to the first select is done, then the "old" rule should reapply.
library(shiny)
ui <- fluidPage(
selectInput("A_sel","select" ,c("A","B","C","D"),"A",FALSE)
,selectInput("B_sel","same" ,c("A","B","C","D"),"A",FALSE)
,actionButton("ForceCombi","force C and D")
)
server <- function(input, output, session) {
observeEvent(input$A_sel,{
updateSelectInput(session,"B_sel",selected = input$A_sel)
})
observeEvent(input$ForceCombi,{
updateSelectInput(session,"A_sel",selected = "C")
updateSelectInput(session,"B_sel",selected = "D")
})
}
shinyApp(ui, server)
EDIT - Timer solution:
I set a timestamp to each activation and see which was the last to be activated, except if the time difference is less than a sec then I assume that the button was pressed which activated the select. Then the return from that reactive is decides how to update the selects. A bit of a hack:
library(shiny)
library(dplyr)
ui <- fluidPage(
selectInput("A_sel","select",c("A","B","C","D"),"A",FALSE)
,selectInput("B_sel","same as above",c("A","B","C","D"),"A",FALSE)
,actionButton("A_to_B","force C and D")
)
server <- function(input, output, session) {
but <- eventReactive(input$A_to_B,{tibble(src = "but", time = Sys.time())})
sel <- eventReactive(input$A_sel ,{tibble(src = "sel", time = Sys.time())})
src <- eventReactive(c(input$A_to_B,input$A_sel),{
df <- try(rbind(but(),sel()))
if(typeof(df) == "character") return("sel")
if(abs(difftime(df$time[1],df$time[2],units = "sec")) < 1) return("but")
df %>% arrange(time) %>% pull(src) %>% last -> df
return(df)
})
observe({
src <- src()
if(src == "sel") {
updateSelectInput(session,"B_sel",selected = input$A_sel)
} else if (src == "but") {
updateSelectInput(session,"A_sel",selected = "C")
updateSelectInput(session,"B_sel",selected = "D")
}
})
}
shinyApp(ui, server)
Here's a simpler implementation of your timestamp idea. I have set the threshold to 0.5 seconds but actual threshold can only be determined after considering other reactive dependencies in the app. You should also look into the priority arguments of observe and observeEvent using which you could potentially control the execution sequence of reactives.
Having said that, I still have a feeling that there is a better way to do this. I think looking at ?shiny::throttle and ?shiny::debounce could help as well.
library(shiny)
ui <- fluidPage(
selectInput("A_sel","select", c("A","B","C","D"),"A",FALSE)
,selectInput("B_sel","same", c("A","B","C","D"),"A",FALSE)
,actionButton("ForceCombi", "force C and D")
)
server <- function(input, output, session) {
tstamp <- reactiveValues(t = Sys.time())
observeEvent(input$A_sel, {
req((Sys.time() - tstamp$t) > 0.5)
tstamp$t <- Sys.time()
updateSelectInput(session,"B_sel", selected = input$A_sel)
})
observeEvent(input$ForceCombi, {
updateSelectInput(session,"A_sel", selected = "C")
updateSelectInput(session,"B_sel", selected = "D")
tstamp$t <- Sys.time()
})
}
shinyApp(ui, server)