R circlize - plot margins and plotting regions
Asked Answered
W

3

6

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])
}
Weimaraner answered 10/1, 2015 at 1:7 Comment(5)
It seems that a lot of this code is not central to the question. It would be great if you could reduce it to a small, reproducible example, e.g. by deleting all the code that prepares df1, and including the output of dput(df1) instead.Glyceryl
Hi jbaums, thank you for your comment. I edited the script in the post, so now it should be more focused. Now it concerns only the segment part, since links are not necessary. As for reproducibility, I know that dput() is usually a good solution, but in this case it would require more than 50 rows, while I used only 25 rows. But I can change it if you think it's better.Weimaraner
Sorry - I didn't realise you were loading a file included in the package. Cheers.Glyceryl
The blog shows how to view its own code for the graphs using the same library, file.show(system.file("demo/cfplot_reg.R", package = "migest")). Or were you already aware of that?Coronet
Please don't put rm(list = ls()) in your code in posts on SO. I removed it (could have commented it out).Tooley
C
6

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.

Cavin answered 10/1, 2015 at 9:43 Comment(2)
Hi gjabel, thank you very much for all the effort you are providing in sharing your work. I edited the post so that everyone can partecipate to our discussion, reading also the link part of the script. I tried to use circos.par, but probably I did't really understood how to manage it, since my result is unchanged. Would you be so kind to point how should I exactly insert this script?Weimaraner
@Weimaraner You just need to add the line in the answer above to your script (before the loop). The full working script is now on my github page: github.com/gjabel/migest/blob/master/demo/cfplot_nat.RCavin
S
7

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()

enter image description here

Sewellel answered 12/1, 2015 at 23:19 Comment(0)
C
6

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.

Cavin answered 10/1, 2015 at 9:43 Comment(2)
Hi gjabel, thank you very much for all the effort you are providing in sharing your work. I edited the post so that everyone can partecipate to our discussion, reading also the link part of the script. I tried to use circos.par, but probably I did't really understood how to manage it, since my result is unchanged. Would you be so kind to point how should I exactly insert this script?Weimaraner
@Weimaraner You just need to add the line in the answer above to your script (before the loop). The full working script is now on my github page: github.com/gjabel/migest/blob/master/demo/cfplot_nat.RCavin
C
1

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 to FALSE 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.

Chloramphenicol answered 8/8, 2017 at 4:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.