I have a Shiny application that runs a long process and I would like to alert the user that the process is actually running. In the example below, I have a toggle switch that executes a block of code with a 1 second delay (my actual application runs for about 20 seconds), and I have an HTMLoutput box that should let the user know something is happening. But, since the underlying bootstrap process only updates the UI elements after the function exits, users only see the last message, "Done".
I've seen other questions like this one with answers that suggest creating a reactive value and then wrapping the renderUI() function in an observe() function (here, for example), but this has the same problem.
I also tried wrapping the htmlOutput() in withSpinner() from the shinycssloaders package, but I get an error saying "missing value where TRUE/FALSE needed". I assume this is coming from shinydashboardPlus because it doesn't like the withspinner() output in the tagList() elements. I was hopeful that this would at least give me an animated spinner on the HTMLoutput indicating that it is running.
Any input on getting this specific setup to work or alternatives to give users some feedback that the process is active is greatly appreciated.
library(shiny)
library(shinycssloaders)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
# Define UI for application that draws a histogram
ui <- dashboardPage(skin = 'blue',
shinydashboardPlus::dashboardHeader(title = 'Example',
leftUi = tagList(
switchInput(inputId = 'swtLabels', label = 'Labels', value = TRUE,
onLabel = 'Label 1', offLabel = 'Label 2',
onStatus = 'info', offStatus = 'info', size = 'mini',
handleWidth = 230),
htmlOutput(outputId = 'labelMessage')
#withSpinner(htmlOutput(outputId = 'labelMessage')) # leads to error noted in text
)
),
dashboardSidebar(),
dashboardBody()
)
server <- function(input, output) {
rv <- reactiveValues()
rv$labelMessage <- 'Start'
observeEvent(input$swtLabels, {
rv$labelMessage <- 'Updating labels...'
Sys.sleep(1)
rv$labelMessage <- 'Done'
})
output$labelMessage <- renderUI(HTML(rv$labelMessage))
}
# Run the application
shinyApp(ui = ui, server = server)
I found a workaround for this using the shinyjs package, code below. The take home message is that by using shinjs::html(), the effect on the htmlOutput is immediate. I even added a fancy fade out at the end to hide the message.
It does create yet another package dependency, but it solves the problem. I'm sure there is a way that one could write a small JavaScript function and add it to the Shiny application to accomplish this same result. Unfortunately, I don't know JavaScript. (References for including JS code in a Shiny app - JavaScript Events in Shiny, Add JavaScript and CSS in Shiny)
library(shiny)
library(shinycssloaders)
library(shinydashboard)
library(shinyjs)
library(shinydashboardPlus)
library(shinyWidgets)
# Define UI for application that draws a histogram
ui <- dashboardPage(skin = 'blue',
shinydashboardPlus::dashboardHeader(title = 'Example',
leftUi = tagList(
useShinyjs(),
switchInput(inputId = 'swtLabels', label = 'Labels', value = TRUE,
onLabel = 'Label 1', offLabel = 'Label 2',
onStatus = 'info', offStatus = 'info', size = 'mini',
handleWidth = 230),
htmlOutput(outputId = 'labelMessage')
#withSpinner(htmlOutput(outputId = 'labelMessage')) # leads to error noted in text
)
),
dashboardSidebar(),
dashboardBody()
)
server <- function(input, output) {
observeEvent(input$swtLabels, {
shinyjs::html(id = 'labelMessage', html = 'Starting...')
shinyjs::showElement(id = 'labelMessage')
Sys.sleep(1)
shinyjs::html(id = 'labelMessage', html = 'Done')
shinyjs::hideElement(id = 'labelMessage', anim = TRUE, animType = 'fade', time = 2.0)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Related
I have a shiny app for which I'd like to add a dark mode switch. I've managed to do that after some research online but as I was trying to implement this bit of code in my app I encountered issues. I have been trying several things on a simpler app to try to identify what causes the switch to not work and I came to the conclusion that adding a navbarPage to my app is what makes it crash.
Below is a simple example:
library(shiny)
library(DT)
library(shinyWidgets)
library(bslib)
ui <- fluidPage(
theme=bs_theme(),
# navbarPage("TITLE",
sidebarLayout(
sidebarPanel(
numericInput("num", label = h3("Numeric input"), value = 1),
materialSwitch(inputId = "mode", label = icon("moon"), right=TRUE,status = "success")),
mainPanel(verbatimTextOutput("value")) #fin main panel
)
# )
)
server <- function(input, output,session) {
observe({
if(input$mode==TRUE)
session$setCurrentTheme(bs_theme_update(theme, bootswatch = "superhero"))
if(input$mode==FALSE)
session$setCurrentTheme(bs_theme_update(theme, bootswatch = "default"))
})
output$value <- renderPrint({ input$num })
}
shinyApp(ui, server)
As long as the navbar line and corresponding ")" is considered as a comment it works fine but as soon as I uncomment these two lines I get this error:
Warning: Navigation containers expect a collection of `bslib::nav()`/`shiny::tabPanel()`s and/or `bslib::nav_menu()`/`shiny::navbarMenu()`s. Consider using `header` or `footer` if you wish to place content above (or below) every panel's contents.
Listening on http://--.--
Warning: Error in : session$setCurrentTheme() cannot be used to change the Bootstrap version from to 4. Try using `bs_theme(version = 4)` for initial theme.
47: stop
46: session$setCurrentTheme
45: observe [C:/Users/user/Documents/shiny/SHINY-APP/app4.R#23]
44: <observer>
1: runApp
I am very new to shiny despite being familiar with R but I really don't understand this error. I have been trying to find answers in the documentation associated to the different functions I'm using as well as in previously asked questions on SO but I haven't been able to fix this yet.
Any help would be greatly appreciated!
Thank you !
You have to specify the theme both in the fluidPage and the navbarPage (maybe you can get rid of fluidPage, didn't test it)
library(shiny)
library(DT)
library(shinyWidgets)
library(bslib)
ui <- fluidPage(
theme=bs_theme(version = 4, bootswatch = "default"),
navbarPage("TITLE",
theme=bs_theme(version = 4, bootswatch = "default"),
tabPanel("Tab",
sidebarLayout(
sidebarPanel(
numericInput("num", label = h3("Numeric input"), value = 1),
materialSwitch(inputId = "mode", label = icon("moon"),
right=TRUE,status = "success")),
mainPanel(verbatimTextOutput("value")) #fin main panel
)
)
)
)
server <- function(input, output,session) {
observe(session$setCurrentTheme(
if(isTRUE(input$mode)){
bs_theme(bootswatch = "superhero")
} else {
bs_theme(bootswatch = "default")
}
))
output$value <- renderPrint({ input$num })
}
shinyApp(ui, server)
I have the shiny dashboard below in which I want to use a variable from my pickerInput() and create a plot. The issue is that if I use ,for example name or snID instead of input$DB the plot is created. But when I use input$DB I get: Warning: Error in table: all arguments must have the same length
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(ggplot2)
library(plotly)
ui <- dashboardPage(
header = dashboardHeader(title = "My dashboard"),
sidebar = dashboardSidebar(
uiOutput("dbs")
),
body = dashboardBody(
plotlyOutput("fn")
)
)
server <- function(input, output, session) {
sts<-c("Rev","Rev")
sID<-c("123","124")
snID<-c("23","34")
name<-c("s","d")
pe<-data.frame(sts,sID,snID,name)
output$dbs<-renderUI({
pickerInput("DB", "Select Database/s",
choices = c("name","snID"),
multiple = F,options = list(`actions-box` = TRUE),
selected = "name")
})
output$fn<-renderPlotly({
#2.2 MAKING A TABLE for public.exists
tbl<-table(pe[[input$DB]], pe$sts)
ggplotly(
ggplot(as.data.frame(tbl), aes(!!sym(input$DB), Freq, fill = sts))
)
})
}
shinyApp(ui, server)
I suspect your output$fn reactive is executing before input$DB has a value. Therefore, add
req(input$DB)
at the start of the reactive, and you should be OK.
In the absence of any demo input data, it's difficult to be definitive.
I have a Shinydashboard with reactive Dygraph boxes. I successfully setup a reactive box title to display the maximum value in the dataset and I'd like to do the same for the Status option. Here's what I've got so far:
ui <- dashboardPage(
dashboardHeader(title = "Sites", disable = TRUE),
dashboardSidebar(
#collapsed = TRUE,
disable = TRUE,
sidebarMenu()
),
dashboardBody(
fluidRow(
box(title = textOutput('dyermax'), background = "black", status = textOutput('dyerStat'), dygraphOutput("plot1", height = 173))
)
)
)
The title works as expected but the status gives an error: status can only be "primary", "success", "info", "warning", or "danger".
server <- function(input, output, session) {
#reactivePoll code for importing CSV data (datap)
renderTable(datap())
#Plot1
output$plot1 <- renderDygraph({
dyersburgp <- xts(x = datap()$dyersburg, order.by = datap()$date)
dyersburgf <- xts(x = datap()$dyersburg.1, order.by = datap()$date)
dyersburgmain <- cbind(dyersburgf, dyersburgp)
output$dyermax <- renderPrint({
cat("Dyersburg (max:", max(dyersburgp, na.rm = TRUE),"ug/m3)")
})
dyersburgMx <- max(dyersburgp, na.rm = TRUE)
output$dyerStat <- renderPrint({
if(dyersburgMx >60)("danger" else "info")
})
dygraph(dyersburgmain)
})
}
shinyApp(ui, server)
I would prefer to use the Color option instead of the Status option, but adding "color = "red"" to the box doesn't change the color at all for some reason.
Background
This is actually a really good question. To my understanding, the reason textOutput doesn't work is that, by default, text is rendered within an HTML div. So instead of just passing the raw string ('danger', 'info', etc.), it is rendered as raw HTML. For example, if we inspect the textOutput element in our browser when we run the following,
output$my_text <- renderText({
'this is some text'
})
textOutput('my_text')
we can see it actually renders the below HTML, rather than just "this is some text".
<div id="my_text" class="shiny-text-output shiny-bound-output">this is some text</div>
Obviously this is for a very good reason, and enables us to make good-looking Shiny apps without having to worry about any HTML. But it means we have to be careful when passing outputs as arguments to UI functions.
Solution
There may be better ways to do this, but one way would be creating the HTML yourself by using renderUI/uiOutput, and using the HTML function in combination with paste0 to dynamically render out HTML string to be read directly by uiOutput (which is an alias for the more descriptive htmlOutput). This example changes the status of the box when the user changes the numericInput to above 60, and allows the user to change the title of the box as well. Extend this as required for your own project.
library(shiny)
library(shinydashboard)
body <-
dashboardBody(
fluidRow(
numericInput(
inputId = 'status_input',
label = 'numeric input',
value = 50),
textInput(
inputId = 'box_title',
label = 'box title',
value = ''),
uiOutput('my_box')
)
)
server <- function(input, output, session) {
# get box status as string representing html element
box_status <- reactive({
if (input$status_input > 60) {
'box-danger'
} else {
'box-info'
}
})
# get user input for box title
box_title <- reactive({
input$box_title
})
# generate html to display reactive box
output$my_box <- renderUI({
status <- box_status()
title <- box_title()
# generate the dynamic HTML string
HTML(paste0("
'
<div class=\"box box-solid ", status, "\">
<div class=\"box-header\">
<h3 class=\"box-title\">", title, "</h3>
</div>
<div class=\"box-body\">
Box content!
</div>
</div>
'"
))
})
}
shinyApp(ui = dashboardPage(dashboardHeader(), dashboardSidebar(),body), server)
I am writing this to seek some help in using plan(multiprocess) or plan(multicore) and killing long running processes in my shiny app. The app has multiple future events (long running processes) that run on clicking their corresponding actionButton. Below is an example app of future() command used within the server function in the app. And i have been using stopMulticoreFuture(fut) to kill the processes.
library(shiny)
library(shinydashboard)
library(promises)
plan(multicore)
library(ipc)
sidebar <- dashboardSidebar(width = 200, sidebarMenu(id = "tabs",
menuItem("File", tabName = "tab1", icon = icon("fas fa-file"))))
body <- tabItem(tabName = "tab1",h2("Input File"),
fluidRow(tabPanel(
"Upload file",
value = "upload_file",
fileInput(
inputId = "uploadFile",
label = "Upload Input file",
multiple = FALSE,
accept = c(".txt")
),
checkboxInput('header', label = 'Header', TRUE)
),
box(
title = "Filter X rows",
width = 7,
status = "info",
tabsetPanel(
id = "input_tab",
tabPanel(
"Parameters",
numericInput(
"nrows",
label = "Entire number of rows",
value = 5,
max = 10
),
actionButton("run", "Analyze"),
actionButton("cancel", "Cancel")
),
tabPanel(
"Results",
value = "results",
navbarPage(NULL,
tabPanel(
"Table", DT::dataTableOutput("res_table"),
icon = icon("table")
)),
downloadButton("downList", "Download")
)
)
)
))
ui <-
shinyUI(dashboardPage(
dashboardHeader(title = "TestApp", titleWidth = 150),
sidebar,dashboardBody(tabItems(body))
))
server <- function(input, output, session) {
file_rows <- reactiveVal()
observeEvent(input$run, {
prog <- Progress$new(session)
prog$set(message = "Analysis in progress",
detail = "This may take a while...",
value = NULL)
file_nrows <- reactive({
return(input$nrows)
})
file_nrows_value <- file_nrows()
file_input <- reactive({
return(input$uploadFile$datapath)
})
file_input_value <- file_input()
fut<- NULL
fut<<- future({system(paste(
"cat",
file_input_value,
"|",
paste0("head -", file_nrows_value) ,
">",
"out.txt"
))
head_rows <- read.delim("out.txt")
head_rows
}) %...>%
file_rows() %>%
finally(~prog$close())
})
observeEvent(file_rows(), {
updateTabsetPanel(session, "input_tab", "results")
output$res_table <-
DT::renderDataTable(DT::datatable(
file_rows(),
options = list(
searching = TRUE,
pageLength = 10,
rownames(NULL),
scrollX = T
)
))
})
output$downList <- downloadHandler(
filename = function() {
paste0("output", ".txt")
}, content = function(file) {
write.table(file_rows(), file, row.names = FALSE)
}
)
observeEvent(input$cancel,{
stopMulticoreFuture(fut)
})
}
shinyApp(ui = ui, server = server)
When i click "Cancel" button, the UI gets disabled but the console shows the below warning and the command still gets executed in the console.
Warning: Error in stopMulticoreFuture: stopMulticoreFuture only works on multicore futures
Since this example represents a quick running process the future() command gets executed before clicking Cancel.
In real case, even after clicking “Cancel” the command inside the future (long process) still runs in the console after the warning while the UI is already disabled.
The app is currently run on MAC with 4 cores. How could i kill the process running in the console rather just getting the UI disabled?
I am currently testing my app and would be great to have expert input in planning multiprocess/multicore and killing the processes to make the app efficient for running async processes among parallel users. The final app will be running on Ubuntu machine with 4 virtual CPUs.
A couple problems here:
You are missing library(promises), plan(multicore) and library(ipc).
fut is not a future, it is a promise because of the %...>%, so stopMulticoreFuture won't work on it.
The ObserveEvent expression needs to return something other than the promise, otherwise your UI will block.
Since stopMulticoreFuture just kills the process, I can't assure you that it will work with system calls that create subprocesses. You may need to figure out the pid values for these and kill them yourself.
I would ask. Does Shiny do like always-refreshing the code after input ?
First I code this in ui :
box( ##title="Quality Attributes",
selectInput("att_ViewChart", width = '100%',label="Quality Attributes",
##multiple = TRUE,
choices=list(
"-",
"Suitability",
"Security",
)
)
),
dataTableOutput("tabelstatus")
Then I code this in server :
server = function(input, output) {
withProgress(message = "AAAAA",{
DateStatus_Sui<-c(1,2,3,4,NA,5,6,NA,7)
TimeStatus_Sui<-c(11,22,33,44,NA,55,66,NA,88)
status_Sui<-c(11,22,44,55,66,77,88)
jumlah<-7
})
if(input$att_ViewChart=="Suitability"){
Date<-DateStatus_Sui[!is.na(DateStatus_Sui)]
Time<-TimeStatus_Sui[!is.na(TimeStatus_Sui)]
Status<-status_Sui
Observation<-1:jumlah
#output
tabelstatus<-data.frame(Observation,Date,Time,Status)
output$tabelstatus<-renderDataTable(tabelstatus)
}
I hope when I run the app. Shiny will process the code (shown by progress bar 'AAAAA') And after that, if I choose Suitability it will do a little more process and then show the table . But I found that the progress bar appears again. Seems to me it re-runs the code from the beginning. How to fix this? Thank you
In the abscence of a fully reproducible example, I'm guessing this is what you're trying to do (i.e, make the table reactive according to your input$att_ViewChart):
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
box( selectInput("att_ViewChart", width = '100%',label="Quality Attributes",
choices=c("-","Suitability","Security"))),
dataTableOutput("tablestatus")
)
)
server = function(input, output) {
withProgress(message = "AAAAA",{
DateStatus_Sui<-c(1,2,3,4,NA,5,6,NA,7)
TimeStatus_Sui<-c(11,22,33,44,NA,55,66,NA,88)
status_Sui<-c(11,22,44,55,66,77,88)
jumlah<-7
})
## make your table reactive on `input$att_ViewChart`
output$tablestatus <- renderDataTable({
if(input$att_ViewChart=="Suitability"){
Date<-DateStatus_Sui[!is.na(DateStatus_Sui)]
Time<-TimeStatus_Sui[!is.na(TimeStatus_Sui)]
Status<-status_Sui
Observation<-1:jumlah
tablestatus <- data.frame(Observation,Date,Time,Status)
}else{
tablestatus <-data.frame()
}
return(tablestatus)
})
}
shinyApp(ui = ui, server = server)