I am interesting in creating a map that looks like this (found at https://www.axios.com/2017/12/15/the-flow-of-goods-between-states-1513304375): enter image description here

Specifically, I want to depict flows between regions on a map with curved lines, and indicate bigger flows with wider lines, and also use arrows to show the direction of the flow. If possible, I would also like the line from A to B to not be on top of the line from B to A in order for the viewer to distinguish between the two. And preferably use ggplot2, though I am open to other solutions.

I will note that there are related questions (such as How can I add directional arrows to lines drawn on a map in R?, How to create a map chart with direction arrows in R?, plotting email flow in map using R, and https://flowingdata.com/2011/05/11/how-to-map-connections-with-great-circles/), but I was wondering if there is a solution that allows me to incorporate all the elements at once. (And I am not sure if prior solutions tackle the problem of not having A to B and B to A overlap.)

1

There are 1 best solutions below

0
VinceGreg On BEST ANSWER

Yes, it is possible with ggplot2 (tidyverse) and sf. The curvature of geom_curve() need to be different of 0, ex. 0.5, in order to create an ellipse without overlapping arrows, A--> B and B-->A.

Here is a quick attempt. You might want creating bins for alpha and the linewidht.

library(sf)
library(tidyverse)

# Shapefile of US in a almost random CRS, downloaded from 
# https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_state_20m.zip
us_shp = st_read( "cb_2018_us_state_20m/cb_2018_us_state_20m.shp") %>% 
  st_transform("ESRI:102003")

# State center (arrow start and end), from datasets::
state_center = state.center %>% as_tibble() %>% st_as_sf(coords=c("x","y") ,
                                                         crs = 4326, remove=FALSE)%>% 
  st_transform("ESRI:102003") %>% 
  mutate(state_id = 1:n()) %>% 
  dplyr::mutate(x = sf::st_coordinates(.)[,1],
                y = sf::st_coordinates(.)[,2])

# Creation of a 50*50 dataframe, but we will select some states only at the end
state_exchange =    expand.grid(state1= 1:50, state2 = 1:50 ) %>% 
  mutate( values = rnorm(50*50)) %>% 
  as_tibble() %>% 
  filter(state1!=state2) %>% 
  filter( state1 <4, state2<4)

# Adding start-end 
state_exchange_sf =  state_exchange %>% 
  left_join( state_center %>% st_drop_geometry(), 
             by = c("state1" = "state_id"),
             suffix= c("","start") ) %>% 
  left_join( state_center %>% st_drop_geometry(), 
             by = c("state2" = "state_id"),
             suffix= c("","end"))

ggplot()+geom_sf(data= us_shp)+
  geom_sf(data=state_center)+
  geom_curve( data =state_exchange_sf %>%
                st_as_sf(coords= c("x","y"), crs= "ESRI:102003", remove=FALSE),
              aes(x= x, y= y, xend= xend, yend=yend,
                  alpha= values , linewidth = values), 
              curvature = -0.5 , arrow= grid::arrow()  )

enter image description here