As a Seminarproject during my bachelorstudies I created an interactive map of Gießen with Quarto, communicating places connected to sustainability.

How I did it

library(crosstalk) # <- main library for static interactive R visualizations
library(leaflet)   # for maps
library(dplyr)     
library(reactable) # <- main library for static interactive R visualizations
library(DT)
library(htmltools)

Since I created my own custom icons I defined helperfunctions to implement these.

Iconheight <- 38
Iconwidth <- 38
 
myIcons <- iconList(
  Garten = makeIcon(iconUrl = "media/1.svg", 
                    iconWidth = Iconwidth, 
                    iconHeight = Iconheight),
  Gastronomie = makeIcon(iconUrl = "media/2.svg", 
                         iconWidth = Iconwidth,
                         iconHeight = Iconheight),
  Shop = makeIcon(iconUrl = "media/3.svg", 
                  iconWidth = Iconwidth, 
                  iconHeight = Iconheight),
  Teilen = makeIcon(iconUrl = "media/4.svg",
                    iconWidth = Iconwidth, 
                    iconHeight = Iconheight),
  Info = makeIcon(iconUrl = "media/6.svg",
                    iconWidth = Iconwidth, 
                    iconHeight = Iconheight)
  )

To make the map easy to update the place-data (title, description, coordinates, type) are stored in a csv file. The shared-data needs to be defined to make the filter possible. It is basically the conenction between the map and the filters.

# load data ----
locations <- read.csv("locations.csv") %>% 
  mutate(type = factor(type)) %>% 
  mutate(ID = row_number()) %>% 
  mutate(popup_content = paste(
    '<strong>', name, '</strong><br>',
    description, '<br>',
    ifelse(url != "", paste('<br><em>Website:</em> ', '<a href="', url, '" target="_blank">', substr(url, 9, nchar(url)), '</a>', '<br>'), ""),
    '<br><em>Type:</em> ', type
  ))
 
 
 
# create shared data for crosstalk
shared_locations <- SharedData$new(locations, key = ~ID) 
# implement filter ----
bscols(
  widths = c(8,4),
  HTML("<p></p>"),
  filter_select(
    id = "location_filter",
    label = "Filter Ortstyp",
    sharedData = shared_locations,
    group = ~type,
    allLevels = TRUE,
    multiple = TRUE
  )
)

Finally the main part of the code:

# Plots ----
 
bscols(
  widths = c(8,4),
  device = "sm",
 
## Map ----
    leaflet(shared_locations) %>% 
      addTiles() %>%
      setView(lng = 8.6731, lat = 50.5867, zoom = 13) %>% 
      addMarkers(
        lng = ~longitude,
        lat = ~latitude,
        icon = ~myIcons[as.character(type)],
        popup = ~popup_content) %>%
      addEasyButton(
        easyButton(
          icon="fa-map", 
          title="Standardansicht - Gießen",
          onClick=JS("function(btn, map){ map.setView([50.5867, 8.6731], 13);}")
          )
        ),
 
 
## Table ----
  datatable(shared_locations,
    extensions = c(
      "Buttons",  # add download buttons, etc
      "Scroller"  # for scrolling down the rows rather than pagination
    ),
    rownames = FALSE,  # remove rownames
    style = "bootstrap",
    class = "compact",
    width = "100%",
    options = list(
      initComplete = JS("function(settings, json) {",
                        "$(this.api().table().header()).css({'background-color': '#dfd4ae', 'color': '#19486a'});","}"),
      dom = "tiQ",  # specify content (search box, etc)
      deferRender = TRUE,
      scrollY = 300,
      scroller = TRUE,
      order = list(list(0, 'asc')),
      columnDefs = list(
        list(
          visible = FALSE,
          targets = c(1,2,3,4,6,7)
        )
      ), 
      buttons = list(
        I("colvis")  # turn columns on and off
        )
      ),
    colnames = c(
      "Name" = "name",
      "Beschreibung" = "description",
      "Website" = "url",
      "Longitude" = "longitude",
      "Latitude" = "latitude",
      "Typ" = "type",
      "ID" = "ID",
      "Marker Text" = "popup_content"
    )
  )
 
 
 
)

The entire project is public on my github: https://github.com/thhaase/interactive_map.