How to wrap a function around a reactive expression in R Shiny? - r

Minimal working example
Say I want to have a custom version of renderDataTable, which I shall name myRenderDataTable and works by wrapping around renderDataTable:
library(shiny)
runApp(list(
ui = basicPage(
actionButton("button", "Increase input"),
tabsetPanel(
tabPanel("table1", shiny::dataTableOutput("table1")),
tabPanel("table2", shiny::dataTableOutput("table2")),
tabPanel("table3", shiny::dataTableOutput("table3"))
)
),
server = function(input, output) {
myRenderDataTable <- function(a) {
renderDataTable(
data.frame(x = a, y = a^2, z = a^3),
options = list(bPaginate = as.logical(a %% 2))
)
}
output$table1 <- myRenderDataTable(input$button)
output$table2 <- myRenderDataTable(input$button + 1)
output$table3 <- myRenderDataTable(input$button + 2)
}
))
Issue
Unfortunately, it appears that myRenderDataTable is not reactive like renderDataTable. Clicking the Increase input button should cause the table values to change, but doesn't.
So what's going wrong?
Attempt: Passing calls to reactive:
Doing output$table1 <- reactive(myRenderDataTable(input$button))) leads to:
Error during wrapup: evaluation nested too deeply: infinite recursion / options(expressions=)?
Error : evaluation nested too deeply: infinite recursion / options(expressions=)?
Attempt: Passing calls to observe:
Doing observe(output$table1 <- myRenderDataTable(input$button)) had no effect on the issue

The problem is that input$button is evaluated "eagerly" - i.e. input$button + 1 evaluates to 2 to the first time it's run and then never changes again. You can make it evaluate every time input$button changes by explicitly making it a reactive:
library(shiny)
runApp(list(
ui = basicPage(
actionButton("button", "Increase input"),
tabsetPanel(
tabPanel("table1", shiny::dataTableOutput("table1")),
tabPanel("table2", shiny::dataTableOutput("table2")),
tabPanel("table3", shiny::dataTableOutput("table3"))
)
),
server = function(input, output) {
myRenderDataTable <- function(a) {
renderDataTable(
data.frame(x = a(), y = a()^2, z = a()^3),
options = list(bPaginate = as.logical(a() %% 2))
)
}
output$table1 <- myRenderDataTable(reactive(input$button))
output$table2 <- myRenderDataTable(reactive(input$button + 1))
output$table3 <- myRenderDataTable(reactive(input$button + 2))
}
))

I think you're underestimating how much magic goes in in the render* functions. From looking at this example, I don't think you want a custom renderDataTable function, I think you want a custom function to build a table, which you can then pass to the built in renderDataTable. I think this does what you want, the wrapping is just in the opposite order (ie, a custom funciton inside a reactive expression):
library(shiny)
runApp(list(
ui = basicPage(
actionButton("button", "Increase input"),
tabsetPanel(
tabPanel("table1", dataTableOutput("table1")),
tabPanel("table2", dataTableOutput("table2")),
tabPanel("table3", dataTableOutput("table3"))
)
),
server = function(input, output) {
myDataTable <- function(a) {
data.frame(x = a, y = a^2, z = a^3)
}
output$table1 <- renderDataTable(myDataTable(input$button))
output$table2 <- renderDataTable(myDataTable(input$button + 1))
output$table3 <- renderDataTable(myDataTable(input$button + 2))
}
))

Related

How to show a progressBar in a single function in shiny?

Here is an example. The progress bar just jumps from 0% to 100% due a single function getres(). How to indicate the progress smoothly?
library("shiny")
library("shinyWidgets")
library("DESeq2")
library("airway")
data(airway)
getres <- function(eset){
dds<-DESeqDataSet(eset, design = ~cell + dex)
keep <- rowSums(counts(dds)) >= 10
dds <- dds[keep,]
dds <- DESeq(dds)
res <- results(dds)
return(res)
}
ui <- fluidPage(
tags$h1("Progress bar in Sweet Alert"),
useSweetAlert(), # /!\ needed with 'progressSweetAlert'
actionButton(
inputId = "go",
label = "Launch long calculation !"
)
)
server <- function(input, output, session) {
observeEvent(input$go, {
progressSweetAlert(
session = session, id = "myprogress",
title = "Work in progress",
display_pct = TRUE, value = 0
)
for (i in seq_len(1)) {
Sys.sleep(0.1)
updateProgressBar(
session = session,
id = "myprogress",
res<-getres(airway),
value = i
)
}
closeSweetAlert(session = session)
sendSweetAlert(
session = session,
title =" Calculation completed !",
type = "success"
)
})
}
shinyApp(ui = ui, server = server)
I was unable to run your example as airway and DESeq2 are not available for R 3.6+. BUT there is an interesting package that I have been meaning to try out called waiter.
Within waiter there is waitress which will "let you display loading bars on the entire screen or specific elements only."
There is a great demo app where you play with the different functions.
Here is an example from the docs!
library(shiny)
library(waiter)
ui <- navbarPage(
"Waitress on nav",
tabPanel(
"home",
use_waitress(),
plotOutput("plot")
)
)
server <- function(input, output){
# now waitress ranges from 0 to 100
waitress <- Waitress$new("nav", theme = "overlay", min = 0, max = 10)
output$plot <- renderPlot({
for(i in 1:10){
waitress$inc(1) # increase by 10%
Sys.sleep(.5)
}
hist(runif(100))
waitress$close() # hide when done
})
}
shinyApp(ui, server)
Hope this helps or gives you other ideas!

Shiny in R: How can I fade-out my plotOutput from renderPlot if certain conditions are not met?

the question is straight forward. First, I tried an if-else condition within the render plot. Something like
if (input$Next > 0) {
plot(...)
}
else {
return()
}
This didn't work. The grey area at which the plot would be placed later was shown even though the condition wasn't met yet.
In a next step, I tried to use validate (see here). I basically copied the code from the given example. However, it still shows the grey area when the condition is actually not met. My current attempt looks as follows:
ui.R
shinyUI(fluidPage(
sidebarPanel(
plotOutput("test"),
actionButton("Next", "Next")
))
server.R
shinyServer(function(input, output, session) {
function(input, output) {
output$test <- renderPlot({
validate(
need(input$Next > 0)
)
pt <- plot(input$Next,2)
print(pt)
})
}
})
The plot function is just for illustration. Mine looks different. Any help is highly appreciated!
First possibility - conditionalPanel
We want to show the plotOutput if the actionButton was pressed. More specifically, if input.Next > 0. This condition is evaluated in javaScript, hence we have a slightly different syntax - instead of $ we use . after input and we use the parentheses.
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test")
)
However, it is quite strange that we multiply input.Next by one. It is necessary because input.Next, expect a number, returns also attributes. It seems that JavaScript doesn't know how to deal with this...but the multiplication does the trick.
[1] 0
attr(,"class")
[1] "integer" "shinyActionButtonValue"
In this example the plotOutput appears immediately...definitely too fast.
library(shiny)
ui1 <- shinyUI(fluidPage(
sidebarPanel(
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test")
),
actionButton("Next", "Next")
)
))
server1 <- shinyServer(function(input, output, session) {
output$test <- renderPlot({
pt <- plot(input$Next, 2)
print(input$Next)
print(pt)
})
})
shinyApp(ui1, server1)
"Slowing down the train"
In this example, we're going to "slow down" the speeding plotOutput. To do so we need the package shinyjs.
First, we're going to wrap the conditionalPanel into a div with an id, say, animation
div(id = "animation",
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test")
)
)
Then, on the server side, we're going to define the animation in the following way: conditional on the input$next the div should show up with the slide animation.
observe({
toggle(id = "animation", anim = TRUE, animType = "slide",
time = 0.5, condition = input$Next > 0)
})
Full example:
ui2 <- shinyUI(fluidPage(
# we need to include this function in order to use shinyjs functions
useShinyjs(),
sidebarPanel(
actionButton("Next", "Next"),
div(id = "animation",
conditionalPanel(
condition = "input.Next * 1 > 0",
plotOutput("test"),
sliderInput("manipulate", "slider", min = 0, max = 1, value = 1)
)
)
)
))
server2 <- shinyServer(function(input, output, session) {
# Introduce gently the div with an id = "animation" and its all content.
observe({
toggle(id = "animation", anim = TRUE, animType = "slide",
time = 0.5, condition = input$Next > 0)
})
# We could animate only the plotOutput with "toogle(id = test")"
# - it would work as well, but for the first time the plot is shown
# way we would get an errors with margins.
output$test <- renderPlot({
#plot(input$Next, 2)
ggplot(iris, aes(x = Species)) + geom_bar(alpha = input$manipulate)
})
})
shinyApp(ui2, server2)
renderUI
As you pointed out, the another possibility is to use the function renderUI. If you want to render more than one element at once, you have to wrap them into a list as in the example below:
library(shiny)
library(ggplot2)
ui3 <- shinyUI(fluidPage(
sidebarPanel(
uiOutput("dynamic"),
actionButton("Next", "Next")
)
))
server3 <- shinyServer(function(input, output, session) {
output$dynamic <- renderUI({
if (input$Next > 0) {
# if we want to render more element, we need the list
list(
plotOutput("test"),
sliderInput("manipulate", "slider", min = 0, max = 1, value = 1)
)
}
})
output$test <- renderPlot({
#plot(input$Next, 2)
ggplot(iris, aes(x = Species)) + geom_bar(alpha = input$manipulate)
})
})
shinyApp(ui3, server3)
Use a conditional panel like so:
library(shiny)
ui =fluidPage(
sidebarPanel(
conditionalPanel(condition="input.Next>0",
plotOutput("test")),
actionButton("Next", "Next")
))
server=shinyServer(function(input, output, session) {
output$test <- renderPlot({
req(input$Next > 0)
pt <- plot(input$Next,2)
print(pt)
})
})
shinyApp(ui=ui,server=server)

Using length of checkboxGroupInput as an input for a loop to create multiple elements

I'm creating Shiny app and I want to use checkboxGroupInput in order to print out multiple plots. However, I want to print out plots only for the elements of checkboxGroupInput that were checked. There is a similar example in Shiny gallery to create UI elements in a loop that uses lapply. Here is a simplified version of that example to show what I want to do:
#server.R
library(shiny)
library(ggplot2)
shinyServer(function(input, output, session) {
numberInput <- reactive({
input$checkbox
})
lapply(1:10, function(i) {
output[[paste0('b', i)]] <- renderPlot({
qplot(x = rnorm(100, mean = as.numeric(numberInput()[i]))) +
ggtitle(paste("This plot was plotted with", numberInput()[i], "option"))
})
})
})
#ui.R
library(shiny)
shinyUI(fluidPage(
title = 'lapply example',
sidebarLayout(
sidebarPanel(
checkboxGroupInput("checkbox", "Checkbox",
choices = sample(1:10, 5))
),
mainPanel(
lapply(1:10, function(i) {
plotOutput(paste0('b', i))
})
)
)
))
This works, but obviously when Shiny tries to extract numberInput()[i] where i is bigger than number of currently checked elements, there is nothing to extract and instead of a plot there is an error. Therefore I need to somehow tell lapply to iterate only n number of times where n is length(input$checkbox).
I tried to use length(input$checkbox) directly, tried putting that element in the numberInput() reactive statement and returning it as the list, I tried to use reactiveValues() in a following way:
v <- reactiveValues(n = length(input$checkbox))
lapply(1:isolate(v$n), function(i) {
However, in all of those instances Shiny complains about lack of active reactive context.
So, what am I missing? How can I use length of input in lapply outside of reactive context?
I've generally had more luck using this approach (only because it's easier for me to wrap my head around it), but the idea is to render your plots into a UI on the server and then render the UI in ui.R
#server.R
library(shiny)
library(ggplot2)
server <- shinyServer(function(input, output, session) {
output$checks <- renderText(input$checkbox)
output$plots <- renderUI({
plot_output_list <-
lapply(input$checkbox,
function(i){
plotOutput(paste0("plot", i))
})
do.call(tagList, plot_output_list)
})
observe({
for (i in input$checkbox) {
local({
local_i <- i
output[[paste0("plot", local_i)]] <-
renderPlot({
qplot(x = rnorm(100, mean = as.numeric(local_i))) +
ggtitle(paste("This plot was plotted with", local_i, "option"))
})
})
}
})
})
#ui.R
library(shiny)
ui <- shinyUI(fluidPage(
title = 'lapply example',
sidebarLayout(
sidebarPanel(
checkboxGroupInput("checkbox", "Checkbox",
choices = sample(1:10, 5))
),
mainPanel(
verbatimTextOutput("checks"),
uiOutput('plots')
)
)
))
shinyApp(ui = ui, server = server)

Measure execution speed inside of Shiny

I am working on developing a Shiny app.
I am interested in clocking the time it takes to execute certain chunks of code (such as a ggplot, etc).
For some reason it appears that using the usual clocking methods don't work within reactive calls, for example:
output$R1_C1 <- renderPlot({
beginning <- Sys.time()
<lots of code here>
end <- Sys.time()
print(end - beginning)
R complains and gives me
Error in (structure(function (input, output) :
object 'beginning' not found
Has anyone found a successful way to time execution speed inside of reactive calls in Shiny?
This works on my system:
library(shiny)
runApp(list(
ui = bootstrapPage(
numericInput('n', 'Number of obs', 100),
plotOutput('plot')
),
server = function(input, output) {
output$plot <- renderPlot({
beginning <- Sys.time()
h <- hist(runif(input$n))
end <- Sys.time()
print(end - beginning)
h
})
}
))
In a similar way, you can use the tictoc package as shown below. The reason I prefer this over the base R solution is because you can easily use multiple tic/toc tags to measure sub-routines within either a reactive or a rendering expression.
library(shiny)
library(tictoc)
runApp(list(
ui = bootstrapPage(
numericInput('n', 'Number of obs', 100),
plotOutput('plot')
),
server = function(input, output) {
output$plot <- renderPlot({
tic("execution time - Histogram")
hist(runif(input$n))
toc()
tic("execution time - Print")
print("this is a second task within 'renderPlot' ")
toc()
})
}
))
The profvis package might be useful. Example:
library(shiny)
library(profvis)
profvis({
sApp <- shinyApp(
ui = fluidPage(
numericInput('n', 'Number of obs', 100, min = 1, max = 200),
plotOutput('plot')
),
server = function(input, output) {
dfTable <- reactive({
as.data.frame(matrix(rnorm(10 * input$n, mean = 5), ncol = input$n))
})
vMeans <- reactive({
apply(dfTable(), 2, mean)
})
output$plot <- renderPlot({
hist(vMeans())
})
}
)
runApp(sApp)
})

R shiny: how to generate sliders in server.r using HTML

I would like to generate sliders in my server (because the number of sliders I need depend on other inputs). As you will see with the code herebelow and the picture, the sliders that appear do not look good. I presume it has to do with the way I specify them in HTML (maybe something to do with the style/css?).
Here is the code:
ui <- pageWithSidebar(
headerPanel("test"),
sidebarPanel(
helpText('these sliders do not look good:')
),
mainPanel(uiOutput('slider'))
)
server <- function(input,output, session){
output$slider <- renderTable({
inputs <- paste0("<input id='Sl_C", 1:2, "' class='jslider-pointer jslider-pointer-to' type = 'range' value='c(0,20)' min='0' max='100'>")
matrix <- data.frame(inputs)
},sanitize.text.function = function(x) x)
}
runApp(list(ui=ui,server=server))
Any advice/suggestion would be highly appreciated.
All the best
Here is one way to achieve multiple slider inputs.
library(shiny)
multiSliders = function(n, ...){
sliders = lapply(1:n, function(i){
sliderInput(paste0('slider-', i), paste('Slider', i), ...)
})
paste_all = function(...) paste(..., collapse = '\n')
HTML(do.call('paste_all', sliders))
}
runApp(list(
ui = pageWithSidebar(
headerPanel('Multiple Sliders'),
sidebarPanel(
sliderInput('slider-0', 'Slider0', 0, 10, 4),
multiSliders(2, 0, 10, 4)
),
mainPanel()
),
server = function(input, output){
}
))

Resources