Skip to content

Instantly share code, notes, and snippets.

@PaulC91
Created March 31, 2020 11:18
Show Gist options
  • Save PaulC91/31c05f84b25975047092f13c2474507a to your computer and use it in GitHub Desktop.
Save PaulC91/31c05f84b25975047092f13c2474507a to your computer and use it in GitHub Desktop.
Add a custom circle legend to leaflet map in R
#' Add custom circle legend to leaflet map
#'
#' To be used alongside \code{leaflet::addCircleMarkers}
#'
#' @param map a leaflet map
#' @param title title of the legend
#' @param range vector of numeric values you want to scale the legend to (same vector used with addCircleMarkers)
#' @param scaling_fun the scaling function used with addCircleMarkers to scale circle radii appropriately for leaflet
#' @param color stroke color
#' @param weight stroke width in pixels
#' @param fillColor fill color
#' @param fillOpacity fill opacity
#' @param position the position of the legend
#' @param layerId the ID of the legend; subsequent calls to \code{addLegend}
#' or \code{addControl} with the same \code{layerId} will replace this
#' legend. The ID can also be used with \code{removeControl}.
addCircleLegend <- function(
map, title = "", range, scaling_fun, ...,
color, weight, fillColor, fillOpacity,
position = c("topright", "bottomright", "bottomleft", "topleft"),
data = leaflet::getMapData(map), layerId = NULL) {
range <- base::pretty(sort(range), 20)
range <- range[range != 0]
min_n <- ceiling(min(range, na.rm = TRUE))
med_n <- round(median(range, na.rm = TRUE), 0)
max_n <- round(max(range, na.rm = TRUE), 0)
n_range <- c(min_n, med_n, max_n)
radii <- scaling_fun(n_range, ...)
n_range <- scales::label_number_si()(n_range)
circle_style <- glue::glue(
"border-radius:50%;
border: {weight}px solid {color};
background: {paste0(fillColor, round(fillOpacity*100, 0))};
position: absolute;
bottom:1px;
right:25%;
left:50%;"
)
text_style <- glue::glue(
"text-align: right;
font-size: 11px;
position: absolute;
bottom: 0px;
right:1px;"
)
circle_legend <- htmltools::HTML(glue::glue(
'<div class="bubble-legend">
<div id="legendTitle" style="text-align: center; font-weight: bold;">{title}</div>
<div class="symbolsContainer" style="min-width: {radii[3]*2 + 20}px; min-height: {radii[3]*2}px;">
<div class="legendCircle" style="width: {radii[3] * 2}px; height: {radii[3] * 2}px; margin-left: {-radii[3]}px; {circle_style}"></div>
<div class="legendCircle" style="width: {radii[2] * 2}px; height: {radii[2] * 2}px; margin-left: {-radii[2]}px; {circle_style}"></div>
<div class="legendCircle" style="width: {radii[1] * 2}px; height: {radii[1] * 2}px; margin-left: {-radii[1]}px; {circle_style}"></div>
<div>
<p class="legendValue" style="margin-bottom: {radii[1] * 2 - 12}px; {text_style}">{n_range[1]}</p>
</div>
<div>
<p class="legendValue" style="margin-bottom: {radii[2] * 2 - 12}px; {text_style}">{n_range[2]}</p>
</div>
<div>
<p class="legendValue" style="margin-bottom: {radii[3] * 2 - 12}px; {text_style}">{n_range[3]}</p>
</div>
</div>
</div>'
))
return(
leaflet::addControl(map, html = circle_legend, position = position, layerId = layerId)
)
}
# example =========================================
library(leaflet)
df <- tibble::tibble(
lng = (runif(20) - .5) * 10 - 90.620130, # lng
lat = (runif(20) - .5) * 3.8 + 25.638077, # lat
size = runif(20, min = 1, max = 1000)
)
# make a scaling function to convert real numbers to radii appropriate for leaflet
calc_radius <- function(n, scale_factor = 30) {
sqrt(n)/sqrt(max(n)) * scale_factor
}
leaflet(df) %>%
addTiles() %>%
addCircleMarkers(
lng = ~lng,
lat = ~lat,
radius = ~calc_radius(size),
fillColor = "#57AACB",
fillOpacity = 0.8,
weight = 1,
color = "#FFFFFF"
) %>%
addCircleLegend(
title = "Circle Legend",
range = df$size,
scaling_fun = calc_radius,
fillColor = "#57AACB",
fillOpacity = 0.8,
weight = 1,
color = "#FFFFFF",
position = "topright"
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment