Search code examples
rshinyr-leaflet

Sync two leaftlet maps on different tabs in RMarkdown with Shiny


Running into issues syncing two leaflet maps on different tabs.

After looking at previous entries (Synchronizing two leaflet maps in R / Rmarkdown), the solution provided by @TimSalabim does not work because the maps are on different tabs.

Here is a MWE RMarkdown example:

---
title: "QuestionforStackOverflow"
output: 
flexdashboard::flex_dashboard:
runtime: shiny
---

```{r setup, include=FALSE}
library(flexdashboard)
library(shiny)
library(leaflet)
```

Tab One
======================================================================
```{r tab1}
output$map1 <-
   renderLeaflet(
    leaflet() %>%
      addProviderTiles("CartoDB.Positron") %>%
      setView(-93.65, 42.0285, zoom = 4)
  )

leafletOutput("map1")

```
Tab Two
======================================================================
```{r tab2}

output$map2 <-
  renderLeaflet(
    leaflet() %>%
      addProviderTiles("CartoDB.Positron") %>%
      setView(-93.65, 42.0285, zoom = 4)
  )

leafletOutput("map2")
```

I want a two way change. Any view changes to map1 -- changes map2 OR any changes to map2 will change map1.

Ideally: if you scroll into St. Louis on map1, map2 will have the same zoom level on St. Louis.

Right now, there is no interactivity between the two maps. Is there a way to make them sync?


Solution

  • You can use the leafletProxy() for this : See the help here : https://rstudio.github.io/leaflet/shiny.html

    For your particular problem, here's an idea :

    ---
      title: "QuestionforStackOverflow"
      output: 
        flexdashboard::flex_dashboard:
      runtime: shiny
    ---
    
    ```{r setup, include=FALSE}
      library(flexdashboard)
      library(shiny)
      library(leaflet)
    ```
    
    Tab One
    ======================================================================
    
    ```{r tab1}
    output$map1 <-
      renderLeaflet(
        leaflet() %>%
          addProviderTiles("CartoDB.Positron") %>%
          setView(-93.65, 42.0285, zoom = 4)
      )
    
    actionButton("syncMap1", "Fit to map2 bounds")
    leafletOutput("map1")
    
    observeEvent(input$syncMap1,{
      map2coords <- input$map2_bounds
      map1Proxy <- leafletProxy("map1")
      map1Proxy %>% fitBounds(lng1 = map2coords$east,
                              lat1 = map2coords$north,
                              lng2 = map2coords$west,
                              lat2 = map2coords$south)
    })
    ```
    
    Tab Two
    ======================================================================
    
    ```{r tab2}
    
    output$map2 <-
      renderLeaflet(
        leaflet() %>%
          addProviderTiles("CartoDB.Positron") %>%
          setView(-93.65, 42.0285, zoom = 4)
      )
    actionButton("syncMap2", "Fit to map1 bounds")
    leafletOutput("map2")
    
    observeEvent(input$syncMap2,{
      map1coords <- input$map1_bounds
      map2Proxy <- leafletProxy("map2")
      map2Proxy %>% fitBounds(lng1 = map1coords$east,
                              lat1 = map1coords$north,
                              lng2 = map1coords$west,
                              lat2 = map1coords$south)
    })
    ```
    

    The idea is to retrieve the coordinates of the other map when clicking on the buttons, and to sync the view then.

    Minor problem : the view is not that well synced : could be better to find the centroid of the displayed map and to use setView() using input$map1_zoom.

    Major problem : this means using buttons, what is not that user-friendly. Theoritically, you could use an observe() block to reflect the coordinates of each map to the other. Tried it and it's quite buggy, probably because of some "infinite" loop as there are micro-variations in the coordinates.