I'm relatively new to R Shiny and reactive programming. From my understanding (and this tutorial), it seems like you are not supposed to tell Shiny "when" to do things (i.e. to enforce execution order) as it will figure that out itself. However, often I find myself wanting plots or other UI to render sequentially. Is there a good way to do this?
I've made up a minimal example below. I want to render header before plot, as plot requires a time-consuming computation.
library(shiny)
ui <- fluidPage(
tags$h1("My app"),
uiOutput("header"),
plotOutput("plot")
)
server <- function(input, output) {
output$header <- renderUI({
tagList(tags$h2("Section header"),
tags$p("Some information relevant to the plot below..."))
})
output$plot <- renderPlot({
# hypothetical expensive computation
Sys.sleep(2)
# hypothetical plot
hist(rnorm(20))
})
}
shinyApp(ui = ui, server = server)
Here I could obviously replace uiOutput("header") in ui with its definition in server and that solves the problem; however, in practice I want header to be dynamic. A hacky solution I found was to include a hidden input inside header and then use req() inside plot. This is kind of like adding an action button that automatically clicks upon load.
server <- function(input, output) {
output$header <- renderUI({
tagList(tags$h2("Section header"),
tags$p("Some information relevant to the plot below..."),
div(style = "display:none", textInput(inputId = "hidden", label = "", value = "x")))
})
output$plot <- renderPlot({
req(input$hidden)
...
})
}
However, if I want to repeat this in multiple situations or if I want to force a chain of more than two outputs to render sequentially, then this seems tedious. Can anyone suggest a more elegant solution?
As your example code includes a "time-consuming computation" I guess in the end you don't really want to control the render order, instead you want to avoid long running functions to block the execution of other parts of your code (XY problem).
By default, R is single threaded, therefore we'll need child processes to solve this issue.
In shiny you can use library(future) + library (promises) for this. However, using asynchronous processes to unblock the elements within a shiny session requires us to "hide" the promise - read more about it here.
Below please find an async version of your example:
library(shiny)
library(promises)
library(future)
plan(multisession)
ui <- fluidPage(
tags$h1("My app"),
uiOutput("header"),
plotOutput("plot")
)
server <- function(input, output) {
output$header <- renderUI({
tagList(tags$h2("Section header"),
tags$p("Some information relevant to the plot below..."))
})
data <- reactiveVal()
observe({
future_promise({
# hypothetical expensive computation
Sys.sleep(2)
# hypothetical plot data
rnorm(20)
}, seed=TRUE) %...>% data()
return(NULL) # "hide" the future_promise
})
output$plot <- renderPlot({
req(data(), cancelOutput = TRUE)
hist(data())
})
}
shinyApp(ui = ui, server = server)
In Shiny, for a given session, no outputs are sent back to the client, until all outputs are ready : the text render function isn't sent until the plot render function completes, see shiny flush cycle.
A workaround is to skip plot rendering using a reactiveVal so that the text gets displayed in a first flush cycle, and then to use invalidateLater() to launch a new flush cycle to run the longer plot rendering.
library(shiny)
ui <- fluidPage(
tags$h1("My app"),
uiOutput("header"),
plotOutput("plot")
)
server <- function(input, output,session) {
skipPlot <- reactiveVal(1)
output$header <- renderUI({
tagList(tags$h2("Section header"),
tags$p("Some information relevant to the plot below..."))
})
output$plot <- renderPlot({
if (isolate(skipPlot()==1)) {
# skip first reactive sequence
skipPlot(0)
# launch next reactive sequence
invalidateLater(1,session)
} else {
# hypothetical expensive computation
Sys.sleep(2)
# hypothetical plot
hist(rnorm(20))
}
})
}
shinyApp(ui = ui, server = server)
Related
Here's a long-shot question. The below code allows the user to build and alter a scaled-logarithmic curve by altering its 4 parameters, via slider inputs. I'd like to reverse the process, so the user clicks/drags the plot line and a new "exponential" curve parameter is backed into. How to do this in R Shiny?
Later, after figuring out how to derive the exponential parameter, I'll try backing into some of the other curve parameters too.
This image illustrates what I'm trying to do:
Code:
library(shiny)
ui <- fluidPage(
sliderInput('periods','Nbr of periods:',min=0,max=36,value=24),
sliderInput('start','Start value:',min=0,max=1,value=0.15),
sliderInput('end','End value:',min=0,max=1,value=0.70),
sliderInput('exponential','Exponential:',min=-100,max=100,value=10),
plotOutput('plot')
)
server <- function(input, output, session) {
data <- reactive({
data.frame(
Periods = c(0:input$periods),
ScaledLog = c(
(input$start-input$end) *
(exp(-input$exponential/100*(0:input$periods))-
exp(-input$exponential/100*input$periods)*(0:input$periods)/input$periods)) +
input$end
)
})
output$plot <- renderPlot(plot(data(),type='l',col='blue',lwd=5))
}
shinyApp(ui,server)
I am a newbie to R, it's definitely been a learning curve and I am still bad at it. I can't really solve this issue myself as I don't really know what I am doing wrong.
library(shiny)
library(dplyr)
library(readxl)
#UI
ui <- fluidPage(
#Title
titlePanel("Testing Shiny"),
#Sidebar Layout
sidebarLayout(
# Sidebar panel
sidebarPanel(
#Input
selectInput("typedata",
label = "What do you want to see?",
list("Count of Job Reqs",
"Candidates")),
selectInput("Work Location", label = "Location", choices = unique(exceldata$"Work Location")),
),
#Main Panel
mainPanel(
#Output
textOutput("selected_typedata"),
textOutput("totalreq"),
plotOutput("plot")
)
)
)
#Server
server <- function(input, output) {
output$selected_typedata <- renderText({
paste("You wanted to see", input$typedata)
})
output$plot <- renderPlot({
ggplot(data=exceldata, aes(x=exceldata$`Application Date`,
y=exceldata$`Candidate ID`, fill=c('steelblue'))) +
geom_bar(stat='Identity', width=0.8)
})
}
#Shiny app
shinyApp(ui = ui, server = server)
What it's doing is not actually graphing anything. I don't know why, it draws the x and y, but not the actual bars. What I am trying to accomplish here is a basic bar graph showing the count of candidate IDs that applied during their application dates.
If someone could point out what I could possibly be doing wrong, I would greatly appreciate it. I tried using (count(exceldata$"Application Date')), but it gives me an error "no applicable method for 'tbl_vars' applied to an object of class "c('double', 'numeric')".
I tried reading documentation, and it just works fine for others. My data comes from a table loaded as a function using read_excel.
I'm trying to create a Shiny app that lets users
create a dataset by entering frequency counts for different values
plot a histogram of that dataset
A paired back example of the code is as follows:
library(shiny)
library(ggplot2)
# Define UI for application
ui <- fluidPage(
# Sidebar with inputs
sidebarLayout(
sidebarPanel(
numericInput("data1s",
"How many have a score of 1?",
value = 0,
min = 0
),
numericInput("data2s",
"How many have a score of 2?",
value = 0,
min = 0
),
sliderInput("bins",
"Number of bins:",
min = 1,
max = 3,
value = 1)
),
# Show a plot of the data
mainPanel(
htmlOutput("mydatatable"),
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
#show the data
output$mydatatable <- renderTable({
#create the dataframe from the frequncies
mydata <- data.frame(our_data=c(rep(1,input$data1s),rep(2,input$data2s))
)
}
)
#show the histogram
output$distPlot <- renderPlot({
ggplot(mydata, aes(x=our_data)) +
geom_histogram(bins = input$bins)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I have achieved the creation of the dataset, but the code for displaying a histogram of the data returns an error: "object 'mydata' not found" instead of showing the histogram. The histogram should update whenever any of the inputs are changed.
Any help to resolve would be much appreciated.
The mydata that you define in the mydatatable reactive is not visible anywhere else. To understand why, I suggest you read about R's namespaces and environments; one good tutorial on it is Hadley's Advanced R -- Environments.
To fix it, I suggest you make the data itself a reactive block, and depend on it in your two other blocks (table and plot):
server <- function(input, output) {
mydata <- reactive({
req(input$data1s, input$data2s)
data.frame(our_data=c(rep(1,input$data1s),rep(2,input$data2s)))
})
#show the data
output$mydatatable <- renderTable({ req(mydata()); })
#show the histogram
output$distPlot <- renderPlot({
req(mydata())
ggplot(mydata(), aes(x=our_data)) +
geom_histogram(bins = input$bins)
})
}
(Untested.)
I added the use of req solely to prevent start-up jittering and warnings/errors in the app. When the shiny app is warming up, it's common to have input variables empty (NULL), and things that depend on it will temporarily produce errors until the inputs stabilize. (For an example of why things will stumble, input$data1s may initially show a NULL value, and try to see if data.frame(our_data=rep(1,NULL)) will work.)
req just looks for something that is "truthy", meaning: not NULL, not NA, not FALSE, length greater than 0, etc. See ?shiny::req for more details.
While req is not strictly required, it has its advantages. As you may infer from the table code, req(x) will return the "first value that was passed in" (from ?req), so it can be used in this shortcut mode for brevity.
And one last soap-box: in my limited experience with shiny reactivity, there are few times that I've generated data within a reactive block and used it solely within that reactive block. Given that, whenever you make a data.frame (or list or ... some important structure that is dependent on user input), it is often beneficial to make it its own reactive component (specifically, not an output component), and then depend on it as many times as necessary.
I am building a UI containing DT tables and sliders (both as inputs), as well as plot outputs. The tables are used to make a selection out of several. The user can only select one cell to make a choice.
I want the user to be able to store the setting of tables and sliders because they are quite complex. The idea is that the user can then switch back and forth between two stored settings, for example, and see how the resulting plots change. When a user restores a setting, the tables and sliders get updated, which updates the plot(s).
The problem is that the plot is not updated once, but usually twice. It seems that there is a delay somewhere in the logic, causing Shiny to first react to the update of the sliders, then to the update of the tables, so that the plot is re-plotted in two steps. This is very annoying for two reasons: (1) it causes the calculation to re-run twice, making the app react twice as slow and (2) it's impossible to see the changes directly in the plot because the original plot is first replaced by an intermediate plot which has no meaning to the user.
To illustrate the problem, I created this minimum working example, where I reduced complexity to just one table and one slider. I added a 3 second Sys.sleep to simulate a long calculation because obviously one would not see the problem otherwise:
library(shiny)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("button", "Preset"),
# No problem with selectInput:
# selectInput("select", "x", choices = names(iris)[1:4], selected = "Sepal.Length"),
DT::dataTableOutput("table"),
sliderInput("slider", "bins", min = 1, max = 50, value = 30)
),
mainPanel(
plotOutput("distPlot")
)
)
)
server <- function(input, output, session) {
observeEvent(input$button, {
# updateSelectInput(session, "select", selected = "Petal.Width")
selectRows(DT::dataTableProxy("table"), 4)
updateSliderInput(session, "slider", value = 15)
})
output$table <- DT::renderDataTable(
DT::datatable(
data.frame(x = names(iris)[1:4]),
rownames = FALSE,
selection = "single",
options = list(searching = FALSE, paging = FALSE, info = FALSE, ordering = FALSE)
)
)
output$distPlot <- renderPlot({
req(input$table_rows_selected)
# x <- iris[[input$select]]
x <- iris[[input$table_rows_selected]]
bins <- seq(min(x), max(x), length.out = input$slider + 1)
# Simulate long calculation:
Sys.sleep(3)
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
shinyApp(ui = ui, server = server)
Clicking first on the cell "Sepal.Length" in the table, then on the button "Preset" will load the preset and demonstrate the problem.
It seems that this is a timing issue/race condition, because sometimes, it works OK and the plot is updated only once (only in the minimal example, not the actual app). Usually the first time after starting the app. But in that case, just click on "Sepal.Length" again and change the slider position, then click on the "Preset" button and usually the plot will update twice.
I noticed that the problem does not appear when I replace the table with a selectInput. But the tables have a certain meaning: they stand for morphological fields (see package morphr), so I'd rather stick with tables to have the right appearance.
I could obviuously also disable reactivity using isolate() as suggested here: R Shiny: how to prevent duplicate plot update with nested selectors? and then e.g. introduce a button "Update plot". But I would prefer to keep the app reactive to changes in the sliders and tables, because that's a very useful user experience and one reason for me to use Shiny instead of PHP/python/etc.
My first idea to solve the problem was to introduce a reactive value:
server <- function(input, output, session) {
updating <- reactiveVal(FALSE)
# ...
}
then change the value before and after the updates to the inputs:
observeEvent(input$button, {
updating(TRUE)
selectRows(DT::dataTableProxy("table"), 4)
updateSliderInput(session, "slider", value = 15)
updating(FALSE)
})
and add an if statement in the renderPlot() code, e.g. with validate:
output$distPlot <- renderPlot({
validate(need(!updating(), ""))
# ...
})
But that has no effect, because the entire code in the observeEvent(input$button) runs first, setting updating to TRUE and immediately back to FALSE. But the code inside renderPlot() is executed later (after the invalidation has occurred) and updating is always FALSE when it runs.
So, at the moment I have few ideas how to solve this. It would be best if one could somehow disable reactivity for the plot, then update the inputs, enable reactivity again and trigger a plot update programmatically. But is this possible?
Any other ideas for a workaround?
I'm not sure to understand the issue. Does this solve the problem:
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
......
observeEvent(input$button, {
runjs("Shiny.setInputValue('slider', 15); Shiny.setInputValue('table_rows_selected', 4);")
selectRows(DT::dataTableProxy("table"), 4)
updateSliderInput(session, "slider", value = 15)
})
I am currently writing a shiny application. I want to decrease the rendering time of plots (because it takes a long time to initialise a plot). Let's say I want to render a plot dynamically, e.g.
plot(x=1:10)
(plot will not be the function which I will use in the shiny app.)
Now I want to divide the plotting into several parts, here:
plot(x=NA, y=NA, xlim=c(0,10), ylim=c(0,10))
points(x=1:10)
where plot(x=NA, y=NA, xlim=c(0,10), ylim=c(0,10)) will take a very long time in the shiny app to render and points(x=1:10) will take a short time. I need a procedure which will execute plot(x=NA, y=NA, xlim=c(0,10), ylim=c(0,10)) only when loading the app for the first time and then the plot will be build bottom-up (add points, lines, etc. to the plot). Has anybody an idea how to write this into an app? Problem here is that the function I will use in the shiny app to plot will not return anything. The plotting function is based on the base graphics system (not on ggplot2, not on lattice).
Here's a minimal working example for such an app:
library(shiny)
shinyAPP <- function() {
ui <- fluidPage(
sidebarPanel(),
mainPanel(
plotOutput("plotPoints"))
)
server <- function(input, output, session) {
output$plotPoints <- renderPlot(
plot(x=1:10)
## this needs to be replaced with:
##plot(x=NA, y=NA, xlim=c(0,10), ylim=c(0,10))
##points(x=1:10)
)
}
app <- list(ui = ui, server = server)
runApp(app)
}
shinyAPP()
Thank you very much!
So maybe try grDevices, like here:
server.R:
library("shiny")
library("grDevices")
data(iris)
plot(x=NA, y=NA, xlim=c(0,10), ylim=c(0,10))
p <- recordPlot()
function(input, output, session) {
output$plotPoints <- renderPlot({
replayPlot(p)
points(1:input$ile)
})
}
and ui.R:
library(shiny)
fluidPage(
sidebarPanel(
sliderInput("ile", min=1, max=10, label="", value=5)
),
mainPanel(
plotOutput("plotPoints"))
)
You said that you won't use plot, but it's important what you're going to use. For example, for ggplot you can do it like here (see reactive):
ui.R:
library(shiny)
fluidPage(
sidebarPanel(
sliderInput("ile", min=1, max=10, label="", value=5)
),
mainPanel(
plotOutput("plotPoints"))
)
server.R
library("shiny")
library("ggplot2")
data(iris)
function(input, output, session) {
wyk <- reactive({ggplot(iris)})
output$plotPoints <- renderPlot(
wyk() + geom_point(aes(x=Sepal.Length, y=Sepal.Width), col=input$ile)
)
}
Here is a little trick that should work if I understood your problem.
However, it's not R't, just a quick fix.
test/ui.R :
fluidPage(
sidebarPanel(
actionButton("trickButton","Useless"),
sliderInput("something", min=1, max=5, label="Useful", value=5)
),
mainPanel(
plotOutput("plotPoints")
)
)
test/server.R :
data(iris)
myData <<- NULL
superHeavyLoad <- function() {
print("That is super heavy !")
myData <<- iris
}
function(input, output, session) {
observe({
if (!input$trickButton)
superHeavyLoad()
})
output$plotPoints <- renderPlot(
plot(myData[,1:as.numeric(input$something)])
)
}
Now, on your R console :
require(shiny)
runApp("test")
Listening on http://127.0.0.1:7175
[1] "That is super heavy !"
And no matter what you do, you will never update the super-heavy part ever again.
Now, from what I understood, what you did is to divide your processing between heavy-one-time functions, and reactive things. This is a (not very beautiful) way of doing it ;-)
About how it works : it's all in the button that we add. The observe function will be called each time we interact with the button, plus at server start. The if(!input$trickButton) states that we just run our code at
server start (because then the button is not valued).
You could also hide this useless button with a renderUI mechanism.