Disclaimer:

The examples and code snippets in this tutorial are adapted from the official Shiny tutorial by Posit (formerly RStudio). Full credit goes to the original authors โ€” this walkthrough simply restructures and supplements their content for a smoother, section-wise learning experience.

Packages required

  • library(shiny) โ€“ to build interactive web apps
  • library(bslib) โ€“ for Bootstrap themes and layout
  • library(leaflet) โ€“ for mapping spatial data
  • library(dplyr) โ€“ for data manipulation

1. Uploading a CSV

Code

# Load Required Libraries
library(shiny)
library(leaflet)
library(DT)

# Define the User Interface (UI)
ui <- fluidPage(
  titlePanel("Upload and View CSV File"),
  
  sidebarLayout(
    sidebarPanel(
      fileInput(inputId = "filedata",
                label = "Upload data (CSV file)",
                accept = c(".csv"))
    ),
    
    mainPanel(
      DTOutput("table")
    )
  )
)

# Define the Server Logic
server <- function(input, output) {
  
  data <- reactive({
    req(input$filedata)  # Ensure file is uploaded
    read.csv(input$filedata$datapath)
  })
  
  output$table <- renderDT({
    datatable(data())
  })
}

# Run the Shiny App
shinyApp(ui = ui, server = server)

Output

2. Uploading point data

To demonstrate this section, a small dataset was created using a few latitude and longitude points around the Athens campus.

Code

# Load Required Libraries
library(shiny)
library(leaflet)
library(DT)

# ---- Sample Data ----
default_data <- data.frame(
  name = c("Point A", "Point B", "Point C"),
  lat = c(28.5383, 34.0522, 40.7128),
  lon = c(-81.3792, -118.2437, -74.0060)
)

# Step 2: Define the User Interface (UI)
# ---- UI ----
ui <- fluidPage(
  titlePanel("Basic Leaflet Map in Shiny"),
  
  sidebarLayout(
    sidebarPanel(
      fileInput("file", "Upload CSV with lat/lon", accept = ".csv"),
      helpText("CSV should have columns: 'lat', 'lon', and optional 'name'"),
      br(),
      DTOutput("table")  # <- Table goes here
    ),
    
    mainPanel(
      leafletOutput("map", height = 500)
    )
  )
)

# ---- Server ----
server <- function(input, output, session) {
  
  # Load data: use uploaded file or default
  data_reactive <- reactive({
    if (is.null(input$file)) {
      default_data
    } else {
      read.csv(input$file$datapath)
    }
  })
  
  # Render map
  output$map <- renderLeaflet({
    df <- data_reactive()
    req(df$lat, df$lon)
    
    leaflet(df) %>%
      addTiles() %>%  # Add default OpenStreetMap tiles
      addCircleMarkers(
        lng = ~lon,
        lat = ~lat,
        label = ~name,
        radius = 6,
        color = "blue",
        fillOpacity = 0.7
      )
  })
  
  # Render scrollable table
  output$table <- renderDT({
    datatable(data_reactive(), options = list(scrollX = TRUE, pageLength = 5))
  })
}

# ---- Run App ----
shinyApp(ui = ui, server = server)

Default Output

Uploaded CSV Output

3. Uploading a Shapefile

This section is inspired by Paula Moragaโ€™s excellent book Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny (2019), published by Chapman & Hall/CRC Biostatistics Series. An online version of the book, along with Shiny code examples, is available here.

The accompanying datasets can also be accessed via the authorโ€™s GitHub repository.

Code

# Load Required Libraries
library(shiny)
library(sf)
library(DT)
library(leaflet)

# Define the User Interface (UI)
ui <- fluidPage(
  titlePanel("Upload and View Shapefile"),
  
  sidebarLayout(
    sidebarPanel(
      fileInput(
        inputId = "filemap",
        label = "Upload shapefile (select all related files)",
        multiple = TRUE,
        accept = c('.shp','.dbf','.sbn','.sbx','.shx','.prj')
      )
    ),
    
    mainPanel(
      leafletOutput("map", height = 400),
      DTOutput("table")
    )
  )
)

# Define the Server Logic
server <- function(input, output) {
  
  map <- reactive({
    req(input$filemap)
    shpdf <- input$filemap
    tempdirname <- dirname(shpdf$datapath[1])
    
    # Rename files to original names
    for (i in 1:nrow(shpdf)) {
      file.rename(shpdf$datapath[i],
                  file.path(tempdirname, shpdf$name[i]))
    }
    
    # Get the .shp file path
    shp_file <- shpdf$name[grep("\\.shp$", shpdf$name)]
    req(length(shp_file) == 1)
    
    # Read shapefile using sf
    sf::st_read(file.path(tempdirname, shp_file))
  })
  
  output$table <- renderDT({
    req(map())
    datatable(map())
  })
  
  output$map <- renderLeaflet({
    req(map())
    leaflet() %>%
      addTiles() %>%
      addPolygons(data = map(), weight = 1, color = "#444444", fillOpacity = 0.5)
  })
}

# Run the App
shinyApp(ui = ui, server = server)

Output

4. Spatio-temporal Analysis

This section too is inspired by Paula Moragaโ€™s excellent book Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny (2019), published by Chapman & Hall/CRC Biostatistics Series. An online version of the book, along with Shiny code examples, is available here. The dataset used in this section is the same as in Section 3.

Code

#Load Required Libraries

library(shiny)
library(leaflet)
library(rgdal)
library(DT)

# Load the data

data <- read.csv("data/Ohio/dataohiocomplete.csv")
map <- readOGR(dsn = "data/Ohio/fe_2007_39_county",layer = "fe_2007_39_county")


# Define UI
ui <- fluidPage(
  titlePanel("Advanced Map of Cases (1980)"),
  leafletOutput("map", height = 600),
  DTOutput("table")
)

# Define Server
server <- function(input, output) {
  output$map <- renderLeaflet({
    # Filter for year 1980
    data1980 <- subset(data, year == 1980)
    
    # Create new columns for clarity
    data1980$cases <- data1980$y
    data1980$county_name <- tolower(trimws(data1980$NAME))
    map_names <- tolower(trimws(map@data$NAME))
    
    # Match counties by name
    ordercounties <- match(map_names, data1980$county_name)
    matched <- !is.na(ordercounties)
    
    if (sum(matched) == 0) {
      showNotification("No matching counties found between data and shapefile", type = "error")
      return(NULL)
    }
    
    map <- map[matched, ]
    map@data <- data1980[ordercounties[matched], ]
    
    # Create color palette
    pal <- colorBin("YlOrRd", domain = map@data$cases, bins = 7)
    
    # Labels for interactivity
    labels <- lapply(
      paste0(map@data$NAME, "<br>Cases: ", map@data$cases),
      htmltools::HTML
    )
    
    # Render map
    leaflet(map) %>%
      addTiles() %>%
      addPolygons(
        fillColor = ~pal(map@data$cases),
        fillOpacity = 0.7,
        color = "white",
        weight = 1,
        label = labels,
        highlight = highlightOptions(
          weight = 2,
          color = "#666",
          fillOpacity = 0.9,
          bringToFront = TRUE
        )
      ) %>%
      addLegend(
        pal = pal,
        values = map@data$cases,
        title = "Cases (1980)",
        opacity = 0.7
      )
  })
  
  output$table <- renderDT({
    dataagg <- aggregate(cbind(y, n) ~ NAME + year, data = data, sum)
    dataagg$cases <- dataagg$y
    dataagg$population <- dataagg$n
    datatable(dataagg[, c("NAME", "year", "cases", "population")])
  })
  
}

# Run the App
shinyApp(ui = ui, server = server)

Output

๐Ÿš€ Dive in โ€” and letโ€™s learn from this awesome resource!