In my previous post, I shared a flow map style that was inspired by a hand drawn map. Today’s post is inspired by a recent academic paper recommended to me by Radoslaw Panczak @RPanczak and Thomas Gratier @ThomasG77:
Jenny et al. (2016) performed a study on how to best design flow maps. The resulting design principles are:
- number of flow overlaps should be minimized;
- sharp bends and excessively asymmetric flows should be avoided;
- acute intersection angles should be avoided;
- flows must not pass under unconnected nodes;
- flows should be radially arranged around nodes;
- quantity is best represented by scaled flow width;
- flow direction is best indicated with arrowheads;
- arrowheads should be scaled with flow width, but arrowheads for thin flows should be enlarged; and
- overlaps between arrowheads and flows should be avoided.
Many of these points concern the arrangement of flow lines but I want to talk about those design principles that can be implemented in a QGIS line style. I’ve summarized the three core ideas:
- use arrow heads and scale arrow width according to flow,
- enlarge arrow heads for thin flows, and
- use nodes to arrange flows and avoid overlaps of arrow heads and flows
To get started, we can use a standard QGIS arrow symbol layer. To represent the flow value (“weight”) according to the first design principle, all arrow parameters are data-defined:
scale_linear("weight",0,10,0.1,3)
To enlarge the arrow heads for thin flow lines, as required by the second design principle, we can add a fixed value to the data-defined head length and thickness:
scale_linear("weight",0,10,0.1,1.5)+1.5
The main issue with this flow map is that it gets messy as soon as multiple arrows end at the same location. The arrow heads are plotted on top of each other and at some point it is almost impossible to see which arrow starts where. This is where the third design principle comes into play!
To fix the overlap issue, we can add big round nodes at the flow start and end points. These node buffers are both used to render circles on the map, as well as to shorten the arrows by cutting off a short section at the beginning and end of the lines:
difference(
difference(
$geometry,
buffer( start_point($geometry), 10000 )
),
buffer( end_point( $geometry), 10000 )
)
Note that the buffer values in this expression only produce appropriate results for line datasets which use a CRS in meters and will have to be adjusted for other units.
It’s great to have some tried and evaluated design guidelines for our flow maps. As always: Know your cartography rules before you start breaking them!
PS: To draw a curved arrow, the line needs to have one intermediate point between start and end – so three points in total. Depending on the intermediate point’s position, the line is more or less curved.

































