Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can you plot reactive data using editMod? #76

Open
m-e-cws opened this issue Apr 4, 2018 · 9 comments
Open

Can you plot reactive data using editMod? #76

m-e-cws opened this issue Apr 4, 2018 · 9 comments

Comments

@m-e-cws
Copy link

m-e-cws commented Apr 4, 2018

Hello,

I am self-taught in R and computer coding, so I apologize if this question is poorly worded or already answered elsewhere.

I am trying to plot a reactive dataset in editMod, while retaining the ability to use the edit (draw) functions provided by editMod. So far I have not found a way to plot reactive data using editMod.

I want a map that allows me to:

  • Plot spatial data that can be subsetted by some kind of reactive variable (below it is the "mag" variable)

  • Draw across a certain group of points and generate summary data based on the reactive subset of the plotted data.

Below is a reproducible example using the supplementary "quakes" dataset. In the below map, I can generate summary data based on a reactive dataset selected by the slider bar, but the reactive dataset is not reflected in what is plotted on the map.

library(mapview)
library(leaflet)
library(shiny)
library(ggplot2)
library(mapedit)
library(leaflet.extras)
library(sf)

ui.q <- fluidPage(
  
  # Application title
  titlePanel("Quake data exploration"),
  
  sidebarLayout(
    sidebarPanel(
      sliderInput("range", 
                  label = "Range of earthquake magnitude:",
                  min = 4.0, max = 6.4, value = c(4.0, 6.4), step = 0.1)
    ),
    
    mainPanel(
      fluidRow(
        
         editModUI("editor", height = 860),
                             
         absolutePanel(id = "controls", class = "panel panel-default", fixed = TRUE, 
                                           draggable = TRUE, top = 120, left = "auto", right = 20, bottom = "auto", 
                                           width = 400, height = "auto",
                                           
                                           h4("Summary data"),
                                           
                                           plotOutput("plot")))
        )
      )
    )

# Define server logic 
server.q <- function(input, output){
  
  #create a sf of the quake data.
  quake_mx <- data.matrix(quakes[,2:1])
  quake_mp <- st_multipoint(quake_mx)
  quake_sf <- st_sf(st_cast(st_sfc(quake_mp), "POINT"), quakes, crs=4326)
  
  
  #trying to subset the quake_sf data by the range of magnitude inputted by the slider bar. 
  #this creates a reactive dataframe and I can't seem to get it to be reflected in the editMod output.
  #I can get this to work when I use leafletProxy but then we lose the ability to draw.
  
  datasetMag <- reactive({
    quake_sf = subset(quake_sf, mag %in% c(input$range[1]:input$range[2]))
    })
    
#generate the leaflet map
  lf.quakes <- leaflet(options = leafletOptions(minZoom = 4, maxZoom = 10)) %>% 
    addTiles() %>%
    addProviderTiles("Esri.OceanBasemap",group="OceanBasemap") %>%
    addProviderTiles("Esri.WorldImagery",group="WorldImagery") %>%
    
    addCircleMarkers(data = quake_sf, 
                     color = "red",
                     weight = 1, 
                     fillOpacity = 0.7,
                     popup = popupTable(quake_sf, zcol = "mag"),
                     radius = quake_sf$mag) %>%
                                           
    addLayersControl(baseGroups = c("OceanBasemap","WorldImagery"),
                                  options = layersControlOptions(collapsed = FALSE))


  #call the editMod function from 'mapedit' to use in the leaflet map. I think this may be the line where I am going wrong

  edits <- callModule(editMod, "editor", leafmap = lf.quakes)

 
  #generate the reactive dataset based on what is drawn on the leaflet map
  datasetInput <- reactive({
    mag.range = c(input$range[1]:input$range[2])
    req(edits()$finished)
    quake_intersect <- st_intersection(edits()$finished, quake_sf)
    df <- data.frame(quake_intersect)
    df = df[df$mag %in% mag.range,] #make the dataset reactive to the slider input range
  }) 
  
  #render a histogram with the reactive dataset as the output
  
  output$plot <- renderPlot({
    hist(datasetInput()$mag, col = "grey", border = "black")
  })
}

shinyApp(ui.q, server.q)  
@timelyportfolio
Copy link
Contributor

@m-e-cws I will try to demonstrate a way of achieving your objective soon. Thanks for the report.

@davis3tnpolitics
Copy link

Just to bring this comment back up to top of mind- I can get reactivity to work after loading in the basemap with a leaflet proxy.

I was using isolate to break the reactivity before loading in the basemap, but I can’t get the basemap to change and display the reactive data I need.

Any thoughts on how to accomplish this?

@leungi
Copy link

leungi commented Feb 28, 2020

@davis3tnpolitics: I'm running into the same problem.

Here's an example taken from mapedit demo.

The code line of interest is callModule(editMod, "editor", mapview(qk_rv())@map), where qk_rv is intentionally set to a reactive object.

I've tried several suggestion from forums but no luck:

  • callModule(editMod, "editor", mapview(reactive(qk_rv))@map)
  • callModule(editMod, "editor", mapview(qk_rv)@map)

The only way to get it to work is with shiny::isolate() to get a static value for qk_rv. The downside is as @davis3tnpolitics noted - the qk_rv doesn't update as it should.

library(mapedit)
library(mapview)
library(shiny)
library(sf)

# make the coordinates a numeric matrix
qk_mx <- data.matrix(quakes[,2:1])
# convert the coordinates to a multipoint feature
qk_mp <- st_multipoint(qk_mx)
# convert the multipoint feature to sf
qk_sf <- st_sf(st_cast(st_sfc(qk_mp), "POINT"), quakes, crs=4326)

ui <- fluidPage(
  fluidRow(
    # edit module ui
    column(6, editModUI("editor")),
    column(
      6,
      h3("Boxplot of Depth"),
      plotOutput("selectstat")
    )
  )
)
server <- function(input, output, session) {
  qk_rv <- reactive({qk_sf})
  
  # edit module returns sf
  edits <- callModule(editMod, "editor", mapview(qk_rv())@map)
  
  output$selectstat <- renderPlot({
    req(edits()$finished)
    qk_intersect <- st_intersection(edits()$finished, qk_sf)
    req(nrow(qk_intersect) > 0) 
    boxplot(
      list(
        all = as.numeric(qk_sf$depth),
        selected = as.numeric(qk_intersect$depth)
      ),
      xlab = "depth"
    )
  })
}
shinyApp(ui, server)

@m-e-cws
Copy link
Author

m-e-cws commented Feb 28, 2020

Hi @davis3tnpolitics @leungi ,

Thank you both for bringing this issue back to my attention. I had originally posted this almost two years ago, and since then I have learned a lot about reactive datasets and how they interact with leaflet maps and shiny apps. I have managed to solve my own problem, and I should have posted my own solution sooner. Below is the code which allows to subset data in an interactive leaflet map by both a slider bar, and by drawing a shape over points.

Feel free to check out a map I made which includes some extra tools for subsetting and selecting data in a leaflet map: https://englishm.shinyapps.io/Plastics/

library(mapview)
library(leaflet)
library(shiny)
library(ggplot2)
library(mapedit)
library(leaflet.extras)
library(sf)

ui.q <- fluidPage(
  
  #Application title
  titlePanel("Quake data exploration"),
  
  sidebarLayout(
    sidebarPanel(
      sliderInput("range", 
                  label = "Range of earthquake magnitude:",
                  min = 4.0, max = 6.4, value = c(4.0, 6.4), step = 0.1)
    ),
    
    mainPanel(
      fluidRow(
        
        editModUI("editor", height = 860),
        
        absolutePanel(id = "controls", class = "panel panel-default", fixed = TRUE, 
                      draggable = TRUE, top = 120, left = "auto", right = 20, bottom = "auto", 
                      width = 400, height = "auto",
                      
                      h4("Summary data"),
                      
                      plotOutput("plot")))
    )
  )
)

#Define server logic 
server.q <- function(input, output){
  
  #create a sf of the quake data.
  quake_mx <- data.matrix(quakes[,2:1])
  quake_mp <- st_multipoint(quake_mx)
  quake_sf <- st_sf(st_cast(st_sfc(quake_mp), "POINT"), quakes, crs=4326)
  
  
  #subset the quake_sf data by the range of magnitude inputted by the slider bar. 
  #this creates a reactive dataframe.

  datasetMag <- reactive({
    quake_sf[quake_sf$mag >= input$range[1] & quake_sf$mag <= input$range[2],]
  })
  
  #generate the leaflet map
  
  #set the namespace for the map
  ns <- shiny::NS("editor")
  
  lf.quakes <- leaflet(options = leafletOptions(minZoom = 4, maxZoom = 10)) %>% 
    addTiles() %>%
    addProviderTiles("Esri.OceanBasemap",group="OceanBasemap") %>%
    addProviderTiles("Esri.WorldImagery",group="WorldImagery") %>%
    
    addCircleMarkers(data = quake_sf,  #use the non-reactive dataset
                     color = "red",
                     weight = 1, 
                     fillOpacity = 0.7,
                     popup = popupTable(quake_sf, zcol = "mag"),
                     radius = quake_sf$mag) %>%
    
    addLayersControl(baseGroups = c("OceanBasemap","WorldImagery"),
                     options = layersControlOptions(collapsed = FALSE))
  
  
  #call the editMod function from 'mapedit' to use in the leaflet map.
  
  edits <- callModule(editMod, "editor", leafmap = lf.quakes)
  
  
  #now we need to create an observer so the leaflet map can 'observe' the reactive dataset and be redrawn based on the input
  
  observeEvent(c(input$range),{  
    proxy.lf <- leafletProxy(ns("map"))
    req(input$range)
    req(nrow(datasetMag())>0)
    
    proxy.lf %>%
      clearMarkers() %>%
      addCircleMarkers(data = datasetMag(),  #use the reactive dataset generated above
                       color = "red",
                       weight = 1, 
                       fillOpacity = 0.7,
                       popup = popupTable(quake_sf, zcol = "mag"),
                       radius = quake_sf$mag) 
  })
  
  
  #generate the reactive dataset based on what is drawn on the leaflet map
  datasetInput <- reactive({
    req(edits()$finished)
    quake_intersect <- st_intersection(edits()$finished, datasetMag()) #intersection between what is drawn and the reactive dataframe
    df <- data.frame(quake_intersect)
  })
  
  #render a histogram with the reactive dataset as the output
  
  output$plot <- renderPlot({
    hist(datasetInput()$mag, col = "grey", border = "black")
  })
}

shinyApp(ui.q, server.q)

@leungi
Copy link

leungi commented Feb 29, 2020

Thanks prompt response and sharing @m-e-cws!

The commenting really helps with my understanding 🙏

@leungi
Copy link

leungi commented Mar 1, 2020

@m-e-cws: have you encountered a case for your solution above where the CRS of the initial map (i.e. lf.quakes) is different than that of the edits() object?

For such a case, even after setting the edits() object's CRS to match the initial map, st_intersection() somehow returns empty.

I posted this issue with sf.

@tim-salabim
Copy link
Member

tim-salabim commented Mar 2, 2020

Just to be clear, setting a crs won't change the coordinates. You need to st_transform your object

@leungi
Copy link

leungi commented Mar 2, 2020

Thanks for tip @tim-salabim!

This is one of those rare moments where I have to deal with objects with NA_crs_.

I tried your suggestion, but got the following error, which is logical given st_transform() expects a EPSG, or proj4string code.

# edits() is objected returned from mapedit selection
selected <- edits()$finished

selected_trf <- selected %>% st_transform(crs = NA_crs_)

#> Error in st_transform.sfc: sfc object should have crs set

@JavierMorales2
Copy link

JavierMorales2 commented Mar 24, 2023

Hello guys!

Any update about the reactive data using editMod? I'm trying to make something similar but can´t add a reactive variable or leaflet without an error on CallModule(editMod).

I want that one can upload a Shapefile, and it could be edited there

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants