{"id":14485413,"url":"https://github.com/JohMast/flowmapper","last_synced_at":"2025-08-30T07:30:29.586Z","repository":{"id":224202951,"uuid":"762702155","full_name":"JohMast/flowmapper","owner":"JohMast","description":"Draw flows (migration, goods, money, information) on ggplots.","archived":false,"fork":false,"pushed_at":"2025-06-11T12:27:24.000Z","size":19783,"stargazers_count":33,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-21T04:12:20.323Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JohMast.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-24T13:02:34.000Z","updated_at":"2025-06-17T01:18:27.000Z","dependencies_parsed_at":null,"dependency_job_id":"772c0757-ea28-47f2-9e60-246721ab284a","html_url":"https://github.com/JohMast/flowmapper","commit_stats":null,"previous_names":["johmast/flowmapper"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/JohMast/flowmapper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JohMast%2Fflowmapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JohMast%2Fflowmapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JohMast%2Fflowmapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JohMast%2Fflowmapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JohMast","download_url":"https://codeload.github.com/JohMast/flowmapper/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JohMast%2Fflowmapper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272821176,"owners_count":24998599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-03T02:00:46.809Z","updated_at":"2025-08-30T07:30:29.579Z","avatar_url":"https://github.com/JohMast.png","language":"R","funding_links":[],"categories":["Plot layers"],"sub_categories":[],"readme":"---\noutput: github_document\n---\n\n[![CRAN status](https://www.r-pkg.org/badges/version/flowmapper)](https://CRAN.R-project.org/package=flowmapper) \u003c!-- [![CRAN downloads](https://cranlogs.r-pkg.org/badges/last-month/flowmapper?color=brightgreen)](https://CRAN.R-project.org/package=flowmapper) --\u003e\n[![](https://cranlogs.r-pkg.org/badges/flowmapper)](https://cran.r-project.org/package=flowmapper)\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,message = FALSE,warning=FALSE,\n  comment = \"#\u003e\",\n  fig.path = \"man/figures/README-\",\n  out.width = \"70%\",\n  dpi=300\n  # fig.width=7\n)\n```\n\n# flowmapper\n\nflowmapper allows to create ggplots with flowmaps in the style of \u003chttps://flowmap.gl/\u003e.\n\n## Installation\n\nYou can install the released version of flowmapper from CRAN:\n\n``` r\ninstall.packages(\"flowmapper\")\n```\n\nOr, you can install the development version of flowmapper like so:\n\n``` r\ndevtools::install_github(\"https://github.com/JohMast/flowmapper\")\n```\n\n## Details\n\nflowmapper 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\n\n-   the x and y coordinates of each these nodes,\n\n-   a unique id for each of these nodes,\n\n-   the intensity of flow between those nodes in both directions (from a to b, and from b to a).\n\nThe data.frame should have the following columns:\n\n```{r example}\n\ntestdata \u003c-\ndata.frame(\n   id_a = c(\"X1\",\"X2\",\"X3\",\"X3\",\"X1\",\"X2\",\"X2\"),\n   id_b = c(\"X5\",\"X4\",\"X1\",\"X5\",\"X4\",\"X5\",\"X3\"),\n   xa = c(2,14,10,10,2,14,14),\n   ya = c(6,10,9,9,6,10,10),\n   xb = c(10,4,2,10,4,10,10),\n   yb = c(4,10,6,4,10,4,9),\n   flow_ab = c(1,2,3,3,1,1,4),\n   flow_ba = c(2,3,2,5,2,1,5)\n)\n\n```\n\nThe dataframe and the ggplot that the flowmap should be added to are then passed into `add_flowmap`.\n\n```{r c2, warning=FALSE}\nlibrary(ggplot2)\nplot \u003c- ggplot() # empty ggplot\n\nlibrary(flowmapper)\nplot |\u003e\n  add_flowmap(testdata)+\n  coord_equal()  # coord equal is highly recommended to create symmetric shapes\n```\n\nIf 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.\n\n```{r t1}\nplot |\u003e\n  add_flowmap(testdata,k_nodes = 4)+\n  coord_equal()\n```\n\nTransparency and outline of the arrows can be controlled with the `outline_col` and `alpha` arguments.\n\n```{r t3}\nplot |\u003e\n  add_flowmap(testdata, outline_col = \"orange\", alpha=0.5)+\n  coord_equal()  # coord equal is highly recommended to create symmetric shapes\n```\n\nThe flow arrows are geom_polygons, with the flow mapped to the fill aesthetic. Thus, the fill can be adjusted like for any geom.\n\n```{r t4}\nplot |\u003e\n  add_flowmap(testdata)+\n  coord_equal() + \n  scale_fill_gradient(low=\"black\", high = \"red\")\n```\n\nSize of the edges and offset (distance between two paired edges) can be controlled with `edge_width_factor` and `edge_offset_factor` .\n\n```{r t5}\nplot |\u003e\n  add_flowmap(testdata, edge_offset_factor = 4, edge_width_factor = 2)+\n  coord_equal()\n```\n\nFinally, the size of the nodes can be adjusted with `node_radius_factor`, and the distance between nodes and edges with `node_buffer_factor`.\n\n```{r t6}\n\nplot |\u003e\n  add_flowmap(testdata, node_radius_factor = 2, node_buffer_factor = 0.5)+\n  coord_equal()\n```\n\nBecause 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`.\n\n```{r t7}\nundebug(add_flowmap)\nplot |\u003e\n  add_flowmap(testdata,add_legend = \"left\")+\n  coord_equal()\n```\n\nInstead of a monotone legend, the legend color can be set to represent the flow intensity with `legend_gradient`.\n\n```{r t7_5}\n# debug(add_flowmap)\nplot |\u003e\n  add_flowmap(testdata, add_legend = \"bottom\",legend_gradient=T)+\n  coord_equal()+\n  theme(legend.position = \"none\")\n```\n\nThe 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.\n\n```{r t8, eval=FALSE, include=TRUE}\nlibrary(plotly)\nplot \u003c- \n  plot |\u003e\n  add_flowmap(testdata)+\n  coord_equal()\nggplotly(plot,tooltip = c(\"text\",\"fill\"))\n```\n\n![](man/figures/plotly_example.PNG)\n\n## Example\n\nThe 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\u0026a=0\u0026d=1\u0026c=0\u0026lt=1\u0026col=Default\u0026f=45) flowmap). The data contains migration flows between the 26 Cantons of Switzerland.\n\n```{r t9}\nlibrary(dplyr,warn.conflicts = FALSE)\nlibrary(ggplot2,warn.conflicts = FALSE)\nlibrary(sf)\nlibrary(flowmapper)\n\n# load migration data\ndata \u003c-\n  flowmapper::CH_migration_data\nhead(data)\n```\n\nAs a background for the flow map, a ggplot is created using the administrative boundaries, sourced from the [GADM](https://gadm.org/index.html) dataset.\n\n```{r t10}\ncantons \u003c- flowmapper::cantons\nst_crs(cantons) \u003c- 3857\n\n# basic plot with just the admin units\np \u003c- ggplot(cantons)+\n  geom_sf(fill=NA,col=\"gray30\",linewidth=0.5) +\n  ggdark::dark_theme_bw()+\n  theme(panel.border = element_blank(),\n        axis.title = element_blank(),\n        axis.text = element_blank(),\n        axis.ticks = element_blank())+\n  labs(title = \"Migration in Switzerland 2016\",\n       caption = \"Data: Federal Statistical Office, Switzerland\")\np\n```\n\nThe 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.\n\n```{r t11}\np2 \u003c-\n  p|\u003e\n  add_flowmap(flowdat = data,\n              add_legend = \"bottom\",\n              edge_width_factor = 0.7,\n              k_nodes = 10,legend_gradient=T,\n              outline_col = NA)+\n  theme(panel.grid = element_blank())+\n  scale_fill_gradient(\"Migration\",\n                      low = \"darkblue\",\n                      high=\"white\")\np2\n```\n\nThe 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.\n\n```{r t12}\nlibrary(basemaps)\n\np \u003c- basemap_ggplot(cantons,\n                    map_service = \"esri\",\n                    map_type = \"world_hillshade\",\n                    alpha=0.3)+\n  theme_bw()+\n  theme(\n    axis.title = element_blank(),\n    axis.text = element_blank(),\n    axis.ticks = element_blank())+\n  scale_x_continuous(expand = expansion(0,0))+\n  scale_y_continuous(expand = expansion(0,0))+\n  ggnewscale::new_scale_fill()+\n  labs(title = \"Migration in Switzerland 2016\",\n       caption = \"Data: Federal Statistical Office, Switzerland\")\n\np|\u003e\n  add_flowmap(flowdat = data,\n              edge_width_factor = 0.7,k_nodes = 10,\n              outline_col = NA)+\n  theme(panel.grid = element_blank())+\n  scale_fill_gradient(\"Migration\",low = \"gray\",high=\"red\")\n```\n\n## Export as Simple Feature objects\n\nFinally, 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.\n\nTo 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.\n\n```{r t13}\nsf_objects \u003c- \n  flowmap_sf(\n  flowdat = data,\n  edge_width_factor = 0.7,\n  k_nodes = 10,\n  crs=3857\n)\n\nsf_edges \u003c- sf_objects$edges\nsf_nodes \u003c- sf_objects$nodes\n\nsf_edges\nsf_nodes\n```\n\nThese objects can then be exported using `st_write`.\n\n```{r t14, eval=FALSE}\nsf::st_write(sf_nodes, \"sf_nodes.gpkg\", delete_dsn = TRUE)\nsf::st_write(sf_edges, \"sf_edges.gpkg\", delete_dsn = TRUE)\n```\n\n\n## Creating matching flowmaps for comparison\n\nFor 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.\n\n```{r t 15}\nflowdatA \u003c-\n  data.frame(\n    id_a = c(\"X1\",\"X2\",\"X3\",\"X3\",\"X1\"),\n    id_b = c(\"X8\",\"X7\",\"X1\",\"X8\",\"X7\"),\n    xa = c(2,14,10,10,2),\n    ya = c(6,10,9,9,6),\n    xb = c(10,4,2,10,4),\n    yb = c(4,10,6,4,10),\n    flow_ab = c(2,1,1,1,1),\n    flow_ba = c(8,1,1,1,2))\n\nflowdatB \u003c-\n  data.frame(\n    id_a = c(\"X1\",\"X2\",\"X3\",\"X3\",\"X1\"),\n    id_b = c(\"X8\",\"X7\",\"X1\",\"X8\",\"X7\"),\n    xa = c(2,14,10,10,2),\n    ya = c(6,10,9,9,6),\n    xb = c(10,4,2,10,4),\n    yb = c(4,10,6,4,10),\n    flow_ab = c(2,3,2,0.2,1),\n    flow_ba = c(3,3,2,1,5))\n\n\nflowdatC \u003c-\n  data.frame(\n    id_a = c(\"X1\",\"X2\",\"X3\",\"X3\",\"X1\"),\n    id_b = c(\"X8\",\"X7\",\"X1\",\"X8\",\"X7\"),\n    xa = c(2,14,10,10,2),\n    ya = c(6,10,9,9,6),\n    xb = c(10,4,2,10,4),\n    yb = c(4,10,6,4,10),\n    flow_ab = c(1,1,2,1,1)/2,\n    flow_ba = c(3,3,2,1,5)/3)\n\n\nlist_of_flowdats \u003c- list(flowdatA, flowdatB, flowdatC) \n\nbase_plot \u003c- \n  ggplot()+\n  theme_bw()+\n  coord_equal()\n\nflowmap_plots \u003c- \n  base_plot |\u003e \n  add_flowmap_list(list_of_flowdats,\n                   legend_gradient = T,\n                   add_legend = \"bottom\",\n                   k_nodes = 3)\n\nlibrary(patchwork)\n(flowmap_plots[[1]]|flowmap_plots[[2]]|flowmap_plots[[3]])  +\n  plot_annotation(title = \"Comparison of three different flow scenarios\")\u0026\n  theme(legend.position = \"none\",\n        panel.grid = element_blank())\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJohMast%2Fflowmapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FJohMast%2Fflowmapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJohMast%2Fflowmapper/lists"}