There are several types of layers in toro:
- Circle
- Symbol
- Line
- Fill
- Text
Data set up
First, let’s set up the data we will plot. We will plot the cycle
hire stations and boroughs in London. The data is available in the
spData package.
library(toro)
library(sf)
#> Linking to GEOS 3.14.1, GDAL 3.12.1, PROJ 9.7.1; sf_use_s2() is TRUE
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
library(spData)
#> To access larger datasets in this package, install the spDataLarge
#> package with: `install.packages('spDataLarge',
#> repos='https://nowosad.github.io/drat/', type='source')`
cycle_hire <- spData::cycle_hire
# We will count the number of cycle hires in boroughs via a spatial join
borough_counts <- cycle_hire |>
dplyr::select(id) |>
sf::st_join(spData::lnd, join = sf::st_within) |>
dplyr::group_by(NAME) |>
dplyr::summarise(cycle_hires = dplyr::n(), .groups = "drop") |>
sf::st_drop_geometry() |>
dplyr::select(cycle_hires, NAME)
boroughs <- spData::lnd |>
dplyr::left_join(
borough_counts,
by = c("NAME")
) |>
dplyr::mutate(
cycle_hires = dplyr::if_else(is.na(cycle_hires), 0L, cycle_hires)
)Adding fill layers
Let’s start off by working with the borough data, which we have
created with a count of cycle hire stations in each borough. We can use
the add_fill_layer() function to add a fill layer to the
map. The add_fill_layer() function takes an id
and a source argument, which is the data we want to plot.
We can also specify a hover_column argument, which will
display the value of the specified column when the user hovers over a
feature on the map.
Note how we center the map on London using the center
argument in the map() function.
We can additionally set the bounds of the map to the data we are
plotting using the set_bounds() function. This will ensure
that the map is zoomed to the extent of the data.
map(
center = c(-0.1, 51.5),
) |>
set_bounds(boroughs, padding = 50) |>
add_fill_layer(
id = "boroughs",
source = boroughs
)To further customise the fill layer, we can use the
paint argument. This allows us to specify how the polygons
are styled.
Let’s use the paint argument to style the polygons based
on the number of cycle hire stations. We can use the
get_paint_options() function to specify the paint options
for the fill layer. In this example, we will use the
get_column_steps() function to specify that the fill colour
of the polygons should change based on the number of cycle hire
stations.
map(
center = c(-0.1, 51.5),
) |>
set_bounds(boroughs, padding = 50) |>
add_fill_layer(
id = "boroughs",
source = boroughs,
paint = get_paint_options(
"fill",
options = list(
colour = get_column_steps(
"cycle_hires",
breaks = pretty(boroughs$cycle_hires, n = 5),
values = rev(viridis::magma(6))
),
opacity = 0.3
)
)
)Initial interactivity can be added to the map by specifying a
hover_column argument. This will display the value of the
specified column when the user hovers over a feature on the map. We can
also specify a popup_column argument, which will display
the value of the specified column when the user clicks on a feature on
the map. Note how HTML can be used to format the text.
The get_column_steps() function takes the column name
from the source data, a vector of break points, and a vector of values
to assign to each break point.
boroughs <- boroughs |>
dplyr::mutate(
popup = paste0(
"<b>",
NAME,
"</b>: ",
cycle_hires,
" cycle hire stations"
)
)
map <- map(
center = c(-0.1, 51.5),
) |>
set_bounds(boroughs, padding = 50) |>
add_fill_layer(
id = "boroughs",
source = boroughs,
hover_column = "NAME",
popup_column = "popup",
paint = get_paint_options(
"fill",
options = list(
colour = get_column_steps(
"cycle_hires",
breaks = c(0, 1, 10, 100, 1000),
values = rev(viridis::magma(6)) # Values should be breaks + 1
),
opacity = 0.3
)
)
)
mapAs can be seen, the cycle hire stations are focused on the center of London.
Adding circle layers
Basic example
We can add a simple circle layer to the map to visualise the individual cycle hire stations. The syntax is similar to the previous functions.
cycle_hire <- cycle_hire |>
dplyr::mutate(
hover = paste0(
"<b>",
name,
"</b>:",
" ",
nbikes,
" bikes available",
", ",
nempty,
" empty docks"
)
)
map |>
set_bounds(cycle_hire, padding = 50) |>
add_circle_layer(
id = "cycle_hire",
source = cycle_hire,
hover_column = "hover",
paint = get_paint_options(
"circle",
options = list(
colour = get_column_steps(
"nbikes",
breaks = cycle_hire$nbikes |> quantile(),
values = viridis::viridis(6) # Values should be breaks + 1
)
)
)
)From there, we can use the layout argument to specify
how the circles are arranged on the map. In this example, we will use
the get_layout_options() function to specify that the
circles should be sorted based on the number of bikes. We can use the
get_column_steps() function to specify that the sort key
should change based on the number of bikes currently parked, with the
circles with the most bikes appearing on top of the circles with fewer
parked bikes.
map <- map |>
set_bounds(cycle_hire, padding = 50) |>
add_circle_layer(
id = "cycle_hire",
source = cycle_hire,
hover_column = "name",
paint = get_paint_options(
"circle",
options = list(
colour = get_column_steps(
"nbikes",
breaks = cycle_hire$nbikes |> quantile(),
values = viridis::viridis(6) # Values should be breaks + 1
),
circle_radius = 2 # You could make this dynamic with `get_column_steps()`
)
),
layout = get_layout_options(
"circle",
options = list(
"circle-sort-key" = get_column_steps(
"nbikes",
breaks = cycle_hire$nbikes |> quantile(),
values = 6:1 # Note how we reverse the order
)
)
)
)
mapNote how the layout option refers to the property as named in the
Mapbox GL JS documentation, which is circle-sort-key. A
full list of options for paints and layouts can be found here.
Adding symbol layers
To show icons on the map, we can use the
add_symbol_layer() function. The syntax is similar to the
previous functions, but we can specify an icon to use for the symbols.
In this example, we will use the add_image() function to
add a custom icon to the map, and then use the
get_column_steps() function to specify that the icon should
change based on the number of parked bikes.
icons <- list(
"pin1" = "../../man/figures/pin_1.png",
"pin2" = "../../man/figures/pin_2.png",
"pin3" = "../../man/figures/pin_3.png",
"pin4" = "../../man/figures/pin_4.png",
"pin5" = "../../man/figures/pin_5.png",
"pinother" = "../../man/figures/pin_other.png"
)
for (i in seq_along(icons)) {
map <- map |>
add_image(
image_id = names(icons)[i],
image_url = icons[[i]]
)
}
borough_centroids <- boroughs |>
dplyr::mutate(
geometry = sf::st_centroid(geometry)
) |>
dplyr::filter(
cycle_hires > 0
)
map <- map |>
add_symbol_layer(
id = "centroid_symbols",
source = borough_centroids,
can_cluster = FALSE,
layout = get_layout_options(
"symbol",
options = list(
icon_image = get_column_steps(
"cycle_hires",
breaks = borough_centroids$cycle_hires |> quantile(),
values = c(
"pinother",
"pin1",
"pin2",
"pin3",
"pin4",
"pin5"
)
),
icon_size = 0.5
)
)
)
mapAdding line layers
Line layers can be added to the map using the
add_line_layer() function. The syntax is similar to the
previous functions. Here we will draw a line around the edges of the
cycle hire stations in London.
cycle_hire_outline <- cycle_hire |>
dplyr::summarise(
geometry = sf::st_combine(geometry)
) |>
sf::st_convex_hull()
map <- map |>
add_line_layer(
id = "cycle_hire_outline_red",
source = cycle_hire_outline,
paint = get_paint_options(
"line",
options = list(
colour = "#ff0000",
line_dash = c(3,2)
)
)
)
mapAdding text layers
Text layers can be added to the map using the
add_text_layer() function. While the syntax is similar,
note that we need to reference the column we want to use for the text
using the toro::get_column() function. For single
annotations, strings will be interpreted as text, but for multiple
annotations, the column name must be specified.
map <- map |>
add_text_layer(
id = "annotations",
source = boroughs,
layout = get_layout_options(
"text",
options = list(
text_field = toro::get_column("NAME")
)
),
paint = get_paint_options(
"text",
options = list(
color = "#000000",
"text-halo-color" = "#ffffff",
"text-halo-width" = 1
)
)
)
mapAdding a legend
At the moment, we have various data plotted, but can only interpret
it via information passed to the hover or popup text. It would be useful
to have the fills and cirlces represented in a legend. The
add_legend() function can be used to add a legend to the
map. The syntax is similar to the previous functions, but we can specify
the type of legend we want to add using the type argument.
In this example, we will add a fill legend and a circle legend.
map <- map |>
add_legend(
layer_id = "boroughs",
title = "Total Stations",
values = c(0, 1, 10, 100, 1000),
colours = rev(viridis::magma(6))
) |>
add_legend(
layer_id = "cycle_hire",
title = "Bikes Available",
# Note that quantiles will be interpreted as ranges (e.g. 0-3, 3-11, etc)
# to disable this behaviour use `as.character`
values = cycle_hire$nbikes |> quantile() |> as.character(),
colours = viridis::viridis(6)
)
mapAdding a layer control
At this point, we have quite an overwhelming map! It would be useful
to have a menu to toggle layers on and off. To do this, we can use the
add_control_group() function to create a control group, and
then use the add_visibility_toggle() function to add a
visibility toggle for each layer we want to control. The syntax is
similar to the previous functions, but we can specify the layer we want
to control using the layer_id argument. We can also specify
the label for the toggle using the left_label argument.
Hiding a layer will also hide its corresponding legend.
map <- map |>
add_control_group(
panel_id = "visibility_toggle",
group_id = "visibility_toggle", # Subgroups not used in this example,
group_title = "Toggle Icons"
) |>
add_visibility_toggle(
layer_id = c("boroughs"),
left_label = "Boroughs",
panel_id = "visibility_toggle",
group_id = "visibility_toggle"
) |>
add_visibility_toggle(
layer_id = c("annotations"),
left_label = "Borough Names",
panel_id = "visibility_toggle",
group_id = "visibility_toggle"
) |>
add_visibility_toggle(
layer_id = c("centroid_symbols"),
left_label = "Borough Centroids",
panel_id = "visibility_toggle",
group_id = "visibility_toggle"
) |>
add_visibility_toggle(
layer_id = c("cycle_hire"),
left_label = "Cycle Hire Stations",
panel_id = "visibility_toggle",
group_id = "visibility_toggle"
) |>
add_visibility_toggle(
layer_id = c("cycle_hire_outline_red"),
left_label = "Cycle Hire Outline",
panel_id = "visibility_toggle",
group_id = "visibility_toggle"
)
mapWith that, the map is complete! We have a map with multiple layers, a legend, and a control panel to toggle the visibility of the layers.
