An open API service indexing awesome lists of open source software.

https://github.com/JohMast/flowmapper

Draw flows (migration, goods, money, information) on ggplots.
https://github.com/JohMast/flowmapper

Last synced: 2 months ago
JSON representation

Draw flows (migration, goods, money, information) on ggplots.

Awesome Lists containing this project

README

          

---
output: github_document
---

[![CRAN status](https://www.r-pkg.org/badges/version/flowmapper)](https://CRAN.R-project.org/package=flowmapper)
[![](https://cranlogs.r-pkg.org/badges/flowmapper)](https://cran.r-project.org/package=flowmapper)

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,message = FALSE,warning=FALSE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "70%",
dpi=300
# fig.width=7
)
```

# flowmapper

flowmapper allows to create ggplots with flowmaps in the style of .

## Installation

You can install the released version of flowmapper from CRAN:

``` r
install.packages("flowmapper")
```

Or, you can install the development version of flowmapper like so:

``` r
devtools::install_github("https://github.com/JohMast/flowmapper")
```

## Details

flowmapper uses a single function `add_flowmap` to add a flowmap layer to an existing ggplot. `add_flowmap` requires as inputs a single data.frame (or tibble) that contains for every combination of two nodes a and b

- the x and y coordinates of each these nodes,

- a unique id for each of these nodes,

- the intensity of flow between those nodes in both directions (from a to b, and from b to a).

The data.frame should have the following columns:

```{r example}

testdata <-
data.frame(
id_a = c("X1","X2","X3","X3","X1","X2","X2"),
id_b = c("X5","X4","X1","X5","X4","X5","X3"),
xa = c(2,14,10,10,2,14,14),
ya = c(6,10,9,9,6,10,10),
xb = c(10,4,2,10,4,10,10),
yb = c(4,10,6,4,10,4,9),
flow_ab = c(1,2,3,3,1,1,4),
flow_ba = c(2,3,2,5,2,1,5)
)

```

The dataframe and the ggplot that the flowmap should be added to are then passed into `add_flowmap`.

```{r c2, warning=FALSE}
library(ggplot2)
plot <- ggplot() # empty ggplot

library(flowmapper)
plot |>
add_flowmap(testdata)+
coord_equal() # coord equal is highly recommended to create symmetric shapes
```

If the number of nodes is very high, the plot will appear cluttered. In that case, nodes can be clustered and merged by proximity, with `k_nodes` controlling the number of clusters.

```{r t1}
plot |>
add_flowmap(testdata,k_nodes = 4)+
coord_equal()
```

Transparency and outline of the arrows can be controlled with the `outline_col` and `alpha` arguments.

```{r t3}
plot |>
add_flowmap(testdata, outline_col = "orange", alpha=0.5)+
coord_equal() # coord equal is highly recommended to create symmetric shapes
```

The flow arrows are geom_polygons, with the flow mapped to the fill aesthetic. Thus, the fill can be adjusted like for any geom.

```{r t4}
plot |>
add_flowmap(testdata)+
coord_equal() +
scale_fill_gradient(low="black", high = "red")
```

Size of the edges and offset (distance between two paired edges) can be controlled with `edge_width_factor` and `edge_offset_factor` .

```{r t5}
plot |>
add_flowmap(testdata, edge_offset_factor = 4, edge_width_factor = 2)+
coord_equal()
```

Finally, the size of the nodes can be adjusted with `node_radius_factor`, and the distance between nodes and edges with `node_buffer_factor`.

```{r t6}

plot |>
add_flowmap(testdata, node_radius_factor = 2, node_buffer_factor = 0.5)+
coord_equal()
```

Because the edges are polygons and not linked to an aesthetic, a typical ggplot legend cannot be created for their width. As an alternative, a legend can be added to the bottom of the main panel by using `add_legend`.

```{r t7}
undebug(add_flowmap)
plot |>
add_flowmap(testdata,add_legend = "left")+
coord_equal()
```

Instead of a monotone legend, the legend color can be set to represent the flow intensity with `legend_gradient`.

```{r t7_5}
# debug(add_flowmap)
plot |>
add_flowmap(testdata, add_legend = "bottom",legend_gradient=T)+
coord_equal()+
theme(legend.position = "none")
```

The flowmap can be turned into an interactive plot using the [plotly](https://github.com/plotly/plotly.R) library. The names of the nodes and flows are mapped to the `text` aesthetic and can be used in the tooltips.

```{r t8, eval=FALSE, include=TRUE}
library(plotly)
plot <-
plot |>
add_flowmap(testdata)+
coord_equal()
ggplotly(plot,tooltip = c("text","fill"))
```

![](man/figures/plotly_example.PNG)

## Example

The code below shows an example of real world data from Switzerland (same data used in [this](https://www.flowmap.blue/15kwLB4baXZ7jpip8q0JjgR6zDoS5Gt3gMLCTUAboQxk?v=46.719075,7.817990,7.51&a=0&d=1&c=0&lt=1&col=Default&f=45) flowmap). The data contains migration flows between the 26 Cantons of Switzerland.

```{r t9}
library(dplyr,warn.conflicts = FALSE)
library(ggplot2,warn.conflicts = FALSE)
library(sf)
library(flowmapper)

# load migration data
data <-
flowmapper::CH_migration_data
head(data)
```

As a background for the flow map, a ggplot is created using the administrative boundaries, sourced from the [GADM](https://gadm.org/index.html) dataset.

```{r t10}
cantons <- flowmapper::cantons
st_crs(cantons) <- 3857

# basic plot with just the admin units
p <- ggplot(cantons)+
geom_sf(fill=NA,col="gray30",linewidth=0.5) +
ggdark::dark_theme_bw()+
theme(panel.border = element_blank(),
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank())+
labs(title = "Migration in Switzerland 2016",
caption = "Data: Federal Statistical Office, Switzerland")
p
```

The flowmap is then added to the base plot by using `add_flowmap`, applying some clustering to reduce the number of nodes from 26 to 10. A custom color scale is applied which matches the dark background.

```{r t11}
p2 <-
p|>
add_flowmap(flowdat = data,
add_legend = "bottom",
edge_width_factor = 0.7,
k_nodes = 10,legend_gradient=T,
outline_col = NA)+
theme(panel.grid = element_blank())+
scale_fill_gradient("Migration",
low = "darkblue",
high="white")
p2
```

The flowmap uses the *color* and *fill* aesthetics, which can be limiting when the ggplot also should contain other layers using the same aesthetic. In such cases, the [ggnewscale](https://eliocamp.github.io/ggnewscale/) package can be used to enable a new scale for those aesthetics. The following example shows a flowmap being added to a basemap from the [basemaps](https://jakob.schwalb-willmann.de/basemaps/) package, which itself uses the *fill* aesthetic.

```{r t12}
library(basemaps)

p <- basemap_ggplot(cantons,
map_service = "esri",
map_type = "world_hillshade",
alpha=0.3)+
theme_bw()+
theme(
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank())+
scale_x_continuous(expand = expansion(0,0))+
scale_y_continuous(expand = expansion(0,0))+
ggnewscale::new_scale_fill()+
labs(title = "Migration in Switzerland 2016",
caption = "Data: Federal Statistical Office, Switzerland")

p|>
add_flowmap(flowdat = data,
edge_width_factor = 0.7,k_nodes = 10,
outline_col = NA)+
theme(panel.grid = element_blank())+
scale_fill_gradient("Migration",low = "gray",high="red")
```

## Export as Simple Feature objects

Finally, it may be desirable to receive the edges and nodes as spatial objects. This way, they can be used for visualisations in other geospatial software.

To achieve this, the `flowmap_sf` function can be used. This function creates a list of two [sf](https://r-spatial.github.io/sf/) objects, one for the edges and one for the nodes.

```{r t13}
sf_objects <-
flowmap_sf(
flowdat = data,
edge_width_factor = 0.7,
k_nodes = 10,
crs=3857
)

sf_edges <- sf_objects$edges
sf_nodes <- sf_objects$nodes

sf_edges
sf_nodes
```

These objects can then be exported using `st_write`.

```{r t14, eval=FALSE}
sf::st_write(sf_nodes, "sf_nodes.gpkg", delete_dsn = TRUE)
sf::st_write(sf_edges, "sf_edges.gpkg", delete_dsn = TRUE)
```

## Creating matching flowmaps for comparison

For comparing flows (e.g., across dates) it is desirable to create multiple flowmaps with a matching style and scale. For this, the `add_flowmap_list` function can be used. It takes a list of inputs, rather than single inputs, and creates a list of ggplots, which can then be combined using the [patchwork](https://patchwork.data-imaginist.com/) package.

```{r t 15}
flowdatA <-
data.frame(
id_a = c("X1","X2","X3","X3","X1"),
id_b = c("X8","X7","X1","X8","X7"),
xa = c(2,14,10,10,2),
ya = c(6,10,9,9,6),
xb = c(10,4,2,10,4),
yb = c(4,10,6,4,10),
flow_ab = c(2,1,1,1,1),
flow_ba = c(8,1,1,1,2))

flowdatB <-
data.frame(
id_a = c("X1","X2","X3","X3","X1"),
id_b = c("X8","X7","X1","X8","X7"),
xa = c(2,14,10,10,2),
ya = c(6,10,9,9,6),
xb = c(10,4,2,10,4),
yb = c(4,10,6,4,10),
flow_ab = c(2,3,2,0.2,1),
flow_ba = c(3,3,2,1,5))

flowdatC <-
data.frame(
id_a = c("X1","X2","X3","X3","X1"),
id_b = c("X8","X7","X1","X8","X7"),
xa = c(2,14,10,10,2),
ya = c(6,10,9,9,6),
xb = c(10,4,2,10,4),
yb = c(4,10,6,4,10),
flow_ab = c(1,1,2,1,1)/2,
flow_ba = c(3,3,2,1,5)/3)

list_of_flowdats <- list(flowdatA, flowdatB, flowdatC)

base_plot <-
ggplot()+
theme_bw()+
coord_equal()

flowmap_plots <-
base_plot |>
add_flowmap_list(list_of_flowdats,
legend_gradient = T,
add_legend = "bottom",
k_nodes = 3)

library(patchwork)
(flowmap_plots[[1]]|flowmap_plots[[2]]|flowmap_plots[[3]]) +
plot_annotation(title = "Comparison of three different flow scenarios")&
theme(legend.position = "none",
panel.grid = element_blank())
```