Here I demonstrate how to use the
tricolore
library to generate ternary choropleth maps using
both ggplot2
and leaflet
.
library(tricolore)
#> Registered S3 methods overwritten by 'ggtern':
#> method from
#> grid.draw.ggplot ggplot2
#> plot.ggplot ggplot2
#> print.ggplot ggplot2
#> Please cite tricolore. See citation("tricolore").
The data set euro_example
contains the administrative
boundaries for the European NUTS-2 regions in the column
geometry
. This data can be used to plot a choropleth map of
Europe using the sf
package. Each region is represented by
a single row. The name of a region is given by the variable
name
while the respective NUTS-2
geocode is given by the variable id
. For each region some
compositional statistics are available: Variables starting with
ed
refer to the relative share of population ages 25 to 64
by educational attainment in 2016 and variables starting with
lf
refer to the relative share of workers by labor-force
sector in the European NUTS-2 regions 2016.
Take the first row of the data set as an example: in the Austrian
region of “Burgenland” (id
= AT11
) 16.5% of
the population aged 25–64 had attained an education of “Lower secondary
or less” (ed_0to2
), 55.7% attained “upper secondary”
education (ed_3to4
), and 27.9% attained “tertiary”
education. In the very same region 4.4% of the labor-force works in the
primary sector, 26.8% in the secondary and 68.2% in the tertiary
sector.
The education and labor-force compositions are ternary,
i.e. made up from three elements, and therefore can be color-coded as
the weighted mixture of three primary colors, each primary mapped to one
of the three elements. Such a color scale is called a ternary
balance scheme1. This is what tricolore
does.
ggplot2
for ternary choropleth mapsHere I show how to create a choropleth map of the regional
distribution of education attainment in Europe 2016 using
ggplot2
.
1. Using the Tricolore()
function, color-code
each educational composition in the euro_example
data set
and add the resulting vector of hex-srgb colors as a new variable to the
data frame. Store the color key separately.
# color-code the data set and generate a color-key
tric <- Tricolore(euro_example, p1 = 'ed_0to2', p2 = 'ed_3to4', p3 = 'ed_5to8')
tric
contains both a vector of color-coded compositions
(tric$rgb
) and the corresponding color key
(tric$key
). We add the vector of colors to the
map-data.
2. Using ggplot2
and the joined color-coded
education data and geodata, plot a ternary choropleth map of education
attainment in the European regions. Add the color key to the
map.
The secret ingredient is scale_fill_identity()
to make
sure that each region is colored according to the value in the
rgb
variable of euro_educ_map
.
library(ggplot2)
plot_educ <-
# using sf dataframe `euro_example`...
ggplot(euro_example) +
# ...draw a polygon for each region...
geom_sf(aes(fill = rgb, geometry = geometry), size = 0.1) +
# ...and color each region according to the color code in the variable `rgb`
scale_fill_identity()
plot_educ
Using annotation_custom()
and ggplotGrob
we
can add the color key produced by Tricolore()
to the map.
Internally, the color key is produced with the ggtern
package. In order for it to render correctly we need to load
ggtern
after loading ggplot2
. Don’t
worry, the ggplot2
functions still work.
library(ggtern)
#> --
#> Remember to cite, run citation(package = 'ggtern') for further info.
#> --
#>
#> Attaching package: 'ggtern'
#> The following objects are masked from 'package:ggplot2':
#>
#> aes, annotate, ggplot, ggplotGrob, ggplot_build, ggplot_gtable,
#> ggsave, layer_data, theme_bw, theme_classic, theme_dark,
#> theme_gray, theme_light, theme_linedraw, theme_minimal, theme_void
plot_educ +
annotation_custom(
ggplotGrob(tric$key),
xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5
)
Because the color key behaves just like a ggplot2
plot
we can change it to our liking.
plot_educ <-
plot_educ +
annotation_custom(
ggplotGrob(tric$key +
theme(plot.background = element_rect(fill = NA, color = NA)) +
labs(L = '0-2', T = '3-4', R = '5-8')),
xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5
)
plot_educ
Some final touches…
leaflet
for ternary choropleth mapsThe ggplot2
example above is easily adapted to
leaflet
. This time I use a continuous color scale.
# color-code the data set and generate a color-key
tric <- Tricolore(euro_example, p1 = 'ed_0to2', p2 = 'ed_3to4', p3 = 'ed_5to8',
breaks = Inf)
# add the vector of colors to the `euro_example` data
euro_example$rgb <- tric$rgb
leaflet
requires geodata in spherical coordinates
(longitude-latitude format). Therefore I reproject the data to a suitable crs
using the sf
package.
library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.4.0; sf_use_s2() is TRUE
library(leaflet)
euro_example %>%
st_transform(crs = 4326) %>%
leaflet() %>%
addPolygons(smoothFactor = 0.1, weight = 0,
fillColor = euro_example$rgb,
fillOpacity = 1)
Adding a background map gives geographical context to the map. I also add a mouse pop-up of the actual data.
euro_example %>%
st_transform(crs = 4326) %>%
leaflet() %>%
addProviderTiles(providers$Esri.WorldTerrain) %>%
addPolygons(smoothFactor = 0.1, weight = 0,
fillColor = euro_example$rgb,
fillOpacity = 1,
popup =
paste0(
'<b>', euro_example$name, '</b></br>',
'Primary: ',
formatC(euro_example$ed_0to2*100,
digits = 1, format = 'f'), '%</br>',
'Secondary: ',
formatC(euro_example$ed_3to4*100,
digits = 1, format = 'f'), '%</br>',
'Tertiary: ',
formatC(euro_example$ed_5to8*100,
digits = 1, format = 'f'), '%</br>'
)
)
Adding the legend to the leaflet map requires a bit of a hack.
makePlotURI <- function(expr, width, height, ...) {
pngFile <- shiny::plotPNG(function() { expr }, width = width, height = height, ...)
on.exit(unlink(pngFile))
base64 <- httpuv::rawToBase64(readBin(pngFile, raw(1), file.size(pngFile)))
paste0("data:image/png;base64,", base64)
}
legend_symbol <- makePlotURI({
print(tric$key +
theme(plot.background = element_rect(fill = NA, color = NA)) +
labs(L = '0-2', T = '3-4', R = '5-8'))
}, 200, 200, bg = "transparent")
df <- data.frame(
lng = 30,
lat = 70,
plot = legend_symbol,
stringsAsFactors = FALSE
)
euro_example %>%
st_transform(crs = 4326) %>%
leaflet() %>%
addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
addPolygons(smoothFactor = 0.1, weight = 0,
fillColor = euro_example$rgb,
fillOpacity = 1,
popup =
paste0(
'<b>', euro_example$name, '</b></br>',
'Primary: ',
formatC(euro_example$ed_0to2*100,
digits = 1, format = 'f'), '%</br>',
'Secondary: ',
formatC(euro_example$ed_3to4*100,
digits = 1, format = 'f'), '%</br>',
'Tertiary: ',
formatC(euro_example$ed_5to8*100,
digits = 1, format = 'f'), '%</br>'
)
) %>%
addMarkers(data = df, icon = ~icons(plot))
#> Assuming "lng" and "lat" are longitude and latitude, respectively
Brewer, C. A. (1994). Color Use Guidelines for Mapping and Visualization. In A. M. MacEachren & D. R. F. Taylor (Eds.), Visualization in Modern Cartography (pp. 123–147). Oxford, UK: Pergamon.
Dorling, D. (2012). The Visualization of Spatial Social Structure. Chichester, UK: Wiley.
Schöley, J. (2021). The centered ternary balance scheme: A technique to visualize surfaces of unbalanced three-part compositions. Demographic Research (44).
See for example Dorling (2012) and Brewer (1994).↩︎