How to make textOutput faster? - r

I'm trying to create an app that just adds text to the mainPanel. However, the text output is very slow when adding text.
I'd like to make this instant and fast instead of it taking so much time. Is there a way to make it be processed in the browser instead of going to R?
Code
library(shiny)
ui <- fluidPage(sidebarLayout(
sidebarPanel(textInput("text", label = NULL)),
mainPanel(textOutput("textout"))
))
server <- function(input, output, session) {
output$textout <- renderText({
input$text
})
}
shinyApp(ui, server)

This is due to the way the inputs are used by shiny. In the javascript it has an option with a 'debounce' of 250ms which explains why it only updates after you stop typing for a quarter of a second.
You can override this but it seems to involve writing a replacement for textInput. The crucial bit is the getRatePolicy function in the javascript.
library(shiny)
library(shinyCustom)
textinput_script <- "
<script>
var customTextInputBinding = $.extend({}, Shiny.inputBindings.bindingNames['shiny.textInput'].binding, {
find: function(scope) {
return $(scope).find('input.customTextInput');
},
subscribe: function(el, callback) {
$(el).on('keyup.customTextInputBinding input.customTextInputBinding', function(event) {
callback();
});
$(el).on('focusout.customTextInputBinding', function(event) { // on losing focus
callback();
});
},
unsubscribe: function(el) {
$(el).off('.customTextInputBinding');
},
getRatePolicy: function() {
return {
policy: 'direct'
};
}
});
Shiny.inputBindings.register(customTextInputBinding, 'shiny.customTextInput');
</script>
"
ui <- fluidPage(sidebarLayout(
sidebarPanel(
HTML(textinput_script),
customTextInput("text", label = NULL)
),
mainPanel(textOutput("textout"))
))
server <- function(input, output, session) {
output$textout <- renderText({
input$text
})
}
shinyApp(ui, server)
This is cannibalized from here, and the actual guide for doing this in general is here.

This approach does not go after the cause of the delay (like #pseudospins great answer) but rather uses a crutch. It is a shiny-only solution which is why it may still be of some interest.
It uses the "greedier" reactivePoll. The check function always yields TRUE to make sure that input$text gets evaluated after every polling interval no matter what. The argument intervalMillis can be changed to fit the requirements of the app. Here it is set to 1/10 of a second.
Please note, that this approach is simple and effective but it can make your app very slow if you overdo it. The check argument may be a chance to save resources. You can try storing the length of the last string and compare it to the new state every after every intervalMillis interval. In this simple example, though, I doubt that will help much.
library(shiny)
ui <- fluidPage(sidebarLayout(
sidebarPanel(textInput("text", label = NULL)),
mainPanel(textOutput("textout"))
))
server <- function(input, output, session) {
text <- reactivePoll(intervalMillis = 100, session,
checkFunc = function() TRUE,
valueFunc = function () input$text
)
output$textout <- renderText({
text()
})
}
shinyApp(ui, server)

Related

Reactive monitoring of a file in Shiny

I have an app where I need to monitor a file for changes. However I am struggling to do this.
Consider the following example:
library(shiny)
A function that returns the modified date of a file:
file_info <- function(){
if(file.exists("example.txt")){
return(as.character(as.Date(file.info("example.txt")[1,4])))
} else {
return("File not present")
}
}
Shiny part:
ui <- shinyUI(fluidPage(
textOutput("file_status"),
actionButton("create_file", "Create file"),
actionButton("delete_file", "Delete file")
))
server <- shinyServer(function(input, output, session) {
file_st <- reactive(file_info()) #what is the correct approach here?
output$file_status <- renderText({
file_st()
})
observeEvent(input$create_file,{
file.create("example.txt")
})
observeEvent(input$delete_file, {
unlink("example.txt")
})
})
I would like the file_status text field to update each time the example.txt file changes - if possible even if this happens outside of the app.
I have tried various combinations of reactive, observe and reactiveValues without finding the adequate combo.
Thank you
I was able to solve this using reactivePoll which is practically tailored for this type of task
server <- shinyServer(function(input, output, session) {
file_st <- reactivePoll(500, session,
checkFunc = function() {
if (file.exists("example.txt"))
file.info("example.txt")$mtime[1]
else
""},
valueFunc = function(){
if(file.exists("example.txt")){
return(as.character(as.Date(file.info("example.txt")[1,4])))
} else {
return("File not present")
}
}
)
output$file_status <- renderText({
file_st()
})
observeEvent(input$create_file,{
file.create("example.txt")
})
observeEvent(input$delete_file, {
unlink("example.txt")
})
})
ui <- shinyUI(fluidPage(
textOutput("file_status"),
actionButton("create_file", "Create file"),
actionButton("delete_file", "Delete file")
))

How to use callr::r_bg within a downloadHandler in a Shiny App

The scenario I'm emulating with the below minimal example is allowing a user to engage with a Shiny App (click the numericInput control and see server-side events occur) while a long-running download is occurring (simulated with Sys.sleep(10) within downloadHandler).
In a synchronous setting, when the "Download" button is clicked, the user can still interact with UI elements, but other Shiny calculations (in this case, renderText), get put in a queue. I'd like the asynchronous setting, where the download occurs in the background, and users can still interact with the UI elements and get desired output (e.g. renderText).
I'm using callr::r_bg() to achieve asynchronicity within Shiny, but the issue is that my current code of the downloadHandler is incorrect (mtcars should be getting downloaded, but the code is unable to complete the download, 404 error message), I believe it's due to the specific way in which downloadHandler expects the content() function to be written, and the way I've written callr::r_bg() is not playing nicely with that. Any insights would be appreciated!
Reference:
https://www.r-bloggers.com/2020/04/asynchronous-background-execution-in-shiny-using-callr/
Minimal Example:
library(shiny)
ui <- fluidPage(
downloadButton("download", "Download"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text")
)
server <- function(input, output, session) {
long_download <- function(file) {
Sys.sleep(10)
write.csv(mtcars, file)
}
output$download <- downloadHandler(
filename = "data.csv",
content = function(file) {
x <- callr::r_bg(
func = long_download,
args = list(file)
)
return(x)
}
)
observeEvent(input$count, {
output$text <- renderText({
paste(input$count)
})
})
}
shinyApp(ui, server)
I figured out a solution, and learned the following things:
Because downloadHandler doesn't have a traditional input$X, it can be difficult to include reactivity in the traditional way. The workaround was to present the UI as a hidden downlodButton masked by an actionButton which the user would see. Reactivity was facilitated in the following process: user clicks actionButton -> reactive updates -> when the reactive finishes (reactive()$is_alive() == FALSE), use shinyjs::click to initiate the downloadHandler
Instead of placing the callr function within the downloadHandler, I kept the file within the content arg. There seems to be some difficulties with scoping because the file needs to be available within the content function environment
I'm using a reactive function to track when the background job (the long-running computation) is finished to initiate the download using the syntax: reactive()$is_alive()
The invalidateLater() and toggling of a global variable (download_once) is important to prevent the reactive from constantly activating. Without it, what will happen is your browser will continually download files ad infinitum -- this behavior is scary and will appear virus-like to your Shiny app users!
Note that setting global variables is not a best practice for Shiny apps (will think of a better implementation)
Code Solution:
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
download_once <- TRUE
#output that tracks progress of background task
check <- reactive({
invalidateLater(millis = 1000, session = session)
if (long_run()$is_alive()) {
x <- "Job running in background"
} else {
x <- "Async job in background completed"
if(isTRUE(download_once)) {
shinyjs::click("download")
download_once <<- FALSE
}
invalidateLater(millis = 1, session = session)
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)
Thanks #latlio for your great answer. I think it cloud be easily improved.
invalidateLater should be used very carefully and only WHEN needed. I use invalidateLater only once and moved it to the logical part where we are waiting for the result. Thus we are NOT invalidating the reactivity infinitely.
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
#output that tracks progress of background task
check <- reactive({
if (long_run()$is_alive()) {
x <- "Job running in background"
invalidateLater(millis = 1000, session = session)
} else {
x <- "Async job in background completed"
shinyjs::click("download")
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)

Shiny promises future is not working on eventReactive

I have an inputButton than when you click it, 2 querys to mysql database are done. One is a heavy one (more than 10 secs) and the other is light (less than 0.01sec to get data).
As I want to show the result of this querys on shiny app, I have intendeed to use Promises and Future packages for asyncronous loading.
In the example that I show you of my code, I have simulated the SQL querys with the function heavyFunction, which is intended to simulate the heavy query and the ligth one time loads.
The issue is that this code is not working for me, because the results of the light query are not shown till the heavy query is done.
Note: In the Rstudio console, this code works perfect...
library(future)
library(promises)
plan(multiprocess)
heavyFunction <- function(n){
Sys.sleep(n)
print(n)
}
ui <- fluidPage(
actionButton("go","Show the data"),
textOutput("result0sec"),
textOutput("result10sec")
)
server <- function(input,output,session){
data0 <- eventReactive(input$go,{
heavyFunction(0)
})
data10 <- eventReactive(input$go,{
heavyFunction(10)
})
output$result0sec <- renderText({
data <- data0()
future(data)%...>%print()
})
output$result10sec <- renderText({
data <- data10()
print(data)
})
}
shinyApp(ui,server)
What I'm doing wrong?
Welcome to SO!
This thread discusses the same issue.
Please also see the detailed answer from Joe Cheng on GitHub.
The main problem you are experiencing is reflected by his following statement:
The goal, at least for this release of Shiny, is not to allow this
kind of intra-session responsiveness, but rather, inter-session; i.e.,
running an async operation won't make its owning session more
responsive, but rather will allow other sessions to be more
responsive.
However, there are ways to work around this behaviour by running the future in a background R process with e.g. library(callr) or more convenient library(future.callr) and it's plan(callr).
Here is a working version of your code:
library(future)
library(promises)
library(future.callr)
plan(callr)
heavyFunction <- function(n) {
Sys.sleep(n)
print(n)
}
ui <- fluidPage(
br(),
actionButton("go", "Show the data"),
br(), br(),
textOutput("result0sec"),
textOutput("result10sec")
)
server <- function(input, output, session) {
futureData <- reactiveValues(data10 = NULL)
data0 <- eventReactive(input$go, {
heavyFunction(0)
})
observeEvent(input$go, {
myFuture <- future({
heavyFunction(5)
})
then(
myFuture,
onFulfilled = function(value) {
futureData$data10 <<- value
},
onRejected = NULL
)
return(NULL)
})
output$result0sec <- renderText({
data0()
})
output$result10sec <- renderText({
req(futureData$data10)
})
}
shinyApp(ui, server)
The most important point here is to realize, that you shouldn't return your future directly, otherwise it will block all other actions - the observer returns nothing, it only has the side-effect of triggering the callback-function.

Register repeated keyboard presses in shiny

I'm trying to build an application where keyboard presses are captured, however I've run into the issue that if the same keyboard presses are used successively then shiny doesn't seem to register the input. Wondering if there is a way around this.
E.g., here's what I mean.
library(shiny)
runApp( list(ui = bootstrapPage(
verbatimTextOutput("results"),
tags$script('
$(document).on("keydown", function (e) {
Shiny.onInputChange("down", e.which);
});'),
tags$script('
$(document).on("keyup", function (e) {
Shiny.onInputChange("up", e.which);
});')
)
, server = function(input, output, session) {
output$results = renderPrint({
print(rnorm(1))
c(input$down, input$up)
})
}
))
Typing/releasing different characters on the keyboard generates new input, hence the random number generator is called when these events occur. But typing, say, 'g' 'g' 'g' only registers the first up-down keystrokes and ignores the rest.
Shiny.onInputChange only reacts when the js object it references changes, but the way you've written your js the value is being reassigned to the same thing with multiple presses of the same key.
An ugly workaround might be something like making the variable be the key presses and a random float in an array.
library(shiny)
runApp( list(ui = bootstrapPage(
verbatimTextOutput("results"),
tags$script('
$(document).on("keydown", function (e) {
Shiny.onInputChange("down", [e.which,Math.random()]);
});'),
tags$script('
$(document).on("keyup", function (e) {
Shiny.onInputChange("up", [e.which,Math.random()]);
});')
)
, server = function(input, output, session) {
output$results = renderPrint({
print(rnorm(1))
c(input$down[1], input$up[1])
})
}
))
It might be more efficient to eliminate the need to generate random numbers, and pass some potentially useful information back. For example using the timestamp property you could implement some filtering on the key presses.
library(shiny)
runApp( list(ui = bootstrapPage(
verbatimTextOutput("results"),
tags$script('
$(document).on("keydown", function (e) {
Shiny.onInputChange("down", [e.which,e.timeStamp]);
});'),
tags$script('
$(document).on("keyup", function (e) {
Shiny.onInputChange("up", [e.which,e.timeStamp]);
});')
)
, server = function(input, output, session) {
output$results = renderPrint({
print(rnorm(1))
c(input$down[1], input$up[1])
})
}
))

Possible to show console messages (written with `message`) in a shiny ui?

I don't understand R's message vs cat vs print vs etc. too deeply, but I'm wondering if it's possible to capture messages and show them in a shiny app?
Example: the following app can capture cat statements (and print statements as well) but not message statements
runApp(shinyApp(
ui = fluidPage(
textOutput("test")
),
server = function(input,output, session) {
output$test <- renderPrint({
cat("test cat")
message("test message")
})
}
))
Cross post from the shiny-discuss Google group since I got 0 answers.
Yihui suggested I use withCallingHandlers, and that indeed let me to a solution. I wasn't quite sure how to use that function in a way that would do exactly what I needed because my problem was that I had a function that printed out several messages one at a time and using a naive approach only printed the last message. Here is the my first attempt (which works if you only have one message to show):
foo <- function() {
message("one")
message("two")
}
runApp(shinyApp(
ui = fluidPage(
actionButton("btn","Click me"),
textOutput("text")
),
server = function(input,output, session) {
observeEvent(input$btn, {
withCallingHandlers(
foo(),
message = function(m) output$text <- renderPrint(m$message)
)
})
}
))
Notice how only two\n gets outputted. So my final solution was to use the html function from shinyjs package (disclaimer: I wrote that package), which lets me change or append to the HTML inside an element. It worked perfectly - now both messages got printed out in real-time.
foo <- function() {
message("one")
Sys.sleep(0.5)
message("two")
}
runApp(shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
actionButton("btn","Click me"),
textOutput("text")
),
server = function(input,output, session) {
observeEvent(input$btn, {
withCallingHandlers({
shinyjs::html("text", "")
foo()
},
message = function(m) {
shinyjs::html(id = "text", html = m$message, add = TRUE)
})
})
}
))
I know this isn't nearly as elegant, but I worked around a bit similar problem using capture.output; sadly sink doesn't allow simultaneous capture of messages and output though. You don't get them in the original order, but you can extract both streams at least (here turned to HTML):
runApp(shinyApp(
ui = fluidPage(
uiOutput("test")
),
server = function(input,output, session) {
output$test <- renderUI({
HTML(
paste(capture.output(type = "message", expr = {
message(capture.output(type = "output", expr = {
cat("test cat<br>")
message("test message")
cat("test cat2<br>")
message("test message2")
}))
}), collapse="<br>")
)})
})
)
Output:
test message
test message2
test cat
test cat2
Perhaps in the case if user wants to capture both but also separate them, this will provide a handy work-around. (Your shinyjs package seems neat, need to take a look at it!)
This can now be done with the high-level function shinyCatch from the spsComps package.
Basic usage
library(shiny)
library(spsComps)
ui <- fluidPage(
actionButton("msg", "msg"),
actionButton("warn", "warn"),
actionButton("err", "err"),
)
server <- function(input, output, session) {
observeEvent(input$msg, {
shinyCatch({message("a message")}, prefix = '')
})
observeEvent(input$warn, {
shinyCatch({warning("a warning")}, prefix = '')
})
observeEvent(input$err, {
shinyCatch({stop("an error")}, prefix = '')
})
}
shinyApp(ui, server)
Choose blocking level
If exceptions happened, we can choose to continue the code or block downstream code in the reactive context. For example, we want to stop downstream code if an error/warning/message happens:
library(shiny)
library(spsComps)
ui <- fluidPage(
actionButton("err", "code not blocked after error"),
actionButton("err_block", "code blocked after error"),
)
server <- function(input, output, session) {
observeEvent(input$err, {
shinyCatch({stop("an error")}, prefix = '')
print("error does not block ")
})
observeEvent(input$err_block, {
shinyCatch({stop("an error")}, prefix = '', blocking_level = "error")
print("you can't see me if error happens")
})
}
shinyApp(ui, server)
More advanced use
check website and demo
cat and print
There is still no good method to catch real-time info from cat and print. I will come back to update this answer if I found a fix for this.

Resources