Search code examples
rleafletr-leaflet

Select field for leaflet color dynamically from a character string


The question is probably not too hard, but I couldn't find the words to properly google it.

I'm building a function in R that makes a leaflet map. The user will be able to chose the field he wants to use for colors in the form of a simple function argument field_color = "AREA" where AREA is the field name in the sf object.

Here is a reproducible example:

library(sf)  
library(leaflet)

# preparing the shapefile
nc <- st_read(system.file("gpkg/nc.gpkg", package="sf"), quiet = TRUE) %>% 
  st_transform(st_crs(4326)) %>% 
  st_cast('POLYGON')

# setting the colors
colpal <- colorNumeric(palette = "plasma", domain=nc$AREA, n=10)

# making the first map like in your example:
leaflet(nc) %>%
  addTiles() %>%
  addPolygons(color = ~colpal(AREA))

This code works and gives :

enter image description here

But in the preceding example, AREA is unquote. If I want it an argument, I need to call it that way:

chosen_field = "AREA"

# setting the colors
colpal2 <- colorNumeric(palette = "plasma", domain=nc[[chosen_field]], n=10)

# making the first map like in your example:
leaflet(nc) %>%
   addTiles() %>%
   addPolygons(color = ~colpal2(chosen_field))
Error in UseMethod("rescale") : 
  no applicable method for 'rescale' applied to an object of class "character"

That way, I could set chosen_field to the value I want to automatically change the color. However, it's not working and I get an error. I have a feeling it's some king of problem with Non Standard Evaluation or something but I don't really understand all of that. I played around function like quo, enquo, quo_name etc. without success.

What is the proper way to make that work?


Solution

  • Honestly, I'd suggest just sidestepping the issue by "pre-computing" your color data outside of the pipe, just as you've already pre-computed your color palette. That may feel inelegant, but then I'd argue that the convolutions magrittr et al. are forcing you into here are at least as awkward. Additionally, the approach I'm suggesting is exactly that used by the "pros" here in their production of this example leaflet app.

    Specifically, I'd use something like this:

    library(sf)  
    library(leaflet)
    
    ## User-selected parameter
    chosen_field <- "AREA"
    
    ## Shapefile preparation
    nc <- st_read(system.file("gpkg/nc.gpkg", package="sf"), quiet = TRUE) %>% 
      st_transform(st_crs(4326)) %>% 
      st_cast('POLYGON')
    
    ## Color setup
    colpal <- colorNumeric(palette = "plasma", domain=nc[[chosen_field]], n=10)
    colorData <- nc[[chosen_field]]
    
    ## Putting it all together
    leaflet(nc) %>%
      addTiles() %>%
        addPolygons(color = ~colpal(colorData))
    

    Alternatively, if you must do it the "rlang way", here is another solution, modeled off of the discussion recorded here. See how much more unreadable this is, though?

    library(sf)  
    library(leaflet)
    
    ## User-selected parameter
    chosen_field <- "AREA"
    
    ## Prep user-selected parameter for passage into pipe
    v <- as.symbol(chosen_field)
    v <- enquo(v)
    
    ## Shapefile preparation
    nc <- st_read(system.file("gpkg/nc.gpkg", package="sf"), quiet = TRUE) %>% 
      st_transform(st_crs(4326)) %>% 
      st_cast('POLYGON')
    
    ## Color setup
    colpal <- colorNumeric(palette = "plasma", domain=nc[[chosen_field]], n=10)
    colorData <- nc[[chosen_field]]
    
    ## Putting it all together
    rlang::eval_tidy(rlang::quo_squash(quo({
        leaflet(nc) %>%
          addTiles() %>%
            addPolygons(color = ~colpal(!!v))
    })))