Natural History Museum
2024-02-07
git clone https://github.com/simon-smart88/shinyworkshopinstall.packages(c("shiny","leaflet", "DT", "rsconnect", "sf", "terra"))slides.qmdShiny apps consist of a user interface object (UI) and a server object
Shiny apps consist of a user interface object (UI) and a server object
list()-like objects as arguments:
input$ where settings made in the UI are stored
output$ where objects created in the server that need to be displayed in the UI are stored
flowchart TD A[Input in UI] --> |input$| B([Computation in server]) B --> |output$| C(Output in UI) class A sin class B sser class C sout
input$ and output$ have an ID used to refer to theminput$ objects, the ID is always the first argument of the function used to create them:output$ objects, you declare them and then reference them by ID in the UI:input$ value changes, then any code which uses the input is rerunflowchart TD A[Input in UI] --> |input$| B([Computation in server]) B --> |output$| C(Output in UI) class A sin class B sser class C sout
flowchart TD
A["textInput()"] --> |input$name| B(["renderText()"])
B --> |output$name_out| C("textOutput()")
class A sin
class B sser
class C sout

Source: https://shiny.posit.co/r/getstarted/shiny-basics/lesson3/
Outputs are generated in the server using render* functions and displayed in the UI using *Output functions
| Data type | Render function | Output function |
|---|---|---|
| Table | renderTable() |
tableOutput() |
| Plot | renderPlot() |
plotOutput() |
| Text | renderText() |
textOutput() |
| Image | renderImage() |
imageOutput() |
| Interactive table | renderDataTable() |
dataTableOutput() |
render*() functions are used with curly brackets {} inside the functionui <- fluidPage(selectInput("animal", "Choose your favourite animal",
choices = c("","Whale", "Dinosaur")),
textOutput("animal_name"))
server <- function(input, output) {
output$animal_name <- renderText({
animal_names = list("Whale" = "Hope", "Dinosaur" = "Dippy")
paste0("Your favourite animal's name is ", animal_names[[input$animal]])})
}
shinyApp(ui = ui, server = server)fluidPage() makes the design responsive so that it fits on different sized screensdf <- read.csv()
# run once when the app starts
ui <- fluidPage()
server <- function(input, output) {
df <- read.csv()
# run whenever a new user uses the app
output$table <- renderTable({
df <- read.csv()
# run whenever input$value changes
df <- df[df$column == input$value,]
})
}
shinyApp(ui = ui, server = server)Unfortunately this will not work as you might expect:
irissliderInput(), numericInput() or selectInput()renderTable() and tableOutput()shinyApp() in a file and clicking on reactive()input$ you must do so inside reactive objectsrender* functions are reactivereactive()() when you access the valuesreactive() example❌
✅
render* functions, you can make these multi-line using {}fileInput() uploads the file to the web server, but not into the R environmentinput$ value is a dataframe containing name, size, type and datapath columnsdatapath column e.g.:renderUI() and uiOutput()observe()reactive() but doesn’t return a resultinput$ which is NULL when the app initiates?req()req() is used to control execution of a function by defining the values that it requiresreactive() and render*()validate() and need()validate(need()) is similar to req() but more user-friendly as errors can be passed back to the UIactionButton() and bindEvent()actionButton() and observeEvent()bindEvent() but for use when the action doesn’t produce an outputCreate an app where you:
iris.csv using fileInput()renderUI() and selectInput()renderPlot()actionButton and bindEvent() to control when the plot is renderedflowchart TD
A["fileInput('file' ...)"] --> |input$file| B(["renderUI({<br/>selectInput(<br/>'variable_two' ...)<br/>})"])
A --> |input$file| C(["renderUI({<br/>selectInput(<br/>'variable_one' ...)<br/>})"])
B --> |output$select_two| D("uiOutput('select_two')")
C --> |output$select_one| E("uiOutput('select_one')")
E --> |input$variable_one|F
D --> |input$variable_two|F(["renderPlot()"])
F --> |output$plot|G("plotOutput('plot')")
class A sin
class B sser
class C sser
class D sout
class E sout
class F sser
class G sout
downloadButton() in the UIdownloadHandler() in the serverreactive() that you have used to create a table or a graph inside the content part of the download handlerdf <- reactive(iris[iris$Sepal.Length <= input$sepal_length,])
output$plot <- renderPlot(plot(df()$Sepal.Length, df()$Sepal.Width))
output$download_data <- downloadHandler(
filename = function() {
"your_plot.png")
},
content = function(file) {
png(file, width = 1000, height = 500)
plot(df()$Sepal.Length, df()$Sepal.Width)
dev.off()
}
)DT::renderDataTable() in the server and DT::dataTableOutput() in the UI:{reactable} and {gt}input$<table ID>_rows_selected{leaflet} is a package for creating interactive mapsrenderLeaflet() for the server and leafletOutput() for the UIaddProviderTiles()addLegend()addMarkers()addPopups()setView() and fitBounds()addLayersControl(){leaflet.extras} has tools for drawing shapes on the map which can be used to edit dataleafletProxy() prevents completely re-drawing the map whenever something changes:input$ values that record events occurring in the mapinput$<map ID>_<object type>_<event type> e.g. input$map_shape_clicklist() containing $lat and $lng which can be used for further calculationsoutput$selected_shape <- renderText({
selected_point <- data.frame(x = input$map_shape_click$lng, y = input$map_shape_click$lat ) %>%
sf::st_as_sf(coords = c("x", "y"), crs = 4326)
index_of_polygon <- sf::st_intersects(selected_point, wards, sparse = T) %>%
as.numeric()
glue::glue("You clicked on the ward of {wards$NAME[index_of_polygon]} which is in {wards$DISTRICT[index_of_polygon]}")
}){leaflet} to select a ward or district of London and zoom when it is selected
selectInput()sf::st_bbox() and leaflet::fitBounds()downloadHandler to download a .png of the satellite imagery of just that area
terra::crop(mask = TRUE)layout.R to be run separately (sorry!)fluidPage(titlePanel("My fourth app"),
tabsetPanel(tabPanel("Tab 1",sidebarLayout(sidebarPanel(
numericInput("number", "Pick a number", value = 10),
selectInput("select", "Select an animal", choices = c("Cat", "Dog"))),
mainPanel(plotOutput("plot"),tableOutput("table")))),
tabPanel("Tab 2",fluidRow(column(width = 3,textInput("word", "Type a word"),
sliderInput("slider", "Pick a value", min = 10, max = 100, value = 50)),
column(width = 6,leafletOutput("map")),
column(width = 3,checkboxInput("check", "Tick me")))),
tabPanel("Tab 3",plotOutput("another_plot"))))fluidPage(
titlePanel("My fourth app"
),
tabsetPanel(
tabPanel("Tab 1",
sidebarLayout(
sidebarPanel(
numericInput("number", "Pick a number", value = 10),
selectInput("select", "Select an animal", choices = c("Cat", "Dog"))
),
mainPanel(
plotOutput("plot"),
tableOutput("table")
)
)
),
tabPanel("Tab 2",
fluidRow(
column(width = 3,
textInput("word", "Type a word"),
sliderInput("slider", "Pick a value", min = 10, max = 100, value = 50)
),
column(width = 6,
leafletOutput("map")
),
column(width = 3,
checkboxInput("check", "Tick me")
)
)
),
tabPanel("Tab 3",
plotOutput("another_plot")
)
)
)fluidPage(theme = bslib::bs_theme(bootswatch ="<theme name>"))app.R - what we will useserver.R and ui.Rglobal.R - loaded before server.R and ui.R so can be a good place to load dataserver.R and ui.R become very long, code can be modularised into infinite files{renv} to check your R version and what package versions the app usesexercise3.R to app.Rapp.R in Rstudio.jpeg instead of .csv?validate(need()) can help:() when accessing their valuesreactive() variable the same as a loaded functioninput or output IDsverbatimTextOutput() and textOutput() to display objects - are they in the state you expect?options(shiny.fullstacktrace = TRUE) in the serverbrowser() above where your code is failing and then inspect the objects in your environment
