Skip to contents

Two independent maps share the same bscode shell without sharing a panel. Switching sections tests whether both widgets recover their full dimensions correctly.

This example checks:

  • Two independent Leaflet widgets in one Shiny application.
  • Full-viewport htmlwidgets with floating controls.
  • Resize events when the selected navigation panel changes.

Live app

The first load may take a moment. The application runs entirely in the browser through WebAssembly.

Open live app

Source code

library(shiny)
library(bslib)
library(bscode)

if (!requireNamespace("leaflet", quietly = TRUE)) {
  stop("This example requires: leaflet.")
}

places <- data.frame(
  name = c("Santiago", "Valparaiso", "Rancagua", "Concepcion"),
  lng = c(-70.6483, -71.6127, -70.7406, -73.0498),
  lat = c(-33.4569, -33.0472, -34.1708, -36.8270)
)

ui <- page_bscode(
  title = "Maps",
  id = "main_nav",

  nav_panel(
    "Network",
    value = "network",
    icon = icon("route"),
    div(
      style = "position:relative; width:100%; height:100%; min-height:0;",
      leaflet::leafletOutput("network_map", width = "100%", height = "100%"),
      absolutePanel(
        top = 16,
        left = 16,
        width = 300,
        class = "card shadow",
        div(
          class = "card-body",
          h5("Network map", class = "card-title"),
          p("A full-screen Leaflet map inside a fillable navigation panel."),
          actionButton(
            "open_terrain",
            "Open terrain",
            icon = icon("mountain-sun"),
            class = "btn-primary btn-sm"
          )
        )
      )
    )
  ),

  nav_panel(
    "Terrain",
    value = "terrain",
    icon = icon("mountain-sun"),
    div(
      style = "position:relative; width:100%; height:100%; min-height:0;",
      leaflet::leafletOutput("terrain_map", width = "100%", height = "100%"),
      absolutePanel(
        top = 16,
        left = 16,
        width = 300,
        class = "card shadow",
        div(
          class = "card-body",
          h5("Terrain map", class = "card-title"),
          p("A second independent map using the same full-screen shell."),
          actionButton(
            "open_network",
            "Open network",
            icon = icon("route"),
            class = "btn-primary btn-sm"
          )
        )
      )
    )
  ),

  nav_spacer(),

  nav_panel(
    "Information",
    value = "info",
    icon = icon("circle-info"),
    div(
      class = "p-4",
      h2("Two maps, one activity bar"),
      p(
        "The maps live in separate navigation panels. Switching panels tests ",
        "that each htmlwidget receives a resize event and fills the available ",
        "viewport without interfering with the other."
      ),
      actionButton("back_network", "Back to network", icon = icon("arrow-left"))
    )
  )
)

server <- function(input, output, session) {
  output$network_map <- leaflet::renderLeaflet({
    map <- leaflet::leaflet(
      places,
      options = leaflet::leafletOptions(zoomControl = FALSE)
    ) |>
      leaflet::addProviderTiles(leaflet::providers$CartoDB.Positron) |>
      leaflet::addCircleMarkers(
        lng = ~lng,
        lat = ~lat,
        radius = 7,
        stroke = TRUE,
        fillOpacity = 0.85,
        label = ~name
      )

    for (index in seq.int(2, nrow(places))) {
      map <- leaflet::addPolylines(
        map,
        lng = c(places$lng[1], places$lng[index]),
        lat = c(places$lat[1], places$lat[index]),
        weight = 2,
        opacity = 0.75
      )
    }

    leaflet::fitBounds(
      map,
      lng1 = min(places$lng) - 0.5,
      lat1 = min(places$lat) - 0.5,
      lng2 = max(places$lng) + 0.5,
      lat2 = max(places$lat) + 0.5
    )
  })

  output$terrain_map <- leaflet::renderLeaflet({
    leaflet::leaflet(
      places,
      options = leaflet::leafletOptions(zoomControl = FALSE)
    ) |>
      leaflet::addProviderTiles(leaflet::providers$OpenTopoMap) |>
      leaflet::addMarkers(lng = ~lng, lat = ~lat, label = ~name) |>
      leaflet::fitBounds(
        lng1 = min(places$lng) - 0.5,
        lat1 = min(places$lat) - 0.5,
        lng2 = max(places$lng) + 0.5,
        lat2 = max(places$lat) + 0.5
      )
  })

  observeEvent(input$open_terrain, nav_select("main_nav", "terrain"))
  observeEvent(input$open_network, nav_select("main_nav", "network"))
  observeEvent(input$back_network, nav_select("main_nav", "network"))
}

shinyApp(ui, server)

View the original app.R