Making choropleths with leaflet is easy. In this example, we’ll duplicate the step-by-step choropleth tutorial from the Leaflet.js website.
The final result will look like this (scroll to the end to see the completed code):
#> Warning in sf::st_is_longlat(x): bounding box has potentially an invalid
#> value range for longlat data
Data source
We’ll start by loading the data from JSON. While the Leaflet.js example loads the JSON directly into JavaScript, with the Leaflet R package we instead want to load the data into R.
In this case, we’ll use the sf package to load the
data into an sf
data.frame
, which will let us
easily manipulate the geographic features, and their properties, in
R.
states <- sf::read_sf("https://rstudio.github.io/leaflet/json/us-states.geojson")
class(states)
#> [1] "sf" "tbl_df" "tbl" "data.frame"
names(states)
#> [1] "id" "name" "density" "geometry"
As you can see, we now have an sf
data.frame
with name
(state name) and
density
(population density in people/mi2)
columns from the GeoJSON.
Basic states map
Next, let’s make a basic map with just the outline of the states. For
our basemap, we’ll use the same "mapbox.light"
MapBox style
that the example does; if you don’t have a MapBox account, you can just
use addTiles()
in place of the
addProviderTiles()
call, or choose a free provider.
m <- leaflet(states) %>%
setView(-96, 37.8, 4) %>%
addProviderTiles("MapBox", options = providerTileOptions(
id = "mapbox.light",
accessToken = Sys.getenv('MAPBOX_ACCESS_TOKEN')))
We’ve saved the basic basemap as a separate variable m
so we can easily iterate on the addPolygons()
call as we
work through the rest of the tutorial.
To add uniform polygons with default styling, just call
addPolygons()
with no additional arguments.
m %>% addPolygons()
#> Warning in sf::st_is_longlat(x): bounding box has potentially an invalid
#> value range for longlat data
Adding some color
Now, let’s color the states according to their population density. You have various options for mapping data to colors; for this example we’ll match the Leaflet.js tutorial by mapping a specific set of bins into RColorBrewer colors.
First, we’ll define the bins. This is a numeric vector that defines
the boundaries between intervals ((0,10]
,
(10,20]
, and so on).
Then, we’ll call colorBin()
to generate a palette
function that maps the RColorBrewer "YlOrRd"
colors to our
bins.
Finally, we’ll modify addPolygons()
to use the palette
function and the density values to generate a vector of colors for
fillColor
, and also add some other static style
properties.
bins <- c(0, 10, 20, 50, 100, 200, 500, 1000, Inf)
pal <- colorBin("YlOrRd", domain = states$density, bins = bins)
m %>% addPolygons(
fillColor = ~pal(density),
weight = 2,
opacity = 1,
color = "white",
dashArray = "3",
fillOpacity = 0.7)
#> Warning in sf::st_is_longlat(x): bounding box has potentially an invalid
#> value range for longlat data
Adding interaction
The next thing we’ll want is to make the polygons highlight as the
mouse passes over them. The addPolygon()
function has a
highlight
argument that makes this simple.
m %>% addPolygons(
fillColor = ~pal(density),
weight = 2,
opacity = 1,
color = "white",
dashArray = "3",
fillOpacity = 0.7,
highlightOptions = highlightOptions(
weight = 5,
color = "#666",
dashArray = "",
fillOpacity = 0.7,
bringToFront = TRUE))
#> Warning in sf::st_is_longlat(x): bounding box has potentially an invalid
#> value range for longlat data
(The Leaflet.js tutorial also adds an event handler that zooms into a state when it’s clicked. This isn’t currently possible with the Leaflet R package, except with either custom JavaScript or using Shiny, both of which are outside the scope of this example.)
Custom info
Now let’s expose the state names and values to the user.
The Leaflet.js tutorial shows the hovered-over state’s information in a custom control. Again, that’s possible by adding custom JavaScript or using Shiny, but for this example we’ll use the built-in labels feature instead.
We’ll generate the labels by handcrafting some HTML, and passing it
to lapply(htmltools::HTML)
so that Leaflet knows to treat
each label as HTML instead of as plain text. We’ll also set some label
options to improve the style of the label element itself.
labels <- sprintf(
"<strong>%s</strong><br/>%g people / mi<sup>2</sup>",
states$name, states$density
) %>% lapply(htmltools::HTML)
m <- m %>% addPolygons(
fillColor = ~pal(density),
weight = 2,
opacity = 1,
color = "white",
dashArray = "3",
fillOpacity = 0.7,
highlightOptions = highlightOptions(
weight = 5,
color = "#666",
dashArray = "",
fillOpacity = 0.7,
bringToFront = TRUE),
label = labels,
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "15px",
direction = "auto"))
#> Warning in sf::st_is_longlat(x): bounding box has potentially an invalid
#> value range for longlat data
m
This is the final version of our polygon layer, so let’s save the
result back to the m
variable.
Legend
As our final step, let’s add a legend. Because we chose to color our
map using colorBin()
, the addLegend()
function
makes it particularly easy to add a legend with the correct colors and
intervals.
Complete code
# From https://leafletjs.com/examples/choropleth/us-states.js
states <- sf::read_sf("https://rstudio.github.io/leaflet/json/us-states.geojson")
bins <- c(0, 10, 20, 50, 100, 200, 500, 1000, Inf)
pal <- colorBin("YlOrRd", domain = states$density, bins = bins)
labels <- sprintf(
"<strong>%s</strong><br/>%g people / mi<sup>2</sup>",
states$name, states$density
) %>% lapply(htmltools::HTML)
leaflet(states) %>%
setView(-96, 37.8, 4) %>%
addProviderTiles("MapBox", options = providerTileOptions(
id = "mapbox.light",
accessToken = Sys.getenv('MAPBOX_ACCESS_TOKEN'))) %>%
addPolygons(
fillColor = ~pal(density),
weight = 2,
opacity = 1,
color = "white",
dashArray = "3",
fillOpacity = 0.7,
highlightOptions = highlightOptions(
weight = 5,
color = "#666",
dashArray = "",
fillOpacity = 0.7,
bringToFront = TRUE),
label = labels,
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "15px",
direction = "auto")) %>%
addLegend(pal = pal, values = ~density, opacity = 0.7, title = NULL,
position = "bottomright")
#> Warning in sf::st_is_longlat(x): bounding box has potentially an invalid
#> value range for longlat data