r shiny: Creating widgets in ui.R vs. renderUI + uiOutput - r

I have a question that falls more into a "best practice" type of inquiry. When using shiny package in r, is it better to create all widgets on the server side using renderUI and then pushing those to the ui via uiOutput? Or, when possible, should all widgets be created on the ui side?
For instance, the two apps below do the same thing but in the second one, I create the sliderInput on the server side and then push that to the ui rather than creating it in the ui side. (Note, this code is pulled from the Hello Shiny page on R Studio)
App 1 - "Standard Approach" creating widget in ui
#ui.R
# Define UI for application that plots random distributions
library(shiny)
ui1 <- shinyUI(fluidPage(
# Application title
titlePanel("Hello Shiny!"),
# Sidebar with a slider input for number of observations
sidebarLayout(
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 1,
max = 1000,
value = 500)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))
#server.R
# Define server logic required to generate and plot a random distribution
library(shiny)
server1 <- (function(input, output) {
output$distPlot <- renderPlot({
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist)
})
})
runApp(shinyApp(ui = ui1, server = server1))
App 2 - Alternative Approach - Creating Widget on server side
#ui.R
# Define UI for application that plots random distributions
library(shiny)
ui2 <- shinyUI(fluidPage(
# Application title
titlePanel("Hello Shiny!"),
# slider comes from the si object created in server.R
sidebarLayout(
sidebarPanel(
uiOutput("si")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))
#server.R
# Define server logic required to generate and plot a random distribution
library(shiny)
server2 <- (function(input, output) {
#create slider with renderUI
output$si <- renderUI(
sliderInput("obs",
"Number of observations:",
min = 1,
max = 1000,
value = 500)
)
output$distPlot <- renderPlot({
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist)
})
})
runApp(shinyApp(ui = ui2, server = server2))
To me, the second approach is more generalizable so it should win. However, I am no expert and I rarely see this approach being used unless there is a specific reason why you need the widgets to be responsive in some way. The ways in which I have required responsiveness include:
There is data loaded on the server side which ends up feeding the widget choices so it is better to only load the data one time on the server side and create the widgets there, rather than load it on the server and ui side.
We need to turn off/on widgets and/or allow them to react to other user input
Since the second approach I've presented can handle the above two options, it makes sense to me that it should be used in all cases, even when there is no real need to create the widget on the server side. I notice that when using the second approach, there is a delay, sometimes accompanied by warnings/errors prior to the widgets being loaded. That is the only downside I've noticed about the approach.
Is one of these approaches considered a "best practice?"
Thanks.

Related

How can I change the template loaded when created a new Shiny App in RStudio?

I've been using RStudio to create Shiny apps. When creating a new one-file Shiny app via the "New..." button in RStudio, the following code is automatically loaded:
#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
# Run the application
shinyApp(ui = ui, server = server)
My first step of app creation is to delete the bodies of fluidPage and the server function. What I really want is an essentially blank template:
library(shiny)
ui <- fluidPage(
)
server <- function(input, output) {
}
shinyApp(ui = ui, server = server)
How can I change the default template used to create new Shiny Apps? I'm shocked that I can't seem to find an answer to this question anywhere online, so if someone could point me towards an existing answer I would be very grateful.
I actually don't care to ever make the app RStudio uses as a template (which is the 01_hello_world example that comes with Shiny) so if anyone could tell me where the new App template is stored, I can just edit that.
Just some notes on things I've already tried:
Changing the script shiny/examples/01_hello_world/app.R to the "blank" one.
looking in the shiny/template folder; this contains only an HTML document.
adding an app.R file containing my desired script to shiny/template.
I'll add that I'm running RStudio on Debian Linux. Thank you in advance!

How to render the shiny's output via plumber/REST API to other programmers?

Hey Shiny and Plumber experts,
Relevant links :
https://abndistro.com/post/2019/07/06/deploying-a-plumber-api-on-aws-ec2-instance/
https://abndistro.com/post/2019/07/06/deploying-a-shiny-app-with-shiny-server-on-an-aws-ec2-instance/
Basically, we are trying to pass the output of shiny via Plumber to other programmers to integrate in an webpage.
From the documentation, running on local machine,
following works individually:
Plumber reprex
#* Plot a histogram
#* #png
#* #get /plot
function(){
random_num <- rnorm(10) * 5
hist(random_num)
}
saving the file as plumber.R
# Running the below in console
r <- plumb("plumber.R") # Where 'plumber.R' is the location of the file shown above
r$run(port=8000)
# Now, loading the browser: http://127.0.0.1:8000/__swagger__/
[![plumber output][1]][1]
Similarly shiny reprex :
library(shiny)
# Define UI for application that plots random distributions
ui = shinyUI(fluidPage(
# Application title
titlePanel("Hello Shiny!"),
# Sidebar with a slider input for number of observations
sidebarLayout(
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 1,
max = 1000,
value = 500)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))
# Define server logic required to generate and plot a random distribution
server = shinyServer(function(input, output) {
# Expression that generates a plot of the distribution. The expression
# is wrapped in a call to renderPlot to indicate that:
#
# 1) It is "reactive" and therefore should be automatically
# re-executed when inputs change
# 2) Its output type is a plot
output$distPlot <- renderPlot({
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist)
})
})
# Run the app
shinyApp(ui, server)
How can we render shiny's output i.e histogram into plumber's swagger ?
i.e. How to display the user
Tried: Embedding the plumber function within Shiny's renderFunction
library(shiny)
# Define UI for application that plots random distributions
ui = shinyUI(fluidPage(
# Application title
titlePanel("Hello Shiny!"),
# Sidebar with a slider input for number of observations
sidebarLayout(
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 1,
max = 1000,
value = 500)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))
# Define server logic required to generate and plot a random distribution
server = shinyServer(function(input, output) {
# Expression that generates a plot of the distribution. The expression
# is wrapped in a call to renderPlot to indicate that:
#
# 1) It is "reactive" and therefore should be automatically
# re-executed when inputs change
# 2) Its output type is a plot
#### Embedding inside the shiny
output$distPlot <- renderPlot({
#* Plot a histogram
#* #png
#* #get /plot
function(){
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist)
}
})
})
shinyApp(ui, server)

How do I use Crosstalk in Shiny to filter a reactive data table?

I cannot use functions from the Crosstalk package in a Shiny app. When running, I receive an error message saying my SharedData object is not found.
I would like to create a Shiny app where users can filter in various ways (selectInput, selecting rows in a data table, etc.) but all tables, charts and maps update simultaneously regardless of the filter method. I have heard that crosstalk is good for this and I have relied on the volcano example here as a template.
My issue is that when I attempt to run my app, I receive a warning saying that the SharedData object cannot be found. I've tried placing this in the server part, and tried placing within reactive, eventReactive, observe, observeEvent, etc. but these don't make any difference. I've also tried placing the code outside of both ui and server parts but the only difference is that my error is now "object of type 'environment' is not subsettable".
Below is not my original code; I have simplified the issue and made it reproducible by sharing the following:
*Created a new Shiny app from Rstudio (the template uses the faithful dataset)
*Modified the dataset from faithful to iris
*Added a call to the crosstalk library
*Included filter_select, SharedData calls
*Modified line 48 to refer to shared_df rather than iris
library(shiny)
library(crosstalk)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Crosstalk test"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
filter_select("rows", "Select rows:",
shared_df,
~Species,
multiple = TRUE)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
# Shared data available for use by the crosstalk package
shared_df <- SharedData$new(iris)
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- shared_df[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
# Run the application
shinyApp(ui = ui, server = server)
My expected results should include a histogram chart with interactive slider to dynamically adjust bin widths (as per the "old faithful" template). In addition, I should be able to select Species of plant and the histogram should dynamically update to only display results for the selected Species.
* edit *
I've managed to recreate the volcano example linked to above for a Shiny app. The code below works:
library(shiny)
library(crosstalk)
library(leaflet)
library(DT)
# Wrap data frame in SharedData
# Use SharedData like a dataframe with Crosstalk-enabled widgets
sd <- SharedData$new(quakes[sample(nrow(quakes), 100),])
ui <- fluidPage(
# Create a filter input
filter_slider("mag", "Magnitude", sd, column=~mag, step=0.1, width=250),
bscols(leafletOutput("map"), DTOutput("table"))
)
server <- function(input,output) {
output$map <- renderLeaflet({ leaflet(sd) %>% addTiles() %>% addMarkers()})
output$table <- renderDT({
datatable(sd, extensions="Scroller", style="bootstrap", class="compact", width="100%",
options=list(deferRender=TRUE, scrollY=300, scroller=TRUE))
}, server = FALSE)
}
shinyApp(ui = ui, server = server)
The only difference between this volcano example and my iris example is (apart from moving the shared_df outside of ui and server) the usage of filter_select. When I move shared_df a different error is returned: "Warning: Error in [: object of type 'environment' is not subsettable"

R/shiny: render elements of plots only when needed in shiny apps

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.

R Shiny Tabsets simultaneous processing

I have a R Shiny app, which calculates several statistics in different tabsets. As the calculations are quite computation intensive, I use submitButton to prevent reactivity. My problem is now that each calculation (all in different tabsets) are writing outputs to a folder and I want Shiny to write an output for all tabsets when initializing. Unfortunately, Shiny only creates an output for the tabset, that is active when initializing. Is there a way to tell Shiny, that it should calculate/render outputs for every tab when initializing?
Here is a modified example from the Shiny[Tutorial]:(http://www.http://rstudio.github.io/shiny/tutorial/#more-widgets/)
ui.R:
library(shiny)
# Define UI for dataset viewer application
shinyUI(pageWithSidebar(
# Application title.
headerPanel("More Widgets"),
# Sidebar with controls to select a dataset and specify the number
# of observations to view. The helpText function is also used to
# include clarifying text. Most notably, the inclusion of a
# submitButton defers the rendering of output until the user
# explicitly clicks the button (rather than doing it immediately
# when inputs change). This is useful if the computations required
# to render output are inordinately time-consuming.
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10),
helpText("Note: while the data view will show only the specified",
"number of observations, the summary will still be based",
"on the full dataset."),
submitButton("Update View")
),
# Show a summary of the dataset and an HTML table with the requested
# number of observations. Note the use of the h4 function to provide
# an additional header above each output section.
mainPanel(
tabsetPanel(
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("view"))
)
)
))
server.R:
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected dataset
shinyServer(function(input, output) {
# Return the requested dataset
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Generate a summary of the dataset
output$summary <- renderPrint({
dataset <- datasetInput()
capture.output(summary(dataset),file="summary.txt")
})
# Show the first "n" observations
output$view <- renderTable({
a<-head(datasetInput(), n = input$obs)
capture.output(a,file="table.txt")
})
})
I think you want:
outputOptions(output, "summary", suspendWhenHidden = FALSE)
outputOptions(output, "view", suspendWhenHidden = FALSE)
Put this into your server.R. Let me (us) know if this works as you expect.
Documentation:
http://www.inside-r.org/packages/cran/shiny/docs/outputOptions

Resources