I am trying to create a date input that automatically updates the inputted value to the end of the selected month. The problem I was having was when I ran this first bit of code (below) was that if the user tries to manually change the date by typing in a date, they are unable to because the input is updated instantly to the end of the month before the user can finish typing.
library(shinydashboard)
library(lubridate)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
dateInput(
inputId = "date",
label = "End of Month Date",
value = ceiling_date(x = Sys.Date() + 365, unit = "month") - 1,
startview = "year"
)
)
)
server <- function(input, output, session) {
observe({
if(is.Date(input$date) & length(input$date) > 0){
if(input$date != ceiling_date(input$date, unit = "month") - 1) {
updateDateInput(
session,
inputId = "date",
value = ceiling_date(x = input$date, unit = "month") - 1
)
}
}
})
}
shinyApp(ui, server)
So, I tried implementing debounce (as shown below - server code change only) so that it would delay the input from updating until the user was finished typing; however, I am running into an issue. The timer does not reset on each key stroke as I understand it should. Instead, the timer operates as if I were using the throttle function and starts when the input is first changed and doesn't reset when the input changes.
server <- function(input, output, session) {
observe({
dateinputdelay <- debounce(r = reactive(input$date), millis = 2000)
if(is.Date(dateinputdelay()) & length(dateinputdelay()) > 0){
if(dateinputdelay() != ceiling_date(dateinputdelay(), unit = "month") - 1) {
updateDateInput(
session,
inputId = "date",
value = ceiling_date(x = input$date, unit = "month") - 1
)
}
}
})
}
Edit: the best I can get is with the code below, but because the "fancy date input thingy" has its own internal updating mechanism separate from shiny's reactive concept, this debouncing is only addressing one source of problem.
The first trick is that the debounce needs to happen before the block of code is even started. That is, it "de-bounces" the start of the dependent code blocks; once they are started, it does not help.
The second is knowing that observe blocks attempt to run their code eagerly (see shiny docs and read "Details"), whereas reactive blocks are relatively lazy -- they only run dependent code as needed. It's the "eager" part that may be hurting.
Additionally, you use side-effect in the observe block to update the input field, but you never store the value elsewhere. I suggest that it might be better to calculate the new value in one place (in a functional manner, like a reactive block that should not operate in side-effect) and then use that later.
server <- function(input, output, session) {
dateinputdelay <- debounce(reactive(input$date), 2000)
end_of_month <- reactive({
# print("react!")
x <- dateinputdelay()
if (is.Date(x) & length(x) > 0) {
if (x != ceiling_date(x, unit = "month") - 1) {
x <- ceiling_date(x = input$date, unit = "month") - 1
}
}
x
})
observe({
# print("observe!")
updateDateInput(
session,
inputId = "date",
value = end_of_month()
)
})
}
(I've kept a couple of print statements in there, not because they help much here, but because they can be a good tool to see when/how-frequently reactitivity is causing code-blocks to run.)
As I said up top, I suspect this behavior is indicative of something within dateInput, not within the subsequent reactive or observe blocks.
Related
I generate a dynamic number of valueBox in my shiny, and this number can change depending of the user input.
I managed to handle this with a renderUI where I put the wanted number of valueBoxOutput, and I have an observe that will feed them with the content using renderValueBox.
My problem is: the code in the renderValueBox, for some reason, is actually executed after the observe is finished, so because the renderValueBox is in a loop (to have a dynamic number of them) but the code is executed for all the output after the loop, all my output will get the last value of the loop.
Here is a min reprex:
library(shiny)
library(shinydashboard)
library(shinyWidgets)
# Function
compute <- function(id)
{
print(paste("Compute ", id))
return(id)
}
# UI
ui = shinyUI(fluidPage(
titlePanel("Compare"),
useShinydashboard(),
sidebarLayout(
sidebarPanel(
numericInput("numitems", label = "Number of items", min = 1, max = 10, value = 2)
),
mainPanel(
uiOutput("boxes")
)
)
))
# Server
server = shinyServer(function(input, output, session) {
data <- reactiveValues(
ids = list()
)
output$boxes <- renderUI({
print("boxes")
box_list <- list()
id_list <- list()
for(id in 1:(input$numitems)) {
id_box <- paste0("box_", id)
print(paste("boxes - ", id_box))
id_list <- append(id_list, id_box)
box_list <- append(
box_list,
tagList(
shinydashboard::valueBoxOutput(id_box)
)
)
data$ids <- id_list
}
print("boxes end")
fluidRow(box_list)
})
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
output[[id_box]] <- shinydashboard::renderValueBox(valueBox(id_box, compute(id_box), icon = icon("circle-info"), color = "teal"))
}
print("end observe")
})
})
# Run
shinyApp(ui = ui , server = server)
Here is the result:
And the console output:
As you can see the compute (and the render in general) is done after the end of the observe function, and both output will use the last id_box that were set (so the last loop, box_2), instead of correctly using box_1 and box_2.
I tried using force, computing valueBox outside the render, using reactive lists, nothing worked, because whatever I do the render is evaluated after the observe so only the last loop values will be used no matter what.
Do anyone know a way to force execution during the loop ? Or see another way of achieving the same result ?
Why it's always after spending hald a day on a problem, looking for dozens of posts and forum, don't find anything, finally decide to ask a question... that a few minutes later I finally find an answer.
Anyway, one way to correct this (found here) is to encapsulate the render inside the local function, like this:
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
local({
tmp <- id_box
output[[tmp]] <- shinydashboard::renderValueBox(valueBox(tmp, compute(tmp), icon = icon("circle-info"), color = "teal"))
})
}
print("end observe")
})
Now the compute is still called after the end of the observe, but the tmp variable has the correct value:
The result is what I wanted:
For the record, I had already tried to use the local function, but if you don't copy the id_box inside another variable just for the local bloc, it won't work.
As I understand, eventReactive (or any reactive function) should not recalculate stuff whose related input did not change, but this is what's happening in my case. I'm pretty sure I'm doing something wrong but I just don't know what. In essence, I have two eventReactive functions, one involves a very time-consuming calculation, and the other mainly just plotting (should be quite quick). However, even when I change some inputs for plotting, the first eventReactive function is executed too (even though it's not needed).
Here is a shortened version of my code:
server <- function(input, output) {
res_tabl <-
eventReactive(c(input$recalc, input$recalc2), # this is a time-consuming calculation
ignoreNULL = FALSE, {
prep_sim(
gg_start = input$gg_start,
gg_end = input$gg_end
)
})
threeplots <-
eventReactive(c(input$recalc, input$recalc2), # this is for plotting
ignoreNULL = FALSE, {
prep_plot(
results_to_plot = res_tabl(),
yval_opt = input$yval_opt
)
})
output$esdc_plot_comb <- renderPlot({
threeplots()[[1]]
})
output$esdc_plot_tot <- renderPlotly({
threeplots()[[2]]
})
output$esdc_plot_comb2 <- renderPlot({
threeplots()[[1]]
})
output$esdc_plot_tot2 <- renderPlotly({
threeplots()[[2]]
})
output$esdc_table <- renderDataTable({
res_tabl()
})
}
What should I do so that when I press a single Action button and I only changed input$yval_opt, only the second eventReactive content would run? (Nothing should run until I click the button.)
Less importantly – and perhaps this should be a separate question – as you can see I render each of the two returned plots twice. Is there perhaps a more efficient way to do this?
(The full code is available here.)
This was tricky.
To avoid automatic calculation at App start-up, you should set ignoreNULL = T
This works on a single condition, but not on multiple conditions using c(recalc1,recalc2)
Solution is :
eventReactive(req(isTruthy(input$recalc1) | isTruthy(input$recalc2)), ignoreNULL = T,...
Added a reactiveVal() to keep track of last calculation update
I think following Minimal Reproducible example responds to your needs :
library(shiny)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Test"),
# Sidebar with a slider inpust
sidebarLayout(
sidebarPanel(
sliderInput("vizslider",
"viz percentage:",
min = 1,
max = 100,
value = 30),
sliderInput("calcslider",
"Calculation duration (s):",
min = 1,
max = 10,
value = 2),
actionButton("recalc1", "Calc 1"),
actionButton("recalc2", "Calc 2"),
),
# Show result
mainPanel(
textOutput("result")
)
)
)
# Define server logic
server <- function(input, output) {
lastcalc <- reactiveVal(0)
run <- reactive({})
calcresult <- eventReactive(req(isTruthy(input$recalc1) | isTruthy(input$recalc2)), ignoreNULL = T, {
if (lastcalc()==input$calcslider) {return("last calculation")} else {lastcalc(input$calcslider)}
cat("Start calc for ",input$calcslider, "seconds\n")
Sys.sleep(input$calcslider)
cat("End calc \n")
paste("calculation done in",input$calcslider,"seconds")
})
output$result <- eventReactive(c(input$recalc1,input$recalc2), ignoreNULL = T, {
req(calcresult())
paste("filter",input$vizslider,"% of a ",calcresult())
})
}
# Run the application
shinyApp(ui = ui, server = server)
In my tiny Shiny app I am asking the user: how many time periods do you want to cut your time series into? For example, the user selects 3.
I want to use this input to take a fixed vector of dates and make it possible for the user the select from it the desired last date of Time Period 1 (in select box 1), and Time Period 2 (in select box 2). (The last date for time period 3 will be the very last date, so I don't need to ask).
I am not sure how to do it. I understand that because I don't know the desired number of time periods in advance, I have to create a list. But how do I then collect the input from those select boxes?
Thanks a lot!
library(shiny)
### UI #######################################################################
ui = shinyUI(fluidPage(
titlePanel("Defining time periods"),
# Sidebar:
sidebarLayout(
sidebarPanel(
# Slider input for the number of time periods:
numericInput("num_periodsnr", label = "Desired number of time periods?",
min = 1, max = 10, value = 2),
uiOutput("period_cutpoints")
),
# Show just the number of periods so far.
mainPanel(
textOutput("nr_of_periods")
)
)
))
### SERVER ##################################################################
server = shinyServer(function(input, output, session) {
library(lubridate)
output$nr_of_periods <- renderPrint(input$num_periodsnr)
# Define our dates vector:
dates <- seq(ymd('2016-01-02'), ymd('2017-12-31'), by = '1 week')
# STUCK HERE:
# output$period_cutpoints<-renderUI({
# list.out <- list()
# for (i in 1:input$num_periodsnr) {
# list.out[[i]] <- renderPrint(paste0("Sometext", i), ,
# )
# }
# return(list.out)
# })
})
# Run the application
shinyApp(ui = ui, server = server)
This is similar to a question I asked and subsequently worked out an answer to here. The big changes are (predictably) in the server.
Nothing needs to change in the UI, but as you'll see below I've included another textOutput so that you can see the dates you end up selecting, and I've also added an actionButton, which I'll explain later.
The server function has a couple additions, which I'll describe first and then put together at the end. You're right that you need to create a list of input objects inside the renderUI, which you can do through lapply. At this step, you're creating as many selectInputs as you'll have cutpoints, minus one because you say you don't need the last:
output$period_cutpoints<-renderUI({
req(input$num_periodsnr)
lapply(1:(input$num_periodsnr-1), function(i) {
selectInput(inputId=paste0("cutpoint",i),
label=paste0("Select cutpoint for Time Period ", i, ":"),
choices=dates)
})
})
Next, you'll need to access the values selected in each, which you can do in the same way, using a reactiveValues object you create first, and assign the new values to it. In my version of this problem, I couldn't figure out how to get the list to update without using an actionButton to trigger it. Simple reactive() or observe() doesn't do the trick, but I don't really know why.
seldates <- reactiveValues(x=NULL)
observeEvent(input$submit, {
seldates$x <- list()
lapply(1:(input$num_periodsnr-1), function(i) {
seldates$x[[i]] <- input[[paste0("cutpoint", i)]]
})
})
Full working app code then looks like this:
library(shiny)
ui = shinyUI(fluidPage(
titlePanel("Defining time periods"),
sidebarLayout(
sidebarPanel(
numericInput("num_periodsnr", label = "Desired number of time periods?",
min = 1, max = 10, value = 2),
uiOutput("period_cutpoints"),
actionButton("submit", "Submit")
),
mainPanel(
textOutput("nr_of_periods"),
textOutput("cutpoints")
)
)
))
server = shinyServer(function(input, output, session) {
library(lubridate)
output$nr_of_periods <- renderPrint(input$num_periodsnr)
dates <- seq(ymd('2016-01-02'), ymd('2017-12-31'), by = '1 week')
output$period_cutpoints<-renderUI({
req(input$num_periodsnr)
lapply(1:(input$num_periodsnr-1), function(i) {
selectInput(inputId=paste0("cutpoint",i),
label=paste0("Select cutpoint for Time Period ", i, ":"),
choices=dates)
})
})
seldates <- reactiveValues(x=NULL)
observeEvent(input$submit, {
seldates$x <- list()
lapply(1:(input$num_periodsnr-1), function(i) {
seldates$x[[i]] <- input[[paste0("cutpoint", i)]]
})
})
output$cutpoints <- renderText({as.character(seldates$x)})
})
shinyApp(ui = ui, server = server)
you can make the boxes dynamically inside an lapply and send them as 1 output object to the ui
require("shiny")
require('shinyWidgets')
ui = shinyUI(fluidPage(
titlePanel("Defining time periods"),
# Sidebar:
sidebarLayout(
sidebarPanel(
# Slider input for the number of time periods:
numericInput("num_periodsnr", label = "Desired number of time periods?",
min = 1, max = 10, value = 2),
uiOutput("period_cutpoints")
),
# Show just the number of periods so far.
mainPanel(
textOutput("nr_of_periods")
)
)
))
# Define server logic ----
server <- function(session, input, output) {
output$period_cutpoints<- renderUI({
req(input$num_periodsnr > 0)
lapply(1:input$num_periodsnr, function(el) {
airDatepickerInput(inputId = paste('PeriodEnd', el, sep = ''), label = paste('Period End', el, sep = ' '), clearButton = TRUE, range = F, update_on = 'close')
})
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Since you did not provide a dataset to apply the inputs on, and I don't know what date ranges your data has, I did not add code to set min/max on the date pickers, and not sure what kind of code to provide for you to use the data. You would need to write something to put them in a list indeed
values <- reactiveValues(datesplits = list(),
previous_max = 0)
observeEvent(input$num_periodsnr, {
if(input$num_periodsnr > values$previous_max) {
lapply(values$previous_max:input$num_periodsnr, function(el) {
observeEvent(input[[paste(paste('PeriodEnd', el, sep = '')]], {
values$datesplits[el] <- input[[paste(paste('PeriodEnd', el, sep = '')]]
})
values$previous_max <- max(values$previous_max, input$num_periodsnr)
})
}
})
and then use the list of dates for whatever you need to do with them I think.
I use the trick with run lapenter code hereply from previous_max to input$num_periodsnr if(input$num_periodsnr > values$previous_max){} to avoid the problem you create when you repeatedly create observers for the same input element. Whereas ui elements are overwritten when created in a loop, observeEvents are made as copies, so every time your loop fires, you make another copy of observers 1:n. This results in all copies firing every time, until you have a million observers all firing, creating possible strange bugs, unwanted effects and loss of speed.
I have a text input box that uses Sys.time() as its value, however as normal time passes it stays static, I've been trying to refresh the time to display a new value, but insofar the only way I've been able to update the time is via restarting the app.
I've tried using textoutput() and the invalidate later() function, however this only displays the current time that "ticks" so to speak but still requires user input, which is redundant since the whole point is to have them not enter the time. This input and a few others eventually gets uploaded into a google sheet via the googlesheets package.
this is my current code:
Stime <- as.character(format(Sys.time(), "%H:%M", "EST"))
textInput(
inputId = "Time",
label = "Today's Time",
value = Stime)
what it looks like now (via imgur) except it is static
The only thing I'm trying to do is literally increment the time! Any help appreciated!
From the R Shiny website:
http://shiny.rstudio.com/gallery/current-time.html
options(digits.secs = 3) # Include milliseconds in time display
function(input, output, session) {
output$currentTime <- renderText({
# invalidateLater causes this output to automatically
# become invalidated when input$interval milliseconds
# have elapsed
invalidateLater(as.integer(input$interval), session)
format(Sys.time())
})
}
I have amended the shiny example slightly from http://shiny.rstudio.com/gallery/current-time.html to help display time within a textInput box, as requested:
library(shiny)
ui <- shiny::fluidPage(shiny::uiOutput("ui"))
server <- function(input, output, session) {
output$ui <- shiny::renderUI({
invalidateLater(1000, session)
shiny::textInput(
inputId = "time",
label = "Today's Time",
value = as.character(format(Sys.time(), "%H:%M", "EST"))
)
})
}
shiny::shinyApp(ui = ui, server = server)
I am trying to automate the interaction of a Shiny app so it displays a series of results while incrementing through a predetermined range of inputs, without having to repetitiously count and change input values. This automation will provide a systematic view of a set of inputs, such as displays of refreshed price charts for selected stocks, or plots of current performance indicators for real-time processes that are being monitored.
This is similar to question [Update graph/plot with fixed interval of time] (Update graph/plot with fixed interval of time) which runs a loop with a timer. Extending that approach, my objective is to:
a) Automatically set the invalidateLater pause high (1 hour) to effectively stop the cycle after a fixed (5) set of displays, pending new user input to restart it.
b) [When I can do that, I will add a counter-based control to cycle through a set of input$obs before it stops. For simplicity, that step, which has the same error and presumably same solution, is omitted here.]
Using the above referenced toy example, the following script does repeatedly cycle through its 5 displays, but it yields this error rather than changing the pause interval.
Listening on port 8100
Error in hist.default(dist, main = paste("Last Histogram count =", as.numeric(updater()), :
'x' must be numeric
as.numeric(autoControl())
Error: could not find function "autoControl"
I can not find the reactive conductor, reactiveValues or other methods that this task requires. Thank you for your help.
library(shiny)
updates <- 0
updater <- function(){ updates + 1 }
runApp(list(
ui = pageWithSidebar(
headerPanel("Hello Shiny!"),
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 1,
max = 1000,
value = 50)
,
selectInput(inputId = "secPause",
label = "Seconds between displays:",
choices = c(1, 2, 3, 60*60),
selected = 2)
),
mainPanel(
plotOutput("distPlot")
)
),
server =function(input, output, session) {
updateTracker <- reactive( {
invalidateLater(as.numeric(input$secPause) * 1000, session)
updates <<- as.numeric(updater())
})
autoControl <- reactive( {
if(updateTracker() <= 5)
secPause <<- input$secPause
else
secPause <<- 60*60
return(secPause)
})
output$distPlot <- renderPlot( {
if(updateTracker() <= 5) {
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist, main = paste("Histogram count =" , updateTracker()))
}
else {
updates <<- 0
hist(dist, main = paste("Last Histogram count =",
as.numeric(updater()), "with secPause =",
as.numeric(autoControl())))
}
})
}
))
You are getting the error because the hist distribution is defined inside the if clause, but you are using it (after 5 intervals) inside the else clause, where it is not defined. That's why it works for the first 5 intervals.
if(updateTracker() <= 5) {
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist, main = paste("Histogram count =" , updateTracker()))
}
else {
updates <<- 0
hist(dist, main = paste("Last Histogram count =",
as.numeric(updater()), "with secPause =",
as.numeric(autoControl())))
}
After I moved the dist to before the if condition, I got your cycling to work. (I also split your code into UI.R and server.R to make it more manageable.) Not pasting here since it is essentially the same code, but you can find the working version of code in this gist.