rggplot2plotmapsflowchart

How to make flow map with arrows indicating direction of flow, varying thickness of lines, and A to B and B to A flows not overlapping


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.)


Solution

  • 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