Idioms for interacting with geographic data.
Maps can be information dense, so it’s often useful to make them interactive. These notes review some basic strategies for interactive spatial data visualization.
leaflet
is an easy-to-use R package that’s often sufficient for routine visualization. It offers several types of marks (marks, circles, polygons) and allows them to
encode fields in a dataset. Note that its interface is more like base R than
ggplot2 — we specify each attribute in one plot command. For example, in the
code block below, addTiles
fetches the background map. addCircles overlays the
new vector features on top of the map. It’s worth noting that the vector
features were created automatically – there was no need to create or read in any
type of sf object.
Leaflet maps can be embedded into Shiny
apps using leafletOutput
and
renderLeaflet
. For example, the Superzip Explorer is a visualization
designed for showing income and education levels across ZIP codes in the US. In
the
server,
the map is initialized using the leaflet command (without even adding any data
layers).
The most interesting aspect of the explorer is that it lets us zoom into
regions and study properties of ZIP codes within the current view.
leaflet automatically creates an input$map_bounds
input which is triggered
anytime we pan or zoom the map. It returns a subset of the full dataset within
the current view.
zipsInBounds <- reactive({
if (is.null(input$map_bounds)) return(zipdata[FALSE,]) # return empty data
bounds <- input$map_bounds
latRng <- range(bounds$north, bounds$south)
lngRng <- range(bounds$east, bounds$west)
# filters to current view
subset(zipdata,
latitude >= latRng[1] & latitude <= latRng[2] &
longitude >= lngRng[1] & longitude <= lngRng[2])
})
Whenever this reactive is run, the histogram (output$histCentile
) and
scatterplot (output$scatterCollegeIncome
) on the side of the app are
updated.
Notice that an observer was created to monitor any interactions with the map.
Within this observe block, a leafletProxy
call is used. This function makes it
possible to modify a leaflet map without redrawing the entire map. It helps
support efficient rendering – we’re able to change the colors of the circles
without redrawing the entire leaflet view.
leafletProxy("map", data = zipdata) %>%
clearShapes() %>%
addCircles(~longitude, ~latitude, radius=radius, layerId=~zipcode,
stroke=FALSE, fillOpacity=0.4, fillColor=pal(colorData)) %>%
addLegend("bottomleft", pal=pal, values=colorData, title=colorBy,
layerId="colorLegend")
We can often dynamically query spatial data. Querying a map can highlight properties of samples within a geographic region. For example, here is a map of in which each US county has been associated with a (random) pair of data features. Clicking on counties (or hovering with the shift key pressed) updates the bars on the right. Each bar shows the average from one of the data fields, across all selected counties.
This linking is accomplished using event listeners. For example, the map
includes the call .on("mouseover", update_selection)
, and update_selection
changes the fill of the currently hovered county,
svg.selectAll("path")
.attr("fill", (d) => selected.indexOf(d.id) == -1 ? "#f7f7f7" : "#4a4a4a");
The full implementation can be read here. Note that interactivity here is done just like in any other D3 visualization. We can treat the map as just another collection of SVG paths, and all our interaction events behave in the same way.
We can also imagine selecting geographic regions by interacting with linked views. This is used in Nadieh Bremer’s Urbanization in East Asia visualization, for example, where we can see all the urban areas within a country by hovering its associated bar.
Here is a somewhat more complex version of the earlier random data example where acounties are associated with (random) time series. Redrawing the lower and upper bounds on the time series changes which counties are highlighted.
Though it’s not exactly interaction, another common strategy for spatiotemporal data is animation. The major trends often become apparent by visualizing the flow of visual marks on the screen. For example, how can we visualize where football players go on a field before they score a goal? One approach is to animate the trajectories leading up to the goal. Here is one beautiful visualization (in D3!) by Karim Douieb that shows the most common paths and the speed at which the players run.