Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw directional spider network in geom_segment/ggplot2 in R?

Tags:

r

ggplot2

I am trying to work out to draw so-called spider network or desire line which illustrates movement of things (person, vehicle, etc.) between specific zones by direction.

This is the data frame that I am using:

df <- data.frame(O=c(1,2,4,4,4,6,6,6,7,7,7,9,9,9,9,10,10,10,11,12,12,12,32,86,108,128,128,157,157,157,157,157),
D=c(2,1,6,7,32,4,7,157,4,6,157,10,11,12,157,9,12,157,9,9,10,157,4,128,128,86,108,6,7,9,10,12),
trip=c(971,971,416,621,330,416,620,1134,621,620,625,675,675,378,439,675,724,472,675,378,724,563,330,610,405,610,405,1134,625,439,472,563),
lon.x=c(697746.6,696929.6,696748.8,696748.8,696748.8,694906.4,694906.4,694906.4,696769.4,696769.4,696769.4,698802.2,698802.2,698802.2,698802.2,698900.5,698900.5,698900.5,699686.7,696822.0,696822.0,696822.0,698250.7,702314.7,700907.1,702839.5,702839.5,694518.9,694518.9,694518.9,694518.9,694518.9),
lat.x=c(9312405,9311051,9308338,9308338,9308338,9307087,9307087,9307087,9305947,9305947,9305947,9304338,9304338,9304338,9304338,9302314,9302314,9302314,9306300,9303080,9303080,9303080,9309423,9320738,9321302,9322619,9322619,9301921,9301921,9301921,9301921,9301921),
lon.y=c(696929.6,697746.6,694906.4,696769.4,698250.7,696748.8,696769.4,694518.9,696748.8,694906.4,694518.9,698900.5,699686.7,696822.0,694518.9,698802.2,696822.0,694518.9,698802.2,698802.2,698900.5,694518.9,696748.8,702839.5,702839.5,702314.7,700907.1,694906.4,696769.4,698802.2,698900.5,696822.0),
lat.y=c(9311051,9312405,9307087,9305947,9309423,9308338,9305947,9301921,9308338,9307087,9301921,9302314,9306300,9303080,9301921,9304338,9303080,9301921,9304338,9304338,9302314,9301921,9308338,9322619,9322619,9320738,9321302,9307087,9305947,9304338,9302314,9303080))

df contains following fields:
O: origin of trips
D: destination of trips
trip: number of trips between O and D
lon.x: longitude of origin zone
lat.x: lattitude of origin zone
lon.y: longitude of destination zone
lat.y: lattitude of destination zone

Currently I can draw following figure by the script here using geom_segment in ggplot2 package:

library(ggplot2)

ggplot() +
  geom_segment(data = df, aes(x = lon.x, y = lat.x, xend = lon.y, yend = lat.y, size = trip),
               color = "blue", alpha = 0.5, show.legend = TRUE,
               position = position_dodge2(width = 100)) +
 scale_size_continuous(range = c(0, 5), breaks = c(300, 600, 900, 1200),
                       limits = c(100, 1200), name = "Person trips/day (over 100 trips)") +
 theme(legend.key = element_rect(colour = "transparent", fill = alpha("black", 0))) + 
 guides(size = guide_legend(override.aes = list(alpha = 1.0))) +
 geom_point(data = df, aes(x = lon.x, y = lat.x), pch = 16, size = 2.4)

enter image description here

The issue is that each line from O to D and from D to O are overlapped. I would prefer to plot the segments which are dodged based on the center line to properly visualize total number of trips and to see the balance of trips between zone pairs.

An example of desired result is shown below.

Dotted center line is not necessarily displayed (I just put it to show what the balance is). It is also preferable to change color by direction, for instance, red in clockwise and blue in anti-clockwise direction. Arrows are not necessary if direction can be shown in color.

enter image description here

I found some examples to solve the issue, however I cannot reach desirable result at this moment.

Calculation of offset for coordinates
It is not so easy to set offset for each direction in this example as I have around 80 zones which results in 6,400 pairs of zones. Offset geom_segment in ggplot

position_dodge2 function
It says that I can set margin between segments in width using variable, however if I use trip in it, it returns error. Also, it is not clear how much should I set the value for appropriate offset to make segments follow center lines. https://ggplot2.tidyverse.org/reference/position_dodge.html

geom_curve and arrow
It is also possible to draw lines with curve so that above issue could be solved. However curved segments are messy to observe the movements in one figure. Arrows are also a bit difficult to see the direction as the shape of arrows are not sharp though I change its style.

color=variable and position=dodge
I also tried to spread/gather the df to get new variable direction and to delete OD-pairs in opposite direction so that I thought I can easily dodge segments using color=direction and position=dodge in ggplot2, however it did not work well (segments are still overlapped). Small example is shown below.

O   D trip  direction    lon.x   lat.x    lon.y   lat.y
1   2  971  clock     697746.6 9312405 696929.6 9311051
2   1  300  anticlock 696929.6 9311051 697746.6 9312405
4   6  416  clock     696748.8 9308338 694906.4 9307087
4   7  621  anticlock 694906.4 9307087 696748.8 9308338

I highly appreciate your idea to obtain well-designed figure.
Please also see the following figure to get actual usage of spider network. enter image description here

like image 895
HSJ Avatar asked Jul 12 '18 08:07

HSJ


Video Answer


1 Answers

You could use trig functions to calculate an offset value, then plug this into the ggplot() call. Below is an example using your dataset above. I'm not exactly sure what you mean by clockwise, so I put in a simple dummy variable.

# make a dummy "clockwise" variable for now
df$clockwise = df$O > df$D
# angle from coordinates of stations
df$angle = atan((df$lat.y - df$lat.x)/(df$lon.y - df$lon.x))
# offsets from cos/sin of orthogonal angle
# scale the distance of the offsets by the trip size so wider bars offset more
# offset them one way if the trip is clockwise, the other way if not clockwise
df$xoffset = cos(df$angle - pi/2) * df$trip/5 * (2 * df$clockwise - 1)
df$yoffset = sin(df$angle - pi/2) * df$trip/5 * (2 * df$clockwise - 1)

ggplot() +
  geom_segment(data = df, aes(x = lon.x + xoffset, y = lat.x + yoffset, xend = lon.y + xoffset, yend = lat.y + yoffset, size = trip, color = clockwise),
               alpha = 0.5, show.legend = TRUE) +
  scale_size_continuous(range = c(0, 5), breaks = c(300, 600, 900, 1200),
                        limits = c(100, 1200), name = "Person trips/day (over 100 trips)") +
  theme(legend.key = element_rect(colour = "transparent", fill = alpha("black", 0))) + 
  guides(size = guide_legend(override.aes = list(alpha = 1.0))) +
  geom_point(data = df, aes(x = lon.x, y = lat.x), pch = 16, size = 2.4) +
  coord_fixed()

Sample plot with above code

like image 130
Tyr Wiesner-Hanks Avatar answered Sep 23 '22 13:09

Tyr Wiesner-Hanks