I'm trying to replicate the graphs of this website https://gjabel.wordpress.com/2014/03/28/circular-migration-flow-plots-in-r/ using the r library "circlize". Unfortunately I have two problems:
First of all I got a warning for all plotting regions (i.e. Note: 1 point is out of plotting region in sector 'Mexico', track '1'
). I thought the problem was that in circos.text
and circos.axis
, since I was using them along with direction
, which is a deprecated function. But also using facing
instead of direction
, the problem persists. So I guess I didn't understand the meaning of this warning. Do you have some hints so to help me?
Moreover, in my plot, links were very far from segments. Thus I tried to reduce the track.margin, which solve the problem, but now the names of the segments are outside the margins, and I cannot visualize them. Is there a better way to solve this problem?
This is what I wrote so far (which is almost entirely taken from this site https://github.com/null2/globalmigration)
library("circlize")
library("plyr")
library("migest")
#load data
m<-read.table(system.file("science", "country_custom.txt", package = "migest"), skip=2, stringsAsFactors=FALSE)
#1)a data.frame to store information on each segment of the circle to be plotted
df1<-m[,1:3]
names(df1)<-c("order","rgb","region")
df1$region<-gsub("\\.", "\n", df1$region)
#2) a matrix containing the flow data (in this example only 28 countries)
m<-m[,-(1:3)]/1e05
m<-as.matrix(m)
dimnames(m)<-list(orig=df1$region,dest=df1$region)
#sort order of data.frame and matrix for plotting in circos
df1<-arrange(df1, order) #reordering a data frame by its columns
df1$region <- factor(df1$region, levels=df1$region)
m<-m[levels(df1$region),levels(df1$region)]
#define ranges of circos sectors and their colors (both of the sectors and the links)
#determine the length of segments on the outside of the plot.
df1$xmin <- 0
df1$xmax <- rowSums(m)+colSums(m) #inflows+outflows
#set the colour names for segments and flows
n<-nrow(df1)
df1 <- cbind(df1, matrix(as.numeric(unlist(strsplit(df1$rgb,","))),nrow=n, byrow=TRUE) )
names(df1)[ncol(df1)-2:0]<-c("r","g","b")
df1$rcol<-rgb(df1$r, df1$g, df1$b, max = 255)
df1$lcol<-rgb(df1$r, df1$g, df1$b, alpha=200, max = 255)
##plot sectors
windows()
par(mar=rep(0,4))
circos.clear()
#1)basic circos graphic parameters
circos.par(cell.padding=c(0,0,0,0), track.margin=c(0,0.01), start.degree = 90, gap.degree =4)
#2)sector details
circos.initialize(factors = df1$region, xlim = cbind(df1$xmin, df1$xmax))
#3)plot sectors
circos.trackPlotRegion(ylim = c(0, 1), factors = df1$region, track.height=0.1,
panel.fun = function(x, y) {
#select details of current sector
name = get.cell.meta.data("sector.index")
i = get.cell.meta.data("sector.numeric.index")
xlim = get.cell.meta.data("xlim")
ylim = get.cell.meta.data("ylim")
#text direction (dd) and adjusmtents (aa)
theta = circlize(mean(xlim), 1.3)[1, 1] %% 360
dd <- ifelse(theta < 90 || theta > 270, "vertical_right", "vertical_left")
aa = c(1, 0.5)
if(theta < 90 || theta > 270) aa =c(0, 0.5)
#plot country labels
circos.text(x=mean(xlim), y=1.7, labels=name, direction = dd,
cex=0.6,adj = aa)
#circos.text(x=mean(xlim), y=2, labels=name, facing = "bending",cex=0.6)
#plot main sector
circos.rect(xleft=xlim[1], ybottom=ylim[1], xright=xlim[2], ytop=ylim[2],
col = df1$rcol[i], border=df1$rcol[i])
#blank in part of main sector
circos.rect(xleft=xlim[1], ybottom=ylim[1], xright=xlim[2]-rowSums(m)[i], ytop=ylim[1]+0.3,
col = "white", border = "white")
#white line all the way around
circos.rect(xleft=xlim[1], ybottom=0.3, xright=xlim[2], ytop=0.32, col = "white", border = "white")
#plot axis
#NOTE: Ticks indicate the number of migrants in 100s.
circos.axis(labels.cex=0.6, direction = "outside", major.at=seq(from=0,to=floor(df1$xmax)[i],by=5),
minor.ticks=1, labels.away.percentage = 0.15)
})
Thank you very much for your help.
EDIT: I add a second part of the script, since it seems that it is necessary to solve the second issue.
#plot links
#create a new dataframe containing the long form of the matrix m
#add sum values to df1, marking the x-position of the first links out (sum1) and in (sum2). Updated for further links in loop below.
df1$sum1 <- colSums(m) #outflows
df1$sum2 <- numeric(n)
#create a data.frame of the flow matrix sorted by flow size, to allow largest flow plotted first
df2<-cbind(as.data.frame(m),orig=rownames(m), stringsAsFactors=FALSE)
#long matrix
df2<-reshape(df2, idvar="orig", varying=list(1:n), direction="long", timevar="dest", time=rownames(m), v.names = "m")
df2<-arrange(df2,desc(m))
#keep only the largest flows to avoid clutter
df2<-subset(df2, m>quantile(m,0.925))
#plot links
for(k in 1:nrow(df2)){
require("circlize")
#i,j reference of flow matrix
#note: you are selecting the states in region1 according to the edgelist in region2
i<-match(df2$orig[k],df1$region)
j<-match(df2$dest[k],df1$region)
#plot link
#sector.index1=sender
#point1=size of the base of the link at the origin
#We set the origin segment to start at the current sum of outflows from the sender country (df1$sum1[i])
#We set the end of the segment outflow equal to the total outflows from the sender country, plus the flow from the edge we considered
circos.link(sector.index1=df1$region[i], point1=c(df1$sum1[i], df1$sum1[i] + abs(m[i, j])),
sector.index2=df1$region[j], point2=c(df1$sum2[j], df1$sum2[j] + abs(m[i, j])),
col = df1$lcol[i]) #, top.ratio==0.66, top.ratio.low==0.67)
#note: The height and thickness of the link at its mid-point is determined by the top.ratio and the top.ratio.low argument
#update sum1 and sum2 for use when plotting the next link
df1$sum1[i] = df1$sum1[i] + abs(m[i, j])
df1$sum2[j] = df1$sum2[j] + abs(m[i, j])
}
You may check chordDiagram()
in current version of circlize. chordDiagram()
is a flexible function which makes such Chord Diagram (i.e. a circular plot with links inside).
This function was introduced in recent versions of circlize and you don't need too much code to self-define a Chord Diagram. There is also a vignette shipped with the package which gives you detailed introduction of how to make a simple or complex Chord Diagram.
A quick demonstration of reproducing the figure you are interested is as follows:
mat = matrix(runif(36), 6)
rownames(mat) = letters[1:6]
colnames(mat) = letters[1:6]
library(circlize)
circos.par(gap.degree = 8)
chordDiagram(mat, grid.col = 1:6, directional = TRUE, annotationTrack = "grid",
preAllocateTracks = list(list(track.height = 0.05),
list(track.height = 0.05)))
circos.trackPlotRegion(track.index = 1, panel.fun = function(x, y) {
xlim = get.cell.meta.data("xlim")
ylim = get.cell.meta.data("ylim")
sector.index = get.cell.meta.data("sector.index")
circos.text(mean(xlim), mean(ylim), sector.index, facing = "inside", niceFacing = TRUE)
}, bg.border = NA)
circos.trackPlotRegion(track.index = 2, panel.fun = function(x, y) {
circos.axis("bottom", major.tick.percentage = 0.2, labels.cex = 0.4)
}, bg.border = NA)
circos.clear()
The first problem concerning the notes messages is not really a problem. You can still get the plot you want regardless of the warning. You can subdue the message using
circos.par(points.overflow.warning=FALSE)
The second problem is a result of a recent update to the circlize package. You need to change the track margins from their initial settings by adding
circos.par(track.margin=c(0,0))
after circos.trackPlotRegion
command and before the links are drawn via the circos.link
function in the for
loop.
Sorry for the problems. I have been meaning to update the demo file in the migest package for a while now but sort of forgot about it over the holidays.
As the author explained here
"points.overflow.warning" Since each cell is in fact not a real plotting region but only an ordinary rectangle, it does not eliminate points that are plotted out of the region. So if some points are out of the plotting region,
circlize
would continue drawing the points and printing warnings. In some cases, draw something out of the plotting region is useful, such as draw some legend or text. Set this value toFALSE
to turn off the warnings.
by canvas.xlim
and canvas.ylim
(described in the same page above), you can set up your canvas in order to avoid, or just ignore the warning.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With