Using withSpinner with an interactive uiOutput in R Shiny - r

I have recently written a Shiny app that takes user data input, does some analysis on it, and then displays the results, including graphs. These graphs take a while to render, so I am using withSpinner to inform the users that Shiny is busy and to be patient and wait for the graphs to appear. The graphs are displayed within boxes that have titles informing the users what the graphs show.
What gets displayed to the users depends on the data they provide to the app (how many items of data are provided in their input file) and also which options they choose from within the app (using checkboxes).
The withSpinner function works well for the graphs when wrapped around plotOutput and called from within ui (see line 38 of the example code below).
However, to use this approach for all graphs would require me to know how many items of data the users are likely to provide and then want to view. I would like to just automatically produce a graph, with a spinner, for each data item, without knowing how many there are in advance.
Placing withSpinner within the server doesn’t work at all (lines 58-65), which makes sense. However, if I use it in the ui around the uiOutput object for all of the boxes and graphs (line 29), the spinner only shows until the boxes are rendered – the graphs then appear about a minute later…
Please can you help me to work out how to get the spinners to show until the graphs are rendered? Thank you for any help you can give!
library(shiny)
library(shinydashboard)
library(shinyjs)
library(shinycssloaders)
library(survival)
ui <- dashboardPage(
skin = "blue",
dashboardHeader(title = "My App"),
dashboardSidebar(
sidebarMenu(
useShinyjs(),
id = "tabs",
menuItem("User Choice", tabName = "uChoice", icon = icon("sliders-h"))
)
),
dashboardBody(
id = "dashboardBody",
tabItems(
tabItem(
tabName = "uChoice",
h2("You have a choice"),
# Check boxes to select choice
fluidRow(
uiOutput("userChoiceCheckbox")
),
fluidRow(
# Only show the data graphs that the user has chosen to look at
withSpinner(uiOutput('chosenGraphs'), type=4)
# this spinner only shows until the box containing the graph is rendered
),
fluidRow(
# Always show lung graph
box(
title = paste("Here's the lung graph"),
width = 12,
height="50px",
withSpinner(plotOutput("lungGraph"), type=4)
# This spinner shows until the graph is plotted
)
)
)
)
)
)
server <- function(input, output, session) {
output$userChoiceCheckbox <- renderUI({
column(6, checkboxGroupInput(inputId = "choices", label = "Which graph(s) would you like to view?", choices = c("Lung", "PBC")))
})
output$chosenGraphs <- renderUI({
lapply(input$choices, function(x) {
box(
title = paste("Graph for", x,"cancer"),
width = 12,
renderPlot({
withSpinner(
# This spinner doesn't seem to work at all
plotOutput({
Sys.sleep(2)
plot(survfit(Surv(time, status) ~ 1, data = eval(as.symbol(tolower(x)))),
xlab = "Days",
ylab = "Overall survival probability")
})
)
})
)
})
})
output$lungGraph <- renderPlot(
plot(survfit(Surv(time, status) ~ 1, data = lung),
xlab = "Days",
ylab = "Overall survival probability")
)
}
shinyApp(ui, server)

In case you didn't find an answer, I couldn't add a single spinner per plot but the whole renderUI region can be wrapped by withSpinner() if you add it after the lapply().
In your case it would be something like this:
output$chosenGraphs <- renderUI({
lapply(input$choices, function(x) {
box(
title = paste("Graph for", x,"cancer"),
width = 12,
renderPlot({
plotOutput({
Sys.sleep(2)
plot(survfit(Surv(time, status) ~ 1, data = eval(as.symbol(tolower(x)))),
xlab = "Days",
ylab = "Overall survival probability")
})
})
)
}) %>% withSpinner()
})

Related

Can I maintain input defaults across different instances of a Shiny App Module?

I wanted to see if it was possible to take the inputs from one instance of a Shiny App module and apply them as the default inputs for a separate instance of the same module on a different tab. I'm struggling with the right way to ask this question, but I have tried to create a reproducible example below. I have a shiny dashboard with ~5 tabs, each calling the same plotting module.
For example, in the code below I've created a simplified dashboard that generates a plot. If someone clicks to 'Tab Page 1" and changes the plot color to "deeppink", is it possible to now set that input as the default color option when the user clicks to "Tab Page 2"? Or will the user always have to re-select the color input?
I originally used tabs/modules intending to have independence among tabs, but received feedback from a colleague that it would help users navigate my app if they did not have to re-select all the input options as they switch tabs.
I found Some Examples of Getting Modules to communicate with each other, (also here), but have not found a solution that really addresses this issue.
Additional Context: My full app will have a different tab for each of 5 geographic locations. Each location will allow users to select a survey that was completed as well as a species to investigate data trends. So if a user (on tab 1) selects a survey and a species, it would be nice to have these as the first options selected when the user switches to tab 2 (new geographic region). In this way, the user could more quickly compare similar plots among geographic regions.
library(shiny)
library(shinydashboard)
# Module UI for simple plot
dataPlotUI <- function(id) {
ns <- NS(id) # create namespace for entered ID
fluidRow(
box(
plotOutput(ns("plot.1"), height = 400)
),
box(
selectInput(
ns("color.choice"), "Color:",
c("darkcyan", "darkolivegreen", "deeppink", "lightsalmon2", "slateblue2", "springgreen3")
),
sliderInput(ns("range"), label = "Range", min = 10, max = 100, value = 50)
) # end box
)
}
########################################
########################################
# Module for Server
#
serverModule <- function(input, output, session, site) {
output$plot.1 <- renderPlot({
x <- seq(1, input$range, 1) # use slider to set max of x
y <- x + rnorm(length(x), 0, 3)
par(mai = c(.6, .6, .1, .1), las = 1, bty = "l")
plot(y ~ x, pch = 20, col = input$color.choice)
})
}
########################################
########################################
# UI
ui <- dashboardPage(
# # Dashboard Header
dashboardHeader(title = "Menu"),
#
dashboardSidebar(
sidebarMenu(
id = "sidebar",
# Icons can be found: https://fontawesome.com/icons?d=gallery&m=free
menuItem("Tab Page 1", tabName = "tabA"),
menuItem("Tab Page 2", tabName = "tabB"),
menuItem("Tab Page 3", tabName = "tabC")
)
), # End Dashboard Sidebar
# Body of the dashboard
dashboardBody(
# Start with overall tabItems
tabItems(
tabItem(
tabName = "tabA",
dataPlotUI("tab_one")
),
#
tabItem(
tabName = "tabB",
dataPlotUI("tab_two")
),
tabItem(
tabName = "tabC",
dataPlotUI("tab_three")
)
)
) # end dashboard body
)
#######################################
#######################################
# Server
# Call modules
server <- function(input, output, session) {
callModule(serverModule, "tab_one")
callModule(serverModule, "tab_two")
callModule(serverModule, "tab_three")
}
shinyApp(ui = ui, server = server)
Yes, it is possible. Here's one way of doing it.
The important concepts are that
Modules can return a value (or values).
The main server can monitor the values returned by modules.
Modules can react to changes in other modules via arguments to their server functions. (Or via session$userData: the approach I've taken.)
I think you knew that last one as you have a site argument in the module server, although you don't seem to use it.
So, taking each step in turn...
Allow the module to server to return a value
Add the following lines at the end of the module server function
rv <- reactive({input$color.choice})
return(rv)
This creates a reactive and returns it. Note that you return the reactive itself, not the reactive's value.
Monitor the modules' return values in the main server
Modify the callModule calls to
tab1 <- callModule(serverModule, "tab_one")
tab2 <- callModule(serverModule, "tab_two")
tab3 <- callModule(serverModule, "tab_three")
All I've done here is assign the modules' return values to local variables in the main server function. They're reactives, so we can monitor them. Add the following lines to the main server function:
session$userData$settings <- reactiveValues(chosenColour=NA)
observeEvent(tab1(), {session$userData$settings$chosenColour <- tab1()})
observeEvent(tab2(), {session$userData$settings$chosenColour <- tab2()})
observeEvent(tab3(), {session$userData$settings$chosenColour <- tab3()})
You can put print calls inside each observeEvent to see what's going on. I did that whilst testing. I think session$userData is a much underused feature of shiny. Unsurprisingly, it's a section of the session object that's writable by the user. The main server function and all module server functions share the same session$userData object, so it's a neat way of passing information between modules.
I've assumed that you'll want to do more than just change the colour of the dots in your real world case, so I've created a settings object. I've made it reactive so that modules can react to changes in it.
Make the modules react to changes
Add the following code to the module server function
observeEvent(
session$userData$settings$chosenColour,
{
if (!is.na(session$userData$settings$chosenColour))
updateSelectInput(
session,
"color.choice",
selected=session$userData$settings$chosenColour
)
}
)
[Again, put print calls in the observeEvent to check what's going on.]
And that's it.
As an aside, it's good practice always to add
ns <- session$ns
as the first line of your module server function. You don't need it right now, but it's likely you will. I've spent many hours chasing down a bug that's been due to "not needing" session$ns. Now I just do it by default to save the pain.
Here's the full listing of your modified MWE.
library(shiny)
library(shinydashboard)
dataPlotUI <- function(id) {
ns <- NS(id) # create namespace for entered ID
fluidRow(
box(plotOutput(ns("plot.1"), height = 400)),
box(
selectInput(
ns("color.choice"), "Color:",
c("darkcyan", "darkolivegreen", "deeppink", "lightsalmon2", "slateblue2", "springgreen3")
),
sliderInput(ns("range"), label = "Range", min = 10, max = 100, value = 50)
) # end box
)
}
# Module for Server
serverModule <- function(input, output, session, site) {
ns <- session$ns
output$plot.1 <- renderPlot({
x <- seq(1, input$range, 1) # use slider to set max of x
y <- x + rnorm(length(x), 0, 3)
par(mai = c(.6, .6, .1, .1), las = 1, bty = "l")
plot(y ~ x, pch = 20, col = input$color.choice)
})
observeEvent(session$userData$settings$chosenColour, {
if (!is.na(session$userData$settings$chosenColour)) updateSelectInput(session, "color.choice", selected=session$userData$settings$chosenColour)
})
rv <- reactive({input$color.choice})
return(rv)
}
# UI
ui <- dashboardPage(
dashboardHeader(title = "Menu"),
dashboardSidebar(
sidebarMenu(
id = "sidebar",
# Icons can be found: https://fontawesome.com/icons?d=gallery&m=free
menuItem("Tab Page 1", tabName = "tabA"),
menuItem("Tab Page 2", tabName = "tabB"),
menuItem("Tab Page 3", tabName = "tabC")
)
), # End Dashboard Sidebar
dashboardBody(
# Start with overall tabItems
tabItems(
tabItem(
tabName = "tabA",
dataPlotUI("tab_one")
),
#
tabItem(
tabName = "tabB",
dataPlotUI("tab_two")
),
tabItem(
tabName = "tabC",
dataPlotUI("tab_three")
)
)
) # end dashboard body
)
# Server
server <- function(input, output, session) {
session$userData$settings <- reactiveValues(chosenColour=NA)
tab1 <- callModule(serverModule, "tab_one")
tab2 <- callModule(serverModule, "tab_two")
tab3 <- callModule(serverModule, "tab_three")
# Module observers
observeEvent(tab1(), {session$userData$settings$chosenColour <- tab1()})
observeEvent(tab2(), {session$userData$settings$chosenColour <- tab2()})
observeEvent(tab3(), {session$userData$settings$chosenColour <- tab3()})
}
shinyApp(ui = ui, server = server)

Removing UI drop-down element dynamically in R Shiny

Probably very basic question - but can't translate similar posts I've found to my exact issue.
Within an R Shiny app, I have a first drop-down menu that is populated by a vector produced on the server - this allows me to make one set of choices.
I want to have a tick box that then introduces a second drop down - but I want that drop down to disappear if I un-tick the tick box.
I've had a go - see MWE below - the graph is just there to keep to the structure of my original code (obviously I'm aware my drop-downs do nothing but that's not the case in the original but wanted the MWE to be as 'M' as possible).
If I remove the removeUI() line then ticking the tick-box does create a new drop down as required - but then un-ticking the tick box fails to remove it.
I'm obviously missing something; any help much appreciated as I totally suck at R Shiny but really want to get better!
library(shiny)
library(shinyMobile)
# define UI elements
ui <- f7Page(
f7SingleLayout(
navbar = f7Navbar(
),
f7Card(htmlOutput("initial_drop_down"), #first drop down
f7checkBox(inputId = "switch", label = "Introduce second choice", FALSE), #tick box for second drop down if required
htmlOutput("reactive_drop_down") #second drop down
),
f7Shadow(
intensity = 16,
f7Card(
plotOutput("distPlot", height = "800px") # plot - originally linked to drop down choices but an arbitrary graph here for simplicity
)
)
)
)
# server calculations
server <- function(input, output) {
library(ggplot2)
# generate first drop down - done on server side since usually choices vector is comprised of information read in from files
output$initial_drop_down = renderUI({
selectInput(inputId = "initial_choice",
label = "First choice:",
choices = c("Choice 1", "Choice 2", "Choice 3"))
})
observeEvent(input$initial_choice, {
# trying to add second drop down based on action in switch - not convinced my use of observeEvent is quite right - issue likely sits in here.
observeEvent(input$switch, {
if(input$switch == T){
output$reactive_drop_down = renderUI({
selectInput(inputId = "second_choice",
label = "Second (dynamic) choice:",
choices = c(1,2,3))
})
}else{
removeUI(selector ="#reactive_drop_down")
}
})
output$distPlot <- renderPlot({
ggplot(data = cars) + geom_line(aes(x=speed, y=dist))
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Could you use conditionalPanel? Put your htmlOutput for your second input there in your ui. I would avoid using nested observeEvent and output.
library(shiny)
library(shinyMobile)
library(ggplot2)
# define UI elements
ui <- f7Page(
f7SingleLayout(
navbar = f7Navbar(
),
f7Card(htmlOutput("initial_drop_down"), #first drop down
f7checkBox(inputId = "switch", label = "Introduce second choice", FALSE), #tick box for second drop down if required
conditionalPanel(
condition = "input.switch==1",
htmlOutput("reactive_drop_down") #second drop down
)
),
f7Shadow(
intensity = 16,
f7Card(
plotOutput("distPlot", height = "800px") # plot - originally linked to drop down choices but an arbitrary graph here for simplicity
)
)
)
)
# server calculations
server <- function(input, output) {
# generate first drop down - done on server side since usually choices vector is comprised of information read in from files
output$initial_drop_down = renderUI({
selectInput(inputId = "initial_choice",
label = "First choice:",
choices = c("Choice 1", "Choice 2", "Choice 3"))
})
output$reactive_drop_down = renderUI({
selectInput(inputId = "second_choice",
label = "Second (dynamic) choice:",
choices = c(1,2,3))
})
output$distPlot <- renderPlot({
ggplot(data = cars) + geom_line(aes(x=speed, y=dist))
})
}
# Run the application
shinyApp(ui = ui, server = server)

Conditional Main Panel in Shiny

I'm building a Shiny App where I want the Main Panel to be dynamic, such that when one drop down menu is chosen a new plot is created. I understand how to do it where the plots are on top of each other (which sucks because I have table underneath that and the User will have to scroll down). What would be great is if the Main Panel Graph just 'switches'. I'm not sure if ConditinalPanel would work here? Or even a Switch statement? Here is my UI.
source("DATA CLEANING.R")
salespeople <- sort(unique(salesdatav3$SALESPERSON))
# Define UI for application that draws a histogram
ui <- fluidPage(theme = shinytheme("united"),
# Application title
titlePanel("Pounds_New"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
pickerInput("slsp", "SalesPerson", choices = salespeople, selected =NULL, options = list(`actions-box` = TRUE), multiple = T),
pickerInput("stats", "Summary Stats", choices = as.vector(c("Positive/Negative Count", "Histogram", "Plot Pounds by Time", "Top Ten Positive Trending",
"Top Ten Negative Trending")), selected = NULL, multiple = F, list(`actions-box` = TRUE))
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("sidebarplot"),
# conditionalPanel(
# condition = "input.stats == 'Histogram'",
# plotOutput("histt"),
# conditionalPanel(
# condition = "input.slsp",
DT::dataTableOutput("data_table"),
plotOutput("plot_pounds")
)
)
)
Yes, you can certainly have conditional panels in the mainPanel plotting area. Your code was quite close to being workable (just one or two errant parentheses). Below is revised code with and dummy plots to show how it works. You'll obviously have to update with what you actually want for plots. The basic structure should be quite clear. In the UI, just include your conditionalPanels in the mainPanel items, and then specify your plots separately in the server.
UI:
library(shiny)
library(shinythemes)
library(shinyWidgets)
ui <- fluidPage(theme = shinytheme("united"),
# Application title
titlePanel("Pounds_New"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
pickerInput("slsp", "SalesPerson", choices = c("a","b","c","d"), selected =NULL, options = list(`actions-box` = TRUE), multiple = T),
pickerInput("stats", "Summary Stats", choices = as.vector(c("Positive/Negative Count", "Histogram", "Plot Pounds by Time", "Top Ten Positive Trending",
"Top Ten Negative Trending")), selected = NULL, multiple = F, list(`actions-box` = TRUE))
),
# Show a plot of the generated distribution
mainPanel(
conditionalPanel(
condition = "input.stats == 'Positive/Negative Count'",
plotOutput("sidebarplot")
),
conditionalPanel(
condition = "input.stats == 'Histogram'",
plotOutput("histt")
),
conditionalPanel(
condition = "input.slsp",
# DT::dataTableOutput("data_table"),
plotOutput("plot_pounds")
)
)
)
)
Server:
server <- function(input, output) {
output$sidebarplot <- renderPlot({
hist(rnorm(50),10)
})
output$histt <- renderPlot({
hist(runif(50),10)
})
output$plot_pounds <- renderPlot({
hist(rbeta(50,1,5),10)
})
}
shinyApp(ui, server)

Shiny app with Plotly disconnects from server after rendering 2 Plotly graphs (used to work before R core and some packages update)

I'm preparing the test application with open NY data
https://tabulinas.shinyapps.io/nyaccidents/
It has tab plotlyOutput, that render one from 8 plots.
This works perfectly on my laptop, and used to work on shinyapps server till previous week. Now app shows two plotly plots (no matter what exact plots) and when i choose the third plot it disconnects from server.
I check - if i remove plotly plots, everything works fine.
And if i choose in third times plot that i have already rendered firstly, it crashes anyway (so there is not the reason, that all plotly objects are stored in memory)
It seems that it exceed some limit, some new limit, that wasn't active before.
Or maybe there is any issue with updated versions of R core, server and packages.
Please share any idea how i can make my application works again on server!
Here is part of code, where can be an issue
#Server part:
# Button "Back"
observeEvent(input$Back, {
current <- as.numeric(input$select)
if (current > 1){
updateSelectInput(session, "select",
selected = current-1)
}
})
#button "Next"
observeEvent(input$Next, {
current <- as.numeric(input$select)
if (current < 9){
updateSelectInput(session, "select",
selected = current+1)
}
})
output$selector <- renderUI({
selectInput("select", label = h3("Select plot"),
choices = list("Plot1" = 1, "Plot2" = 2,
"Plot3" = 3, "Plot4"=4,
"Plot5"=5, "Plot6"=6, "Plot7"=7,
"Plot8"=8), selected = 1)
})
output$distPlot <- renderPlotly({
if (as.numeric(input$select)==1){
resplot <- # here code of plotly plot}
# the same for other plots
resplot
})
# UI part
tabPanel(
title= div(img(src="summary.jpg",height = 30)),
fluidPage(
fluidRow(
column(4,
uiOutput("selector"), # Selectize input with plots
actionButton("Back", label = "Back"),
actionButton("Next", label = "Next")
),
column( 8,
htmlOutput("plot_comment") # Plot comment is a text output depending on plot choosen
)),
hr(),
hr(),
plotOutput("distPlot", width = "100%", height ="100%")
)
)
It looks like this

Select a point on ggplot

I'm trying to add a feature where the user can look at a plot of all time stamps from several patients of a certain variable, select a point and see which patient that point belongs to, as well as all other information about the patient at that time stamp. My data is reactive because the SQL Query that fetches it requires user input. I'm trying to use click in plotOutput and nearPoints in the server, and the clicker will show up when hovered over the plot, although if I try to click on a point, nothing will happen (the data display will remain null). Here is what I have:
#ui.R
library(shiny)
library(ggplot2)
shinyUI(navbarPage("Choose Page",
tabPanel("Page 1",
#sidebar
sidebarLayout(
sidebarPanel(
*unrelated tab*
)
)
),
tabPanel("Overall Metrics",
sidebarLayout(
sidebarPanel(
h3("Metrics Across All Patients"),
selectInput("selecty1",
label = "Choose a variable to display on the y-axis:",
choices = list('Var1', 'Var2', 'Var3', 'Var4')),
dateRangeInput("dates1", label= "Date Range:"),
submitButton("Create Graph"),
),
mainPanel(
plotOutput('plot1', click = "plotClick"),
verbatimTextOutput("click_info")
)
)
)
)
)
#server.R
library(shiny)
require(RODBC)
library(ggplot2)
library(quantmod)
library(reshape)
shinyServer(function(input, output) {
chan2 <- odbcConnect('date_base', uid='username')
queryString2 <- reactive({sprintf("**SQL Query**",input$dates1[1],input$dates1[2])})
overallData <- reactive({sqlQuery(chan2, queryString2())})
output$heartplot1 <- renderPlot({
ggplot(overallData(), aes_string(x = "Time_Stamp", y = input$selecty1)) + geom_point(size=2) + geom_line(aes(colour = factor(PatientNum))) + theme_bw() + stat_smooth(method="lm",se=FALSE,size=1)
})
output$click_info <- renderPrint({
nearPoints(overallData(), input$plotClick, addDist = FALSE)
})
})
All demos I find of nearPoints use typical data tables, how can I make this work with reactive data, or is that not the problem here?

Resources