-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathcreating-maps.Rmd
432 lines (340 loc) · 24.8 KB
/
creating-maps.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# Maps {#maps}
There are numerous ways to make a map with **plotly**; each with its own strengths and weaknesses. Generally speaking, the approaches fall under two categories: integrated or custom. Integrated maps leverage plotly.js's built-in support for rendering a basemap layer. Currently there are two supported ways of making integrated maps: either via [Mapbox](https://www.mapbox.com/) or via an integrated d3.js powered basemap. The integrated approach is convenient if you need a quick map and don't necessarily need sophisticated representations of geo-spatial objects. On the other hand, the custom mapping approach offers complete control since you're providing all the information necessary to render the geo-spatial object(s). Section \@ref(maps-custom) covers making sophisticated maps (e.g., cartograms) using the **sf** R package, but it's also possible to make custom **plotly** maps via other tools for geo-computing (e.g., **sp**, **ggmap**, etc.).
It's worth noting that **plotly** aims to be a general purpose visualization library, and thus, doesn't aim to be the most fully featured geo-spatial visualization toolkit. That said, there are benefits to using **plotly**-based maps since the mapping APIs are very similar to the rest of plotly, and you can leverage the larger **plotly** ecosystem (e.g., linking views client-side like Figure \@ref(fig:mapbox-bars)). However, if you run into limitations with **plotly**'s mapping functionality, there is a very rich set of tools for [interactive geospatial visualization in R](https://geocompr.robinlovelace.net/adv-map.html#interactive-maps), including but not limited to: **leaflet**, **mapview**, **mapedit**, **tmap**, and **mapdeck** [@geocomputation].
## Integrated maps {#maps-integrated}
### Overview
\index{plot\_mapbox()@\texttt{plot\_mapbox()}}
If you have fairly simple latitude/longitude data and want to make a quick map, you may want to try one of **plotly**'s integrated mapping options (i.e., `plot_mapbox()` and `plot_geo()`). Generally speaking, you can treat these constructor functions as a drop-in replacement for `plot_ly()` and get a dynamic basemap rendered behind your data. Furthermore, all the scatter-based layers we learned about in Section \@ref(scatter-traces) work as you'd expect it to with `plot_ly()`.^[Unfortunately, non-scatter traces currently don't work with `plot_mapbox()`/ `plot_geo()` meaning that, for one, raster (i.e., heatmap) maps are not natively supported.] For example, Figure \@ref(fig:mapbox-bubble) uses `plot_mapbox()` and `add_markers()` to create a bubble chart:
```r
plot_mapbox(maps::canada.cities) %>%
add_markers(
x = ~long,
y = ~lat,
size = ~pop,
color = ~country.etc,
colors = "Accent",
text = ~paste(name, pop),
hoverinfo = "text"
)
```
```{r mapbox-bubble, echo = FALSE, fig.cap = "(ref:mapbox-bubble)"}
include_vimeo("317352934")
```
\index{plot\_mapbox()@\texttt{plot\_mapbox()}!Basemap style}
The Mapbox basemap styling is controlled through the [`layout.mapbox.style`](https://plot.ly/r/reference/#layout-mapbox-style) attribute. The **plotly** package comes with support for 7 different styles, but you can also supply a custom URL to a [custom mapbox style](https://docs.mapbox.com/help/tutorials/create-a-custom-style/). To obtain all the pre-packaged basemap style names, you can grab them from the official plotly.js `schema()`:
```{r}
styles <- schema()$layout$layoutAttributes$mapbox$style$values
styles
```
Any one of these values can be used for a mapbox style. Figure \@ref(fig:satellite) demonstrates the satellite earth imagery basemap.
```r
layout(
plot_mapbox(),
mapbox = list(style = "satellite")
)
```
```{r satellite, echo = FALSE, fig.cap = "(ref:satellite)"}
include_vimeo("326683897")
```
Figure \@ref(fig:mapbox-style-dropdown) demonstrates how to create an integrated plotly.js dropdown menu to control the basemap style via the [`layout.updatemenus`](https://plot.ly/r/reference/#layout-updatemenus-items-updatemenu-buttons) attribute. The idea behind an integrated plotly.js dropdown is to supply a list of buttons (i.e., menu items) where each button invokes a plotly.js method with some arguments. In this case, each button uses the [relayout](https://plot.ly/javascript/plotlyjs-function-reference/) method to modify the `layout.mapbox.style` attribute.^[To see more examples of creating and using plotly.js's integrated dropdown functionality to modify graphs, see <https://plot.ly/r/dropdowns/>]
\index{layout()@\texttt{layout()}!updatemenus@\texttt{updatemenus}}
```r
style_buttons <- lapply(styles, function(s) {
list(
label = s,
method = "relayout",
args = list("mapbox.style", s)
)
})
layout(
plot_mapbox(),
mapbox = list(style = "dark"),
updatemenus = list(
list(y = 0.8, buttons = style_buttons)
)
)
```
```{r mapbox-style-dropdown, echo = FALSE, fig.cap="(ref:mapbox-style-dropdown)"}
include_vimeo("326684625")
```
\index{plot\_geo()@\texttt{plot\_geo()}}
The other integrated mapping solution in **plotly** is `plot_geo()`. Compared to `plot_mapbox()`, this approach has support for different mapping projections, but styling the basemap is limited and can be more cumbersome. Figure \@ref(fig:geo-flights) demonstrates using `plot_geo()` in conjunction with `add_markers()` and `add_segments()` to visualize flight paths within the United States. Whereas `plot_mapbox()` is fixed to a mercator projection, the `plot_geo()` constructor has a handful of different projections available to it, including the orthographic projection which gives the illusion of the 3D globe.
\index{plot\_geo()@\texttt{plot\_geo()}!layout.geo@\texttt{layout(geo = ...)}}
```{r, eval = FALSE, summary = "Click to show code"}
library(plotly)
library(dplyr)
# airport locations
air <- read.csv(
'https://plotly-r.com/data-raw/airport_locations.csv'
)
# flights between airports
flights <- read.csv(
'https://plotly-r.com/data-raw/flight_paths.csv'
)
flights$id <- seq_len(nrow(flights))
# map projection
geo <- list(
projection = list(
type = 'orthographic',
rotation = list(lon = -100, lat = 40, roll = 0)
),
showland = TRUE,
landcolor = toRGB("gray95"),
countrycolor = toRGB("gray80")
)
plot_geo(color = I("red")) %>%
add_markers(
data = air, x = ~long, y = ~lat, text = ~airport,
size = ~cnt, hoverinfo = "text", alpha = 0.5
) %>%
add_segments(
data = group_by(flights, id),
x = ~start_lon, xend = ~end_lon,
y = ~start_lat, yend = ~end_lat,
alpha = 0.3, size = I(1), hoverinfo = "none"
) %>%
layout(geo = geo, showlegend = FALSE)
```
```{r geo-flights, echo = FALSE, fig.cap = "(ref:geo-flights)"}
include_vimeo("317358033")
```
One nice thing about `plot_geo()` is that it automatically projects geometries into the proper coordinate system defined by the map projection. For example, in Figure \@ref(fig:maps) the simple line segment is straight when using `plot_mapbox()`, yet curved when using `plot_geo()`. It's possible to achieve the same effect using `plot_ly()` or `plot_mapbox()`, but the relevant marker/line/polygon data has to be put into an **sf** data structure before rendering (see Section \@ref(sf) for more details).
\index{add\_trace()@\texttt{add\_trace()}!add\_segments()@\texttt{add\_segments()}}
\indexc{htmltools::tagList()}
```r
map1 <- plot_mapbox() %>%
add_segments(x = -100, xend = -50, y = 50, yend = 75) %>%
layout(
mapbox = list(
zoom = 0,
center = list(lat = 65, lon = -75)
)
)
map2 <- plot_geo() %>%
add_segments(x = -100, xend = -50, y = 50, yend = 75) %>%
layout(geo = list(projection = list(type = "mercator")))
library(htmltools)
browsable(tagList(map1, map2))
```
```{r maps, echo = FALSE, fig.cap = "(ref:maps)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/maps.html"'}
knitr::include_graphics("images/maps.png")
```
### Choropleths
\index{Chart types!Choropleth}
In addition to scatter traces, both of the integrated mapping solutions (i.e., `plot_mapbox()` and `plot_geo()`) have an optimized choropleth trace type (i.e., the [choroplethmapbox](https://plot.ly/r/reference/#choroplethmapbox) and [choropleth](https://plot.ly/r/reference/#choropleth) trace types). Comparatively speaking, choroplethmapbox is more powerful because you can fully specify the feature collection using GeoJSON, but the choropleth trace can be a bit easier to use if it fits your use case.
Figure \@ref(fig:us-density) shows the population density of the U.S. via the choropleth trace using the U.S. state data from the **datasets** package [@RCore]. By simply providing a [`z`](https://plot.ly/r/reference/#choropleth-z) attribute, `plotly_geo()` objects will try to create a choropleth, but you'll also need to provide [`locations`](https://plot.ly/r/reference/#choropleth-locations) and a [`locationmode`](https://plot.ly/r/reference/#choropleth-locationmode). It's worth noting that the `locationmode` is currently limited to countries and US states, so if you need to plot a different geo-unit (e.g., counties, municipalities, etc.), you should use the choroplethmapbox trace type and/or use a "custom" mapping approach as discussed in Section \@ref(maps-custom).
\index{plot\_geo()@\texttt{plot\_geo()}!Choropleth}
```r
density <- state.x77[, "Population"] / state.x77[, "Area"]
g <- list(
scope = 'usa',
projection = list(type = 'albers usa'),
lakecolor = toRGB('white')
)
plot_geo() %>%
add_trace(
z = ~density, text = state.name, span = I(0),
locations = state.abb, locationmode = 'USA-states'
) %>%
layout(geo = g)
```
```{r us-density, echo = FALSE, fig.cap = "(ref:us-density)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/us-density.html"'}
knitr::include_graphics("images/us-density.svg")
```
Choroplethmapbox is more flexible than choropleth because you supply your own GeoJSON definition of the choropleth via the `geojson` attribute. Currently this attribute must be a URL pointing to a geojson file. Moreover, the `location` should point to a top-level id attribute of each feature within the geojson file. Figure \@ref(fig:us-density-geojson) demonstrates how we could visualize the same information as Figure \@ref(fig:us-density), but this time using choroplethmapbox.
\index{plot\_mapbox()@\texttt{plot\_mapbox()}!Choropleth}
```r
plot_ly() %>%
add_trace(
type = "choroplethmapbox",
# See how this GeoJSON URL was generated at
# https://plotly-r.com/data-raw/us-states.R
geojson = paste(c(
"https://gist.githubusercontent.com/cpsievert/",
"7cdcb444fb2670bd2767d349379ae886/raw/",
"cf5631bfd2e385891bb0a9788a179d7f023bf6c8/",
"us-states.json"
), collapse = ""),
locations = row.names(state.x77),
z = state.x77[, "Population"] / state.x77[, "Area"],
span = I(0)
) %>%
layout(
mapbox = list(
style = "light",
zoom = 4,
center = list(lon = -98.58, lat = 39.82)
)
) %>%
config(
mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN"),
# Workaround to make sure image download uses full container
# size https://github.com/plotly/plotly.js/pull/3746
toImageButtonOptions = list(
format = "svg",
width = NULL,
height = NULL
)
)
```
```{r us-density-geojson, echo = FALSE, fig.cap = "(ref:us-density-geojson)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/us-density-geojson.html"'}
knitr::include_graphics("images/us-density-geojson.svg")
```
Figures \@ref(fig:us-density) and \@ref(fig:us-density-geojson) aren't an ideal way to visualize state population a graphical perception point of view. We typically use the color in choropleths to encode a numeric variable (e.g., GDP, net exports, average SAT score, etc.) and the eye naturally perceives the area that a particular color covers as proportional to its overall effect. This ends up being misleading since the area the color covers typically has no sensible relationship with the data encoded by the color. A classic example of this misleading effect in action is in US election maps -- the proportion of red to blue coloring is not representative of the overall popular vote [@election-maps].
Cartograms are an approach to reducing this misleading effect and grant another dimension to encode data through the size of geo-spatial features. Section \@ref(cartograms) covers how to render cartograms in **plotly** using **sf** and **cartogram**.
## Custom maps {#maps-custom}
### Simple features (sf) {#sf}
The **sf** R package is a modern approach to working with geo-spatial data structures based on tidy data principles [@sf; @tidy-data]. The key idea behind **sf** is that it stores geo-spatial geometries in a [list-column](https://jennybc.github.io/purrr-tutorial/ls13_list-columns.html) of a data frame. This allows each row to represent the real unit of observation/interest --- whether it's a polygon, multi-polygon, point, line, or even a collection of these features --- and as a result, works seamlessly inside larger tidy workflows.^[This is way more intuitive compared to older workflows based on, say using `ggplot2::fortify()` to obtain a data structure where a row represents a particular point along a feature and having another column track which point belongs to each feature.] The **sf** package itself does not really provide geo-spatial data; it provides the framework and utilities for storing and computing on geo-spatial data structures in an opinionated way.
There are numerous packages for accessing geo-spatial data as simple features data structures. A couple of notable examples include **rnaturalearth** and **USAboundaries**. The **rnaturalearth** package is better for obtaining any map data in the world via an API provided by <https://www.naturalearthdata.com/> [@rnaturalearth]. The **USAboundaries** package is great for obtaining map data for the United States at any point in history [@USAboundaries]. It doesn't really matter what tool you use to obtain or create an **sf** object; once you have one, `plot_ly()` knows how to render it:
```r
library(rnaturalearth)
world <- ne_countries(returnclass = "sf")
class(world)
#> [1] "sf" "data.frame"
plot_ly(world, color = I("gray90"), stroke = I("black"), span = I(1))
```
```{r world, echo = FALSE, fig.cap = "(ref:world)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/world.html"'}
knitr::include_graphics("images/world.svg")
```
How does `plot_ly()` know how to render the countries? It's because the geo-spatial features are encoded in special (geometry) list-column. Also, meta-data about the geo-spatial structure are retained as special attributes of the data. Figure \@ref(fig:view-sf) augments the print method for **sf** to data frames to demonstrate that all the information needed to render the countries (i.e., polygons) in Figure \@ref(fig:world) is contained within the `world` data frame. Note also that **sf** provides special **dplyr** methods for this special class of data frame so that you can treat data manipulation as if it were a 'tidy' data structure. One thing about this method is that the special 'geometry' column is always retained; if we try to just select the `name` column, then we get both the name and the geometry.
```r
library(sf)
world %>%
select(name) %>%
print(n = 4)
```
```{r view-sf, echo = FALSE, fig.cap = "(ref:view-sf)"}
knitr::include_graphics("images/view-sf.svg")
```
There are actually 4 different ways to render **sf** objects with **plotly**: `plot_ly()`, `plot_mapbox()`, `plot_geo()`, and via **ggplot2**'s `geom_sf()`. These functions render multiple polygons using a *single* trace by default, which is fast, but you may want to leverage the added flexibility of multiple traces. For example, a given trace can only have one `fillcolor`, so it's impossible to render multiple polygons with different colors using a single trace. For this reason, if you want to vary the color of multiple polygons, make sure the `split` by a unique identifier (e.g., `name`), as done in Figure \@ref(fig:split-color). Note that, as discussed for line charts in Figure \@ref(fig:scatter-lines), using multiple traces automatically adds the ability to filter `name` via legend entries.
```r
canada <- ne_states(country = "Canada", returnclass = "sf")
plot_ly(canada, split = ~name, color = ~provnum_ne)
```
```{r split-color, echo = FALSE, fig.cap = "(ref:split-color)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/split-color.html"'}
knitr::include_graphics("images/split-color.svg")
```
Another important feature for maps that may require you to `split` multiple polygons into multiple traces is the ability to display a different hover-on-fill for each polygon. By providing `text` that is unique within each polygon and specifying `hoveron='fills'`, as in Figure \@ref(fig:split-text), the tooltip behavior is tied to the trace's fill (instead of being displayed at each point along the polygon).
```r
plot_ly(
canada,
split = ~name,
color = I("gray90"),
text = ~paste(name, "is \n province number", provnum_ne),
hoveron = "fills",
hoverinfo = "text",
showlegend = FALSE
)
```
```{r split-text, echo = FALSE, fig.cap = "(ref:split-text)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/split-text.html"'}
knitr::include_graphics("images/split-text.png")
```
Although the integrated mapping approaches (`plot_mapbox()` and `plot_geo()`) can render **sf** objects, the custom mapping approaches (`plot_ly()` and `geom_sf()`) are more flexible because they allow for any well-defined mapping projection. Working with and understanding map projections can be intimidating for a causal map maker. Thankfully, there are nice resources for searching map projections in a human-friendly interface, like <http://spatialreference.org/>. Through this website, one can search desirable projections for a given portion of the globe and extract commands for projecting their geo-spatial objects into that projection. As shown in Figure \@ref(fig:canada-ggplotly), one way to perform the projection is to supply the relevant PROJ4 command to the `st_transform()` function in **sf** [@PROJ].
```r
# filter the world sf object down to canada
canada <- filter(world, name == "Canada")
# coerce cities lat/long data to an official sf object
cities <- st_as_sf(
maps::canada.cities,
coords = c("long", "lat"),
crs = 4326
)
# A PROJ4 projection designed for Canada
# http://spatialreference.org/ref/sr-org/7/
# http://spatialreference.org/ref/sr-org/7/proj4/
moll_proj <- "+proj=moll +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84
+units=m +no_defs"
# perform the projections
canada <- st_transform(canada, moll_proj)
cities <- st_transform(cities, moll_proj)
# plot with geom_sf()
p <- ggplot() +
geom_sf(data = canada) +
geom_sf(data = cities, aes(size = pop), color = "red", alpha = 0.3)
ggplotly(p)
```
```{r canada-ggplotly, echo = FALSE, fig.cap = "(ref:canada-ggplotly)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/canada-ggplotly.html"'}
knitr::include_graphics("images/canada-ggplotly.svg")
```
Some geo-spatial objects have an unnecessarily high resolution for a given visualization. In these cases, you may want to consider simplifying the geo-spatial object to improve the speed of the R code and responsiveness of the visualization. For example, we could recreate Figure \@ref(fig:world) with a much higher resolution by specifying `scale = "large"` in `ne_countries()`; this gives us a **sf** object with over 50 times more spatial coordinates than the default scale. The higher resolution allows us to zoom in better on more complex geo-spatial regions, but it allow leads to slower R code, larger HTML files, and slower responsiveness. @ggplotly-blog-post explores this issue in more depth and demonstrates how to use the `st_simplify()` function from **sf** to simplify features before plotting them.
```r
sum(rapply(world$geometry, nrow))
#> [1] 10586
world_large <- ne_countries(scale = "large", returnclass = "sf")
sum(rapply(world_large$geometry, nrow))
#> [1] 548121
```
Analogous to the discussion surrounding \@ref(fig:scatter-lines), it pays to be aware of the tradeoffs involved with rendering **plotly** graphics using one or many traces, and to be knowledgeable about how to leverage either approach. Specifically, by default, **plotly** attempts to render all simple features in a single trace, which is performant, but doesn't have a lot of interactivity.
```r
plot_mapbox(
world_large,
color = NA,
stroke = I("black"),
span = I(0.5)
)
```
For those interested in learning more about geocomputation in R with **sf** and other great R packages like **sp** and **raster**, @geocomputation provide lots of nice and freely available learning resources [@sp; @raster].
### Cartograms {#cartograms}
\index{Chart types!Cartogram}
Cartograms distort the size of geo-spatial polygons to encode a numeric variable other than the land size. There are numerous types of cartograms and they are typically categorized by their ability to preserve shape and maintain contiguous regions. Cartograms have been shown to be an effective approach to both encode and teach about geo-spatial data, though the effects certainly vary by cartogram type [@cartogram-vis].
The R package **cartogram** provides an interface to several popular cartogram algorithms [@cartogram]. A number of other R packages provide cartogram algorithms, but the great thing about **cartogram** is that all the functions can take an **sf** (or **sp**) object as input and return an **sf** object. This makes it incredibly easy to go from raw spatial objects, to transformed objects, to visual. Figure \@ref(fig:cartogram) demonstrates a continuous area cartogram of US population in 2014 using a rubber sheet distortion algorithm from @Dougenik.
\index{add\_trace()@\texttt{add\_trace()}!add\_sf()@\texttt{add\_sf()}}
```r
library(cartogram)
library(albersusa)
us_cont <- cartogram_cont(usa_sf("laea"), "pop_2014")
plot_ly(us_cont) %>%
add_sf(
color = ~pop_2014,
split = ~name,
span = I(1),
text = ~paste(name, scales::number_si(pop_2014)),
hoverinfo = "text",
hoveron = "fills"
) %>%
layout(showlegend = FALSE) %>%
colorbar(title = "Population \n 2014")
```
```{r cartogram, echo = FALSE, fig.cap = "(ref:cartogram)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/cartogram.html"'}
knitr::include_graphics("images/cartogram.png")
```
Figure \@ref(fig:cartogram-dorling) demonstrates a non-continuous Dorling cartogram of US population in 2014 from @Dorling. This cartogram does not try to preserve the shape of polygons (i.e., states), but instead uses circles to represent each geo-spatial object, then encodes the variable of interest (i.e., population) using the area of the circle.
```r
us <- usa_sf("laea")
us_dor <- cartogram_dorling(us, "pop_2014")
plot_ly(stroke = I("black"), span = I(1)) %>%
add_sf(
data = us,
color = I("gray95"),
hoverinfo = "none"
) %>%
add_sf(
data = us_dor,
color = ~pop_2014,
split = ~name,
text = ~paste(name, scales::number_si(pop_2014)),
hoverinfo = "text",
hoveron = "fills"
) %>%
layout(showlegend = FALSE)
```
```{r cartogram-dorling, echo = FALSE, fig.cap = "(ref:cartogram-dorling)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/cartogram-dorling.html"'}
knitr::include_graphics("images/cartogram-dorling.svg")
```
Figure \@ref(fig:cartogram-ncont) demonstrates a non-continuous cartogram of the U.S. population in 2014 from @Olson. In contrast to the Dorling cartogram, this approach does preserve the shape of polygons. The implementation behind Figure \@ref(fig:cartogram-ncont) is to simply take the implementation of Figure \@ref(fig:cartogram-dorling) and change `cartogram_dorling()` to `cartogram_ncont()`.
```{r cartogram-ncont, echo = FALSE, fig.cap = "(ref:cartogram-ncont)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/cartogram-ncont.html"'}
knitr::include_graphics("images/cartogram-ncont.svg")
```
A popular class of contiguous cartograms that do not preserve shape are sometimes referred to as tile cartograms (aka tilegrams). At the time of writing, there doesn't seem to be a great R package for _computing_ tilegrams, but Pitch Interactive provides a nice web service where you can generate tilegrams from existing or custom data <https://pitchinteractiveinc.github.io/tilegrams/>. Moreover, the service allows you to download a TopoJSON file of the generated tilegram, which we can read in R and convert into an **sf** object via **geojsonio** [@geojsonio]. Figure \@ref(fig:cartogram-tiles) demonstrates a tilegram of U.S. Population in 2016 exported directly from Pitch's free web service.
```r
library(geojsonio)
tiles <- geojson_read("~/Downloads/tiles.topo.json", what = "sp")
tiles_sf <- st_as_sf(tiles)
plot_ly(
tiles_sf, color = ~name,
colors = "Paired", stroke = I("transparent")
)
```
```{r cartogram-tiles, echo = FALSE, fig.cap = "(ref:cartogram-tiles)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/cartogram-tiles.html"'}
knitr::include_graphics("images/cartogram-tiles.png")
```