复杂图像
我们可以使用一些简单的图形对象,将它们组合起来,绘制成各种复杂的图形
1. 圆形柱状图
我们介绍过了各种柱状图/条形图的绘制,如堆积型、并列型,还有径向柱状图,现在我们再介绍一种圆形柱状图的绘制。
圆形柱状图就是将圆形布局中,每一条轨迹当做一个柱子。例如,我们有 9
个样本,要绘制每个样本的肿瘤纯度信息
首先,构造数据
category <- paste0("sample", "_", 1:9)
percent <- sort(sample(40:80, 9))
color <- rev(rainbow(length(percent)))
初始化,以 12
点钟方向作为起始,同时只设置一个扇形,即整个圆就是一个 sector
circos.par("start.degree" = 90, cell.padding = c(0, 0, 0, 0))
circos.initialize("a", xlim = c(0, 100))
添加图形
circos.track(
ylim = c(0.5, length(percent)+0.5), track.height = 0.8,
bg.border = NA,
panel.fun = function(x, y) {
xlim = CELL_META$xlim
# 添加圆形中心线
circos.segments(rep(xlim[1], 9), 1:9,
rep(xlim[2], 9), 1:9,
col = "#CCCCCC")
# 添加 9 个圆形矩形
circos.rect(rep(0, 9),
1:9 - 0.45,
percent,
1:9 + 0.45,
col = color,
border = "white")
# 添加文本信息
circos.text(
rep(xlim[1], 9),
1:9,
paste(category, " - ", percent, "%"),
facing = "downward",
adj = c(1.05, 0.5),
cex = 0.8
)
# 添加轴信息
breaks = seq(0, 85, by = 5)
circos.axis(
h = "top",
major.at = breaks,
labels = paste0(breaks, "%"),
labels.cex = 0.6
)
})
2. 直方图
可以使用 circos.trackHist()
函数在所有单元格中绘制直方图,如果设置 draw.density = TRUE
则会绘制数据分布的密度曲线。
默认情况下,每个单元格的柱子的数量会根据数据自动确定,通过固定 bin.size
的值,可以让所有的单元格绘制相同数量的柱子,有利于单元格之间的分布比较。
例如
x <- rnorm(1600)
sectors <- sample(letters[1:16], 1600, replace = TRUE)
circos.initialize(sectors, x = x)
circos.trackHist(
sectors, x = x, col = "#a6cee3",
border = "#1f78b4"
)
circos.trackHist(
sectors, x = x, bin.size = 0.1,
col = "#b2df8a", border = "#33a02c"
)
circos.trackHist(
sectors, x = x, draw.density = TRUE,
col = "#fdbf6f", border = "#ff7f00"
)
circos.clear()
3. 系统发育树
圆形树状图可以有多方面的应用,比如系统发育树的展示。
R
中有多种树结构类,例如 hclust
, dendrogram
和 phylo
,它们之间可以进行相互转换,所以我们只使用 dendrogram
类来说明
首先,导入 ape
包提供的鸟类物种信息,并对数据进行层次聚类
library(ape)
data(bird.orders)
hc <- as.hclust(bird.orders)
然后,使用 cutree()
将数进行切割,划分出 6
个物种
# 获取鸟类名称
labels <- hc$labels
# 划分为 6 个物种
ct <- cutree(hc, 6)
# 物种的数量
n <- length(labels)
# 转换为 dendrogram 结构
dend <- as.dendrogram(hc)
因为树形图在内侧,所以要先绘制物种的标签
circos.par(cell.padding = c(0, 0, 0, 0))
# 只需要一个扇形
circos.initialize("a", xlim = c(0, n))
circos.track(
ylim = c(0, 1), bg.border = NA, track.height = 0.3,
panel.fun = function(x, y) {
for (i in seq_len(n)) {
circos.text(
i - 0.5, 0, labels[i], adj = c(0, 0.5),
facing = "clockwise", niceFacing = TRUE,
col = ct[labels[i]], cex = 0.5
)
}
})
最后使用 circos.dendrogram()
函数绘制树形图
library(dendextend)
dend <- color_branches(dend, k = 6, col = 1:6)
dend_height <- attr(dend, "height")
circos.track(
ylim = c(0, dend_height), bg.border = NA,
track.height = 0.4,
panel.fun = function(x, y) {
circos.dendrogram(dend)
}
)
circos.clear()
可以使用 dendextend
包对树形图的属性进行设置,树形图默认是朝外的,可以设置 facing = "inside"
,使其朝向内部
circos.dendrogram(dend, facing = "inside")
注意,要先绘制树形图后绘制标签
4. 手动绘制圆形热图
既然我们可以绘制圆形树状图,那么很容易想到,在最外层使用 circos.rect()
函数添加一圈热图,就变成了圆形热图。
我们要绘制两个独立的热图,首先,构造数据
mat <- matrix(rnorm(100*10), nrow = 100, ncol = 10)
col_fun <- colorRamp2(c(-2, 0, 2), c("#fc8d59", "#ffffbf", "#99d594"))
# 设置两个扇形
sectors <- rep(letters[1:2], times = c(30, 70))
mat_list <- list(
a = mat[sectors == "a", ],
b = mat[sectors == "b", ]
)
# 将聚类结果转换为 dendrogram 类
dend_list <- list(
a = as.dendrogram(hclust(dist(mat_list[["a"]]))),
b = as.dendrogram(hclust(dist(mat_list[["b"]])))
)
我们需要从外到内依次绘制,先绘制圆形热图
circos.par(cell.padding = c(0, 0, 0, 0), gap.degree = 5)
circos.initialize(sectors, xlim = cbind(c(0, 0), table(sectors)))
circos.track(
ylim = c(0, 10), bg.border = NA,
panel.fun = function(x, y) {
sector.index = CELL_META$sector.index
m = mat_list[[sector.index]]
dend = dend_list[[sector.index]]
m2 = m[order.dendrogram(dend),]
col_mat = col_fun(m2)
nr = nrow(m2)
nc = ncol(m2)
for (i in 1:nc) {
circos.rect(
1:nr - 1, rep(nc - i, nr), 1:nr,
rep(nc - i + 1, nr), border = col_mat[, i],
col = col_mat[, i])
}
})
因为我们需要绘制了两个树状图,要保证它们的高度一致,可以去两个当中更高的那个来设置 ylim
# 获取最大树高
max_height <- max(sapply(dend_list, function(x) attr(x, "height")))
circos.track(
ylim = c(0, max_height), bg.border = NA,
track.height = 0.3, panel.fun = function(x, y) {
sector.index = get.cell.meta.data("sector.index")
dend = dend_list[[sector.index]]
circos.dendrogram(dend, max_height = max_height)
}
)
circos.clear()
图例
circlize
为用户提供了完全自由的图形设计,但是缺少了对图例的控制。
例如,对于如下图形
col_fun <- colorRamp2(c(-2, 0, 2), c("green", "blue", "red"))
circlize_plot <- function() {
set.seed(123)
sectors = letters[1:10]
circos.initialize(sectors, xlim = c(0, 1))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
circos.points(runif(20), runif(20), cex = 0.5, pch = 16, col = 2)
circos.points(runif(20), runif(20), cex = 0.5, pch = 16, col = 3)
})
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
circos.lines(sort(runif(20)), runif(20), col = 3)
circos.lines(sort(runif(20)), runif(20), col = 7)
})
for(i in 1:10) {
circos.link(sample(sectors, 1), sort(runif(10))[1:2],
sample(sectors, 1), sort(runif(10))[1:2],
col = add_transparency(col_fun(rnorm(1))))
}
circos.clear()
}
circlize_plot()
现在要为这三个轨迹添加图例,我们可以使用 ComplexHeatmap
的 Legend()
函数来自定义图例
library(ComplexHeatmap)
# 点
lgd_points <- Legend(
at = c("label1", "label2"), type = "points",
legend_gp = gpar(col = 2:3), title_position = "topleft",
title = "Track1"
)
# 线
lgd_lines <- Legend(
at = c("label3", "label4"), type = "lines",
legend_gp = gpar(col = 4:5, lwd = 2),
title_position = "topleft", title = "Track2"
)
# 颜色条
lgd_links <- Legend(
at = c(-2, -1, 0, 1, 2), col_fun = col_fun,
title_position = "topleft", title = "Links"
)
要将这三个图例合并在一起,可以使用 packLegend()
函数,默认按竖直方向添加
lgd_list_vertical <- packLegend(lgd_points, lgd_lines, lgd_links)
可以使用 draw()
函数来绘制图例
draw(
lgd_list_vertical, x = unit(5, "mm"),
y = unit(4, "mm"), just = c("left", "bottom")
)
虽然 circlize
是基于基础图形系统,而 ComplexHeatmap
是基于 grid
绘图系统,但是两种系统可以混合使用。事实上,它们是绘制在同一个图形设备上的不同图层
但是这种方法很容易出现图例与圆形布局重叠的情况,更好的方式是将图例和圆形图分为两部分。
我们可以用 grid
包来对绘图区域进行重排,例如
grid.newpage()
circle_size = unit(1, "snpc")
pushViewport(
viewport(
x = 0, y = 0.5, width = circle_size,
height = circle_size, just = c("left", "center")
)
)
par(omi = c(0, 0, 0.5, 0.5), new = TRUE)
circlize_plot()
upViewport()
draw(lgd_list_vertical, x = circle_size, just = "left")
使用 par(new = TRUE)
防止基础图形绘制到新的图片上,并用 omi
参数来设置边距。
也可以设置水平排列的图例
lgd_points <- Legend(
at = c("label1", "label2"), type = "points",
legend_gp = gpar(col = 2:3), title_position = "topleft",
title = "Track1", nrow = 1
)
lgd_lines <- Legend(
at = c("label3", "label4"), type = "lines",
legend_gp = gpar(col = 4:5, lwd = 2), title_position = "topleft",
title = "Track2", nrow = 1
)
lgd_links <- Legend(
at = c(-2, -1, 0, 1, 2), col_fun = col_fun,
title_position = "topleft", title = "Links", direction = "horizontal"
)
lgd_list_horizontal <- packLegend(
lgd_points, lgd_lines, lgd_links,
direction = "horizontal"
)
plot.new()
pushViewport(viewport(
x = 0.5, y = 1, width = circle_size,
height = circle_size, just = c("center", "top"))
)
par(omi = c(0, 0, 0, 0), new = TRUE)
circlize_plot()
upViewport()
draw(lgd_list_horizontal, y = unit(1, "npc") - circle_size, just = "top")
这种方式也是要调整绘图区域的大小,来显示图例