rshinyr-leafletr-dygraphs

Integrating time series graphs and leaflet maps using R shiny


I have data/results that contain both a geocode location (latitude/longitude) and a date/time stamp that I would like to interact with using R shiny. I have created R shiny apps that contain several leaflet maps (leaflet R package) and also contain time series graphs (dygraphs R package). I know how to synchronize different dygraphs (https://rstudio.github.io/dygraphs/gallery-synchronization.html), but not sure how to synchronize it to a leaflet map too. My question is how best to link all the graphs together, so when I select a region on a leaflet map or period of time on a dygraph time series graph the other graphs are all updated to show only that filtered data?

One thought I had was to use a leaflet plugin, but not sure how to do this with R/shiny? For example, I see some leaflet plugins offer the capability to animate a map that contains date/time information (http://apps.socib.es/Leaflet.TimeDimension/examples/). Another question is there any documentation/examples showing how to work with leaflet plugins using R shiny?

I think it is possible to extract the time/date that is selected from a time series graph (dygraph), but not sure if/how to extract the region that is displayed on the leaflet map in R shiny. My last question is whether if it is possible how I could extract the region over which the leaflet map is displayed, so I can update the time series graph.


Solution

  • This will probably be more of a continuous discussion than a single answer.

    Fortunately, your question involves htmlwidgets created by RStudio who also made Shiny. They have taken extra effort to integrate Shiny communication into both dygraphs and leaflet. This is not the case for many other htmlwidgets. For a broader discussion of intra-htmlwidget communication outside of Shiny, I would recommend following this Github issue.

    part 1 - leaflet control dygraph

    As my first example, we'll let leaflet control dygraphs, so clicking on a state in Mexico will limit the dygraph plot to just that state. I should give credit to these three examples.

    1. Kyle Walker's Rpub Mexico Choropleth Leaflet
    2. Shiny example included in leaflet
    3. Diego Valle Crime in Mexico project

    R Code

      # one piece of an answer to this StackOverflow question
      #  http://stackoverflow.com/questions/31814037/integrating-time-series-graphs-and-leaflet-maps-using-r-shiny
    
      # for this we'll use Kyle Walker's rpubs example
      #   http://rpubs.com/walkerke/leaflet_choropleth
      # combined with data from Diego Valle's crime in Mexico project
      #   https://github.com/diegovalle/mxmortalitydb
    
      # we'll also build on the shiny example included in leaflet
      #  https://github.com/rstudio/leaflet/blob/master/inst/examples/shiny.R
    
      library(shiny)
      library(leaflet)
      library(dygraphs)
      library(rgdal)
    
      # let's build this in advance so we don't download the
      #    data every time
      tmp <- tempdir()
      url <- "http://personal.tcu.edu/kylewalker/data/mexico.zip"
      file <- basename(url)
      download.file(url, file)
      unzip(file, exdir = tmp)
      mexico <- {
        readOGR(dsn = tmp, layer = "mexico", encoding = "UTF-8")
        #delete our files since no longer need
        on.exit({unlink(tmp);unlink(file)})
      }
      pal <- colorQuantile("YlGn", NULL, n = 5)
    
      leaf_mexico <- leaflet(data = mexico) %>%
        addTiles() %>%
        addPolygons(fillColor = ~pal(gdp08), 
                    fillOpacity = 0.8, 
                    color = "#BDBDC3", 
                    weight = 1,
                    layerId = ~id)
    
      # now let's get our time series data from Diego Valle
      crime_mexico <- jsonlite::fromJSON(
        "https://rawgit.com/diegovalle/crimenmexico.diegovalle.net/master/assets/json/states.json"
      )
    
      ui <- fluidPage(
        leafletOutput("map1"),
        dygraphOutput("dygraph1",height = 200),
        textOutput("message", container = h3)
      )
    
      server <- function(input, output, session) {
        v <- reactiveValues(msg = "")
    
        output$map1 <- renderLeaflet({
          leaf_mexico
        })
    
        output$dygraph1 <- renderDygraph({
          # start dygraph with all the states
          crime_wide <- reshape(
            crime_mexico$hd[,c("date","rate","state_code"),drop=F],
            v.names="rate",
            idvar = "date",
            timevar="state_code",
            direction="wide"
          )
          colnames(crime_wide) <- c("date",as.character(mexico$state))
          rownames(crime_wide) <- as.Date(crime_wide$date)
          dygraph(
            crime_wide[,-1]
          )
        })
    
        observeEvent(input$map1_shape_mouseover, {
          v$msg <- paste("Mouse is over shape", input$map1_shape_mouseover$id)
        })
        observeEvent(input$map1_shape_mouseout, {
          v$msg <- ""
        })
        observeEvent(input$map1_shape_click, {
          v$msg <- paste("Clicked shape", input$map1_shape_click$id)
          #  on our click let's update the dygraph to only show
          #    the time series for the clicked
          state_crime_data <- subset(crime_mexico$hd,state_code == input$map1_shape_click$id)
          rownames(state_crime_data) <- as.Date(state_crime_data$date)
          output$dygraph1 <- renderDygraph({
            dygraph(
              xts::as.xts(state_crime_data[,"rate",drop=F]),
              ylab = paste0(
                "homicide rate ",
                as.character(mexico$state[input$map1_shape_click$id])
              )
            )
          })
        })
        observeEvent(input$map1_zoom, {
          v$msg <- paste("Zoom changed to", input$map1_zoom)
        })
        observeEvent(input$map1_bounds, {
          v$msg <- paste("Bounds changed to", paste(input$map1_bounds, collapse = ", "))
        })
    
        output$message <- renderText(v$msg)
      }
    
      shinyApp(ui, server)
    

    part 2 dygraph control leaflet + part 1 leaflet control dygraph

    # one piece of an answer to this StackOverflow question
    #  http://stackoverflow.com/questions/31814037/integrating-time-series-graphs-and-leaflet-maps-using-r-shiny
    
    # for this we'll use Kyle Walker's rpubs example
    #   http://rpubs.com/walkerke/leaflet_choropleth
    # combined with data from Diego Valle's crime in Mexico project
    #   https://github.com/diegovalle/mxmortalitydb
    
    # we'll also build on the shiny example included in dygraphs
    #  https://github.com/rstudio/leaflet/blob/master/inst/examples/shiny.R
    
    library(shiny)
    library(leaflet)
    library(dygraphs)
    library(dplyr)
    library(rgdal)
    
    # let's build this in advance so we don't download the
    #    data every time
    tmp <- tempdir()
    url <- "http://personal.tcu.edu/kylewalker/data/mexico.zip"
    file <- basename(url)
    download.file(url, file)
    unzip(file, exdir = tmp)
    mexico <- {
      #delete our files since no longer need
      on.exit({unlink(tmp);unlink(file)})  
      readOGR(dsn = tmp, layer = "mexico", encoding = "UTF-8")
    }
    
    # now let's get our time series data from Diego Valle
    crime_mexico <- jsonlite::fromJSON(
      "https://rawgit.com/diegovalle/crimenmexico.diegovalle.net/master/assets/json/states.json"
    )
    
    # instead of the gdp data, let's use mean homicide_rate
    #   for our choropleth
    mexico$homicide <- crime_mexico$hd %>%
      group_by( state_code ) %>%
      summarise( homicide = mean(rate) ) %>%
      ungroup() %>%
      select( homicide ) %>%
      unlist
    
    
    pal <- colorBin(
      palette = RColorBrewer::brewer.pal(n=9,"YlGn")[-(1:2)]
      , domain = c(0,50)
      , bins =7
    )
    
    popup <- paste0("<strong>Estado: </strong>", 
                          mexico$name, 
                          "<br><strong>Homicide Rate: </strong>", 
                          round(mexico$homicide,2)
              )
    
    leaf_mexico <- leaflet(data = mexico) %>%
      addTiles() %>%
      addPolygons(fillColor = ~pal(homicide), 
                  fillOpacity = 0.8, 
                  color = "#BDBDC3", 
                  weight = 1,
                  layerId = ~id,
                  popup = popup
                  )
    
    
    ui <- fluidPage(
      leafletOutput("map1"),
      dygraphOutput("dygraph1",height = 200),
      textOutput("message", container = h3)
    )
    
    server <- function(input, output, session) {
      v <- reactiveValues(msg = "")
    
      output$map1 <- renderLeaflet({
        leaf_mexico
      })
    
      output$dygraph1 <- renderDygraph({
        # start dygraph with all the states
        crime_wide <- reshape(
          crime_mexico$hd[,c("date","rate","state_code"),drop=F],
          v.names="rate",
          idvar = "date",
          timevar="state_code",
          direction="wide"
        )
        colnames(crime_wide) <- c("date",as.character(mexico$state))
        rownames(crime_wide) <- as.Date(crime_wide$date)
        dygraph( crime_wide[,-1])  %>%
          dyLegend( show = "never" )
      })
    
      observeEvent(input$dygraph1_date_window, {
        if(!is.null(input$dygraph1_date_window)){
          # get the new mean based on the range selected by dygraph
          mexico$filtered_rate <- crime_mexico$hd %>%
          filter( 
                  as.Date(date) >= as.Date(input$dygraph1_date_window[[1]]),
                  as.Date(date) <= as.Date(input$dygraph1_date_window[[2]])  
                ) %>%
          group_by( state_code ) %>%
          summarise( homicide = mean(rate) ) %>%
          ungroup() %>%
          select( homicide ) %>%
          unlist
    
          # leaflet comes with this nice feature leafletProxy
          #  to avoid rebuilding the whole map
          #  let's use it
          leafletProxy( "map1", data = mexico  ) %>%
            removeShape( layerId = ~id ) %>%
            addPolygons( fillColor = ~pal( filtered_rate ), 
                        fillOpacity = 0.8, 
                        color = "#BDBDC3", 
                        weight = 1,
                        layerId = ~id,
                        popup = paste0("<strong>Estado: </strong>", 
                            mexico$name, 
                            "<br><strong>Homicide Rate: </strong>", 
                            round(mexico$filtered_rate,2)
                        )
                        )
        }
      })
    
      observeEvent(input$map1_shape_click, {
        v$msg <- paste("Clicked shape", input$map1_shape_click$id)
        #  on our click let's update the dygraph to only show
        #    the time series for the clicked
        state_crime_data <- subset(crime_mexico$hd,state_code == input$map1_shape_click$id)
        rownames(state_crime_data) <- as.Date(state_crime_data$date)
        output$dygraph1 <- renderDygraph({
          dygraph(
            xts::as.xts(state_crime_data[,"rate",drop=F]),
            ylab = paste0(
              "homicide rate ",
              as.character(mexico$state[input$map1_shape_click$id])
            )
          )
        })
      })
    
    }
    
    shinyApp(ui, server)