Circular heatmaps are pretty. With circlize package, it is possible to
implement circular heatmaps by the low-level function
circos.rect()
.
From version 0.4.10, I implemented a new high-level function circos.heatmap()
which
simplifies the creation of circular heatmaps. In this post, I will demostrate
the usage of the new circos.heatmap()
function.
First let’s generate a random matrix and randomly split it into five groups.
set.seed(123)
mat1 = rbind(cbind(matrix(rnorm(50*5, mean = 1), nr = 50),
matrix(rnorm(50*5, mean = -1), nr = 50)),
cbind(matrix(rnorm(50*5, mean = -1), nr = 50),
matrix(rnorm(50*5, mean = 1), nr = 50))
)
rownames(mat1) = paste0("R", 1:100)
colnames(mat1) = paste0("C", 1:10)
mat1 = mat1[sample(100, 100), ] # randomly permute rows
split = sample(letters[1:5], 100, replace = TRUE)
split = factor(split, levels = letters[1:5])
Following plot is the normal layout of the heatmap (by the ComplexHeatmap package).
library(ComplexHeatmap)
Heatmap(mat1, row_split = split)
In the next sections, I will demonstrate how to visualize it circularly.
Input data
The input for circos.heatmap()
should be a matrix (or a vector which will be
converted to a one-column matrix). If the matrix is split into groups, a
categorical variable must be specified with the split
argument. Note the
value of spilt
should be a character vector or a factor. If it is a numeric
vector, it is converted to characters internally.
Colors are important aesthetic mappings for the values in the matrix. In
circos.heatmap()
, users must specify col
argument with a user-defined
color schema. If the matrix is continuous numeric, value for col
should be a
color mapping generated by
colorRamp2()
, and if
the matrix is in characters, value of col
should be a named color vector.
Following plot is the circular version of the previous heatmap. Note the matrix rows distribute in the circular direction and the matrix columns distribute in the radical direction. In following plot, the circle is split into five sectors where each sector corresponds to one row group.
library(circlize) # >= 0.4.10
col_fun1 = colorRamp2(c(-2, 0, 2), c("blue", "white", "red"))
circos.heatmap(mat1, split = split, col = col_fun1)
circos.clear()
There is one thing very important that is after creating the circular heatmap,
you must call circos.clear()
to remove the layout completely. I will explain
this point later in this post.
If split
is not specified, there is only one big sector that contains
the complete heatmap.
circos.heatmap(mat1, col = col_fun1)
circos.clear()
Circular layout
Similar as other circular plots generated by circlize package, the circular layout
can be controlled by circos.par()
before making the plot.
The parameters for the heatmap track can be controlled in circos.heatmap()
function,
such as track.height
(height of the track) and bg.border
(border of the track).
In the following example, The labels for the sectors are added by setting the
show.sector.labels
argument. The order of sectors is c("a", "b", "c", "d", "e")
clock-wisely. You can see in the following plot, sector a
starts
from \(\theta = 90^{\circ}\).
circos.par(start.degree = 90, gap.degree = 10)
circos.heatmap(mat1, split = split, col = col_fun1, track.height = 0.4,
bg.border = "green", bg.lwd = 2, bg.lty = 2, show.sector.labels = TRUE)
circos.clear()
If the value for split
argument is a factor, the order of the factor levels
controls the order of heatmaps. If split
is a simple vector, the order of
heatmaps is unique(split)
.
# note since circos.clear() was called in the previous plot,
# now the layout starts from theta = 0 (the first sector is 'e')
circos.heatmap(mat1, split = factor(split, levels = c("e", "d", "c", "b", "a")),
col = col_fun1, show.sector.labels = TRUE)
circos.clear()
Dendrograms and row names
By default, the numeric matrix is clustered on rows, thus, there are
dendrograms generated from the clustering. dend.side
argument controls the
position of dendrograms relative to the heatmap track. Note, the dendrograms
are on a separated track.
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside")
circos.clear()
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "outside")
circos.clear()
The height of the dendrograms is controlled by dend.track.height
argument.
Row names of the matrix can be drawn by setting rownames.side
argument.
Row names are also drawn in a separated track.
circos.heatmap(mat1, split = split, col = col_fun1, rownames.side = "inside")
circos.clear()
text(0, 0, 'rownames.side = "inside"')
circos.heatmap(mat1, split = split, col = col_fun1, rownames.side = "outside")
circos.clear()
text(0, 0, 'rownames.side = "outside"')
Row names of the matrix and the dendrograms can be both drawn. Of course, they cannot be on the same side of the heatmap track.
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside",
rownames.side = "outside")
circos.clear()
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "outside",
rownames.side = "inside")
circos.clear()
Graphic parameters for row names can be set as a scalar or a vector with the length same as the number of rows in the matrix.
circos.heatmap(mat1, split = split, col = col_fun1, rownames.side = "outside",
rownames.col = 1:nrow(mat1) %% 10 + 1,
rownames.cex = runif(nrow(mat1), min = 0.3, max = 2),
rownames.font = 1:nrow(mat1) %% 4 + 1)
circos.clear()
The graphic parameters of dendrogram can be set by directly rendering the dendrograms through a callback function, as will be demonstrated later.
Clustering
By default, the numeric matrix is clustered on rows. cluster
argument
can be set to FALSE
to turn off the clustering.
Of cource, when cluster
is set to FALSE
, no dendrogram is drawn even
if dend.side
is set.
circos.heatmap(mat1, split = split, cluster = FALSE, col = col_fun1)
circos.clear()
Clustering method and distance method are controlled by clustering.method
and distance.method
arguments.
Please note circos.heatmap()
does not directly support clustering on
matrix columns. You should apply column reordering before send to
circos.heatmap()
, e.g.,
column_od = hclust(dist(t(mat1)))$order
circos.heatmap(mat1[, column_od])
circos.clear()
The value of the cluster
argument can also be a clustering object (e.g. a dendrogram
/hclust
object or
any other object that can be converted to dendrogram
). In this case, if you want to split the heatmap,
the value for split
argument can only be a single number.
par(mfrow = c(1, 2))
hc = hclust(dist(mat1), method = "single")
circos.heatmap(mat1, cluster = hc, col = col_fun1, dend.side = "inside")
circos.clear()
circos.heatmap(mat1, cluster = hc, split = 2, col = col_fun1, dend.side = "inside")
circos.clear()
par(mfrow = c(1, 1))
Callback on dendrograms
The clustering generates dendrograms. Callback function can be applied to every dendrogram after it is generated in the corresponding sector. The callback function edits the dendrograms such as 1. reorder the dendrogrmas, or 2. color the dendrograms.
In circos.heatmap()
, a user-defined function should be set to
dend.callback
argument. The user-defined function should have three
arguments:
dend
: The dendrogram in the current sector.m
: The sub-matrix that corresponds to the current sector.si
: The sector index (or the sector name) for the current sector.
The default callback function is defined as follows and it reorders the dendrogram by weighting the matrix row means.
function(dend, m, si) reorder(dend, rowMeans(m))
Following example reorders the dendrograms in every sector by dendsort::dendsort()
.
library(dendsort)
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside",
dend.callback = function(dend, m, si) {
dendsort(dend)
}
)
circos.clear()
We can use color_branches()
from dendextend package to render the
dendrogram edges. E.g., to assign different colors for the dendrograms in
the five sectors. Here the height of the dendrogram track is increased
by the dend.track.height
argument.
library(dendextend)
dend_col = structure(1:5, names = letters[1:5])
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside",
dend.track.height = 0.2,
dend.callback = function(dend, m, si) {
# when k = 1, it renders one same color for the whole dendrogram
color_branches(dend, k = 1, col = dend_col[si])
}
)
circos.clear()
Or if the matrix is not split, we can assign the sub-dendrograms with different colors.
circos.heatmap(mat1, col = col_fun1, dend.side = "inside",
dend.track.height = 0.2,
dend.callback = function(dend, m, si) {
color_branches(dend, k = 4, col = 2:5)
}
)
circos.clear()
Multiple heatmap tracks
If you make a circular plot which only contains one heatmap track, using
circos.heatmap()
is very straightforward. If you make a more complex
circular plot which contains multiple tracks, there are more details on
circos.heatmap()
you should know.
The first call of circos.heatmap()
actually initializes the layout, i.e.,
applying clustering and splitting the matrix. The dendrograms and split
variable are stored internally. This is why you should explicitly call
circos.clear()
to remove all the internal variables so that it can ensure
when you make a new circular heatmap, the first call of circos.heatmap()
is
in a clean environment.
The first call of circos.heatmap()
determines the row ordering (the order in
the circular direction) for all tracks, thus, matrices in the following tracks
share the same row ordering as in the first track. Also, the matrices in the
following tracks are also split accordingly to the split in the first heatmap
track.
If clustering is not applied in the first heatmap track, the natural ordering
of rows (i.e., c(1, 2, ..., n)
) is used.
mat2 = mat1[sample(100, 100), ] # randomly permute mat1 by rows
col_fun2 = colorRamp2(c(-2, 0, 2), c("green", "white", "red"))
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "outside")
circos.heatmap(mat2, col = col_fun2)
circos.clear()
If I switch the two tracks, you can see now the clustering is controlled by the first heatmap track which is the green-red heatmap track.
circos.heatmap(mat2, split = split, col = col_fun2, dend.side = "outside")
circos.heatmap(mat1, col = col_fun1)
circos.clear()
You might want to ask, what if I don’t want the clustering to be determined by
the first track, while the second or the third track? The solution is simple.
As I mentioned, the first call of circos.heatmap()
initializes the layout.
Actually the initialization can be manually done by explicitly calling
circos.heatmap.initialize()
function which circos.heatmap()
internally
calls.
In circos.heatmap.initialize()
, you specify whatever matrix you want to
apply clustering as well as the split variable, then, the following
circos.heatmap()
calls all share this layout.
In the following example, the global layout is determined by mat1
which is
visualized in the second track. I set dend.side = "outside"
in the first
track and actually you can find the dendrograms are actually generatd based
on the matrix in the second track.
circos.heatmap.initialize(mat1, split = split)
circos.heatmap(mat2, col = col_fun2, dend.side = "outside")
circos.heatmap(mat1, col = col_fun1)
circos.clear()
In the next example, the heatmap layout is generated from mat1
, while
the two heatmap tracks only contain five columns for each.
circos.heatmap.initialize(mat1, split = split)
circos.heatmap(mat1[, 1:5], col = col_fun1)
circos.heatmap(mat1[, 6:10], col = col_fun1)
circos.clear()
With other tracks
circos.heatmap()
can also be integrated with other non-heatmap tracks,
however, it is a little bit tricky. In the circular layout, values on x-axes
and y-axes are just the numeric indices. Assume there are nr
rows and nc
columns for the heatmap in a sector, the heatmap rows are drawn in intervals
of (0, 1)
, c(1, 2)
, …, c(nr-1, nr)
and similar for the heatmap
columns. Also the original matrix is reordered. All these effects need to be
considered if more tracks are added to make sure to have the correct
correspondance to the heatmap track.
After the heatmap layout is done, additional information for the
tracks/sectors/cells can be retrieved by the special variable CELL_META
. The
additional meta data for the cell/sector are listed as follows and they are
important for correctly corresponding to the heatmap track.
CELL_META$row_dend
or simplyCELL_META$dend
: the dendrogram in the current sector. If no clustering was done, the value isNULL
.CELL_META$row_order
or simplyCELL_META$order
: the row ordering of the sub-matrix in the current sector after clustering. If no clustering was done, the value isc(1, 2, ..., )
.CELL_META$subset
: The subset of indices in the original complete matrix. The values are sorted increasing.
Following are the outputs of CELL_META$row_dend
, CELL_META$row_order
and CELL_META$subset
in the first sector in the example circular heatmap.
CELL_META$row_dend
## 'dendrogram' with 2 branches and 14 members total, at height 10.51736
CELL_META$row_order
## [1] 2 6 4 12 8 1 5 10 7 9 13 11 3 14
CELL_META$subset
## [1] 8 9 14 18 20 37 55 62 66 72 78 85 93 97
In following example, I add a track which visualizes the row means of the
first five columns in mat1
. I added cell.padding = c(0.02, 0, 0.02, 0)
so
that the maximal and minimal points won’t overlap with the top and bottom
borders of the cells.
circos.heatmap(mat1, split = split, col = col_fun1)
row_mean = rowMeans(mat1[, 1:5])
circos.track(ylim = range(row_mean), panel.fun = function(x, y) {
y = row_mean[CELL_META$subset]
y = y[CELL_META$row_order]
circos.lines(CELL_META$cell.xlim, c(0, 0), lty = 2, col = "grey")
circos.points(seq_along(y) - 0.5, y, col = ifelse(y > 0, "red", "blue"))
}, cell.padding = c(0.02, 0, 0.02, 0))
circos.clear()
Similarly, if the points track is put as the first track, the layout should be initialized in advance.
circos.heatmap.initialize(mat1, split = split)
# This is the same as the previous example
circos.track(ylim = range(row_mean), panel.fun = function(x, y) {
y = row_mean[CELL_META$subset]
y = y[CELL_META$row_order]
circos.lines(CELL_META$cell.xlim, c(0, 0), lty = 2, col = "grey")
circos.points(seq_along(y) - 0.5, y, col = ifelse(y > 0, "red", "blue"))
}, cell.padding = c(0.02, 0, 0.02, 0))
circos.heatmap(mat1, col = col_fun1) # no need to specify 'split' here
circos.clear()
Boxplots are very frequently used to correspond to the matrix rows.
circos.heatmap(mat1, split = split, col = col_fun1)
circos.track(ylim = range(mat1), panel.fun = function(x, y) {
m = mat1[CELL_META$subset, 1:5, drop = FALSE]
m = m[CELL_META$row_order, , drop = FALSE]
n = nrow(m)
# circos.boxplot is applied on matrix columns, so here we transpose it.
circos.boxplot(t(m), pos = 1:n - 0.5, pch = 16, cex = 0.3)
circos.lines(CELL_META$cell.xlim, c(0, 0), lty = 2, col = "grey")
}, cell.padding = c(0.02, 0, 0.02, 0))
circos.clear()
Add annotations
The labels for the sectors can be added by setting show.sector.labels = TRUE
,
however, this does not provide any customization on the labels. Users can
customize their own labels by self-defining a panel.fun
function,
demonstrated as follows. Here the labels are added 2mm away from the
heatmap track (by convert_y(2, "mm")
which defines the offset in the y-direction).
Here I set track.index = get.current.track.index()
to make sure the labels
are always added in the correct track.
circos.heatmap(mat1, split = split, col = col_fun1)
circos.track(track.index = get.current.track.index(), panel.fun = function(x, y) {
circos.text(CELL_META$xcenter, CELL_META$cell.ylim[2] + convert_y(2, "mm"),
paste0("this is group ", CELL_META$sector.index),
facing = "bending.inside", cex = 0.8,
adj = c(0.5, 0), niceFacing = TRUE)
}, bg.border = NA)
circos.clear()
Column names of the matrix are not directly supported by circos.heatmap()
,
but they can be easily added also by self-defining a panel.fun
function. In
followig example, I set larger space (10 degrees, users normally need to try
several values to get a best space) after the last sector (the fifth sector)
by gap.after
parameter in circos.par()
, later I draw the column names in
the last sector in panel.fun
.
Note the first column in the original matrix is put on the most outside circle of the circular heatmap.
circos.par(gap.after = c(2, 2, 2, 2, 10))
circos.heatmap(mat1, split = split, col = col_fun1, track.height = 0.4)
circos.track(track.index = get.current.track.index(), panel.fun = function(x, y) {
if(CELL_META$sector.numeric.index == 5) { # the last sector
cn = colnames(mat1)
n = length(cn)
circos.text(rep(CELL_META$cell.xlim[2], n) + convert_x(1, "mm"),
n - 1:n + 0.5, cn,
cex = 0.5, adj = c(0, 0.5), facing = "inside")
}
}, bg.border = NA)
circos.clear()
Next example adds rectangles and labels to show the two groups of columns in
the matrix. The code inside panel.fun
is simple. It basically draws
rectangles and texts. convert_x()
converts a unit on the x-direction to a
proper value measured in tbe circular coordinate system.
circos.par(gap.after = c(2, 2, 2, 2, 10))
circos.heatmap(mat1, split = split, col = col_fun1, track.height = 0.4)
circos.track(track.index = get.current.track.index(), panel.fun = function(x, y) {
if(CELL_META$sector.numeric.index == 5) { # the last sector
circos.rect(CELL_META$cell.xlim[2] + convert_x(1, "mm"), 0,
CELL_META$cell.xlim[2] + convert_x(5, "mm"), 5,
col = "orange", border = NA)
circos.text(CELL_META$cell.xlim[2] + convert_x(3, "mm"), 2.5,
"group 1", cex = 0.5, facing = "clockwise")
circos.rect(CELL_META$cell.xlim[2] + convert_x(1, "mm"), 5,
CELL_META$cell.xlim[2] + convert_x(5, "mm"), 10,
col = "pink", border = NA)
circos.text(CELL_META$cell.xlim[2] + convert_x(3, "mm"), 7.5,
"group 2", cex = 0.5, facing = "clockwise")
}
}, bg.border = NA)
circos.clear()
circlize does not generates legends, but the legends can be manually
generated by ComplexHeatmap::Legend()
function and added to the
circular plot.
Following is a simple example of adding a legend. In the next section, you can
find a more complex example of adding many legends.
circos.heatmap(mat1, split = split, col = col_fun1)
circos.clear()
library(ComplexHeatmap)
lgd = Legend(title = "mat1", col_fun = col_fun1)
grid.draw(lgd)
A complex example of circular heatmaps
In this section, I will demonstrate how to make complex circular heatmaps. The heatmaps in the normal layout are in the following figure and now I will change them with the circular layout.
The heatmaps visualize correlations between DNA methylation, gene expression and other genome-level information. You can go to this link to see how the original heatmaps were generated.
The original heatmaps were generated with random datasets. The code for generating them is available at https://gist.github.com/jokergoo/0ea5639ee25a7edae3871ed8252924a1. Here I just directly source the script from Gist.
source("https://gist.githubusercontent.com/jokergoo/0ea5639ee25a7edae3871ed8252924a1/raw/57ca9426c2ed0cebcffd79db27a024033e5b8d52/random_matrices.R")
Similar as the original heatmap, rows of all heatmaps are split into 5 groups
by applying k-means clustering on rows of the methylation matrix
(mat_meth
).
set.seed(123)
km = kmeans(mat_meth, centers = 5)$cluster
Now there are following matrices/vectors that need to be visualized as heatmaps:
mat_meth
: a matrix in which rows correspond to differetially methylated regions (DMRs). The value in the matrix is the mean methylation level in the DMR in every sample.mat_expr
: a matrix in which rows correspond to genes which are associated to the DMRs (i.e. the nearest gene to the DMR). The value in the matrix is the expression level for each gene in each sample. Expression is scaled for every gene across samples.direction
: direction of the methylation change (hyper meaning higher methylation in tumor samples, hypo means lower methylation in tumor samples).cor_pvalue
: p-value for the correlation test between methylation and expression of the associated gene. Values are -log10 transformed.gene_type
: type of the genes (e.g., protein coding genes or lincRNAs).anno_gene
: annotation to the gene models (i.e., intergenic, intragenic or transcription start site (TSS)).dist
: distance from DMRs to TSS of the assiciated genes.anno_enhancer
: fraction of each DMR that overlaps enhancers.
Among these variables, mat_meth
, mat_expr
, cor_pvalue
, dist
and anno_enhancer
are numeric
and I set color mapping functions for them. For the others I set named color vectors.
In the following code, I specify split
in the first call of circos.heatmap()
which
is the methylation heatmap. The track heights are manually adjusted.
col_meth = colorRamp2(c(0, 0.5, 1), c("blue", "white", "red"))
circos.heatmap(mat_meth, split = km, col = col_meth, track.height = 0.12)
col_direction = c("hyper" = "red", "hypo" = "blue")
circos.heatmap(direction, col = col_direction, track.height = 0.01)
col_expr = colorRamp2(c(-2, 0, 2), c("green", "white", "red"))
circos.heatmap(mat_expr, col = col_expr, track.height = 0.12)
col_pvalue = colorRamp2(c(0, 2, 4), c("white", "white", "red"))
circos.heatmap(cor_pvalue, col = col_pvalue, track.height = 0.01)
library(RColorBrewer)
col_gene_type = structure(brewer.pal(length(unique(gene_type)), "Set3"), names = unique(gene_type))
circos.heatmap(gene_type, col = col_gene_type, track.height = 0.01)
col_anno_gene = structure(brewer.pal(length(unique(anno_gene)), "Set1"), names = unique(anno_gene))
circos.heatmap(anno_gene, col = col_anno_gene, track.height = 0.01)
col_dist = colorRamp2(c(0, 10000), c("black", "white"))
circos.heatmap(dist, col = col_dist, track.height = 0.01)
col_enhancer = colorRamp2(c(0, 1), c("white", "orange"))
circos.heatmap(anno_enhancer, col = col_enhancer, track.height = 0.03)
circos.clear()
The circular heatmaps look pretty! Since rows in the matrices are genomic regions (the differentially methylated regions), if we can establish connections between some of the regions, e.g. physical interactions in the 3D chromosome structure, the plot would be nicer and more useful.
In following code, I generate some random interactions between DMRs.
Each row in df_link
means there is an interaction from the ith DMR
to the jth DMR.
df_link = data.frame(
from_index = sample(nrow(mat_meth), 20),
to_index = sample(nrow(mat_meth), 20)
)
Finding the positions of these DMRs on the circular heatmaps is tricky.
Check the comments in the following code. Note here the subset
and
row_order
meta data are retrieved by get.cell.meta.data()
function
by explicitly specifying the sector index.
for(i in seq_len(nrow(df_link))) {
# Let's call the DMR with index df_link$from_index[i] as DMR1,
# and the other one with index df_link$to_index[i] as DMR2.
# The sector where DMR1 is in.
group1 = km[ df_link$from_index[i] ]
# The sector where DMR2 is in.
group2 = km[ df_link$to_index[i] ]
# The subset of DMRs (row indices from mat_meth) in sector `group1`.
subset1 = get.cell.meta.data("subset", sector.index = group1)
# The row ordering in sector `group1`.
row_order1 = get.cell.meta.data("row_order", sector.index = group1)
# This is the position of DMR1 in the `group1` heatmap.
x1 = which(subset1[row_order1] == df_link$from_index[i])
# The subset of DMRs (row indices from mat_meth) in sector `group2`.
subset2 = get.cell.meta.data("subset", sector.index = group2)
# The row ordering in sector `group2`.
row_order2 = get.cell.meta.data("row_order", sector.index = group2)
# This is the position of DMR2 in the `group2` heatmap.
x2 = which(subset2[row_order2] == df_link$to_index[i])
# We take the middle point and draw a link between DMR1 and DMR2
circos.link(group1, x1 - 0.5, group2, x2 - 0.5, col = rand_color(1))
}
To make things easier, I implemented a function circos.heatmap.link()
that basically
wraps the code above. Now drawing links between matrix rows is simpler:
for(i in seq_len(nrow(df_link))) {
circos.heatmap.link(df_link$from_index[i],
df_link$to_index[i],
col = rand_color(1))
}
After adding the links, the plots look nicer!
Legends are important for understanding heatmaps. Unfortunately, circlize
does not naturally support legends, however, the circlize plots can be
combined with the legends generated from
ComplexHeatmap::Legend()
.
Following the instructions from that link, we need a function that draws the
circlize plot and a Legends
object (which is a grid::grob
object).
The function that draws the circular plot is simply a wrapper of the previous code without any modification.
circlize_plot = function() {
circos.heatmap(mat_meth, split = km, col = col_meth, track.height = 0.12)
circos.heatmap(direction, col = col_direction, track.height = 0.01)
circos.heatmap(mat_expr, col = col_expr, track.height = 0.12)
circos.heatmap(cor_pvalue, col = col_pvalue, track.height = 0.01)
circos.heatmap(gene_type, col = col_gene_type, track.height = 0.01)
circos.heatmap(anno_gene, col = col_anno_gene, track.height = 0.01)
circos.heatmap(dist, col = col_dist, track.height = 0.01)
circos.heatmap(anno_enhancer, col = col_enhancer, track.height = 0.03)
for(i in seq_len(nrow(df_link))) {
circos.heatmap.link(df_link$from_index[i],
df_link$to_index[i],
col = rand_color(1))
}
circos.clear()
}
The legends can be generated from the color mapping functions and color vectors.
The ComplexHeatmap::Legend()
function is very flexible that you can customize
the labels on the legends (see how lgd_pvalue
, lgd_dist
and lgd_enhancer
are
defined).
lgd_meth = Legend(title = "Methylation", col_fun = col_meth)
lgd_direction = Legend(title = "Direction", at = names(col_direction),
legend_gp = gpar(fill = col_direction))
lgd_expr = Legend(title = "Expression", col_fun = col_expr)
lgd_pvalue = Legend(title = "P-value", col_fun = col_pvalue, at = c(0, 2, 4),
labels = c(1, 0.01, 0.0001))
lgd_gene_type = Legend(title = "Gene type", at = names(col_gene_type),
legend_gp = gpar(fill = col_gene_type))
lgd_anno_gene = Legend(title = "Gene anno", at = names(col_anno_gene),
legend_gp = gpar(fill = col_anno_gene))
lgd_dist = Legend(title = "Dist to TSS", col_fun = col_dist,
at = c(0, 5000, 10000), labels = c("0kb", "5kb", "10kb"))
lgd_enhancer = Legend(title = "Enhancer overlap", col_fun = col_enhancer,
at = c(0, 0.25, 0.5, 0.75, 1), labels = c("0%", "25%", "50%", "75%", "100%"))
Now we use the gridBase to combine both base graphics (circlize is implemented with the base graphics) and grid graphics (ComplexHeatmap is implemented with the grid graphics). You can just use the following code as a template for your plot if you want to try.
And, BINGO! Wie schön!!
library(gridBase)
plot.new()
circle_size = unit(1, "snpc") # snpc unit gives you a square region
pushViewport(viewport(x = 0, y = 0.5, width = circle_size, height = circle_size,
just = c("left", "center")))
par(omi = gridOMI(), new = TRUE)
circlize_plot()
upViewport()
h = dev.size()[2]
lgd_list = packLegend(lgd_meth, lgd_direction, lgd_expr, lgd_pvalue, lgd_gene_type,
lgd_anno_gene, lgd_dist, lgd_enhancer, max_height = unit(0.9*h, "inch"))
draw(lgd_list, x = circle_size, just = "left")
Session info
sessionInfo()
## R version 4.2.0 (2022-04-22)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur/Monterey 10.16
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib
##
## locale:
## [1] C/UTF-8/C/C/C/C
##
## attached base packages:
## [1] grid stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] gridBase_0.4-7 RColorBrewer_1.1-3 dendextend_1.16.0
## [4] dendsort_0.3.4 circlize_0.4.16 ComplexHeatmap_2.13.2
## [7] knitr_1.39 colorout_1.2-2
##
## loaded via a namespace (and not attached):
## [1] Rcpp_1.0.9 png_0.1-7 assertthat_0.2.1
## [4] digest_0.6.29 foreach_1.5.2 utf8_1.2.2
## [7] R6_2.5.1 stats4_4.2.0 evaluate_0.15
## [10] ggplot2_3.3.6 highr_0.9 blogdown_1.10
## [13] pillar_1.8.0 GlobalOptions_0.1.2 rlang_1.0.4
## [16] jquerylib_0.1.4 magick_2.7.3 S4Vectors_0.34.0
## [19] GetoptLong_1.1.0 rmarkdown_2.14 stringr_1.4.0
## [22] munsell_0.5.0 compiler_4.2.0 xfun_0.31
## [25] pkgconfig_2.0.3 BiocGenerics_0.42.0 shape_1.4.6
## [28] htmltools_0.5.3 tidyselect_1.1.2 gridExtra_2.3
## [31] tibble_3.1.8 bookdown_0.27 IRanges_2.30.0
## [34] codetools_0.2-18 matrixStats_0.62.0 viridisLite_0.4.0
## [37] fansi_1.0.3 crayon_1.5.1 dplyr_1.0.9
## [40] jsonlite_1.8.0 gtable_0.3.0 lifecycle_1.0.1
## [43] DBI_1.1.3 magrittr_2.0.3 scales_1.2.0
## [46] cli_3.3.0 stringi_1.7.8 cachem_1.0.6
## [49] viridis_0.6.2 doParallel_1.0.17 bslib_0.4.0
## [52] vctrs_0.4.1 generics_0.1.3 rjson_0.2.21
## [55] iterators_1.0.14 tools_4.2.0 Cairo_1.6-0
## [58] glue_1.6.2 purrr_0.3.4 parallel_4.2.0
## [61] fastmap_1.1.0 yaml_2.3.5 clue_0.3-61
## [64] colorspace_2.0-3 cluster_2.1.3 sass_0.4.2