
1. 先搞清楚这张圆形热图到底要解决什么展示问题看到“复现文献中的圆形热图”这个需求很多人的第一反应是去找代码。但更关键的一步是先理解文献作者为什么选择圆形热图而不是常规的矩形热图。这决定了你复现的起点和终点。圆形热图在学术图表里通常被称为“Circular Heatmap”或“Circular Visualization”它不只是把矩形热图弯成一个圈那么简单。它核心解决的是两类展示问题周期性或循环关系数据的可视化比如一天24小时的基因表达变化、一年12个月的物种丰度、细胞周期相关基因的表达模式。用圆形布局能直观地体现“周期结束即是开始”的循环特性。高维度、多层次关联数据的整合展示当你有多个数据矩阵并且它们之间存在层级或网络关系时比如基因-通路-表型圆形布局可以更紧凑地展示这些关系并通过扇区、轨道等元素进行分层。所以在动手敲代码之前我建议你先问自己两个问题第一我手头的数据是否具有上述的周期性或多层次关联特性第二文献中的那张图除了“圆”这个形状它的颜色、扇区划分、内外轨道、连线分别代表了什么数据维度把这两个问题搞清楚复现就成功了一半。对于R语言用户ComplexHeatmap包是绘制这类高级热图的绝对主力它功能强大但学习曲线也相对陡峭。它的核心优势在于将热图拆解成一个个可以自由组合的“模块”比如热图块、行/列注释、图形元素再通过%v%或运算符进行拼接最终实现几乎任何你能想到的布局。复现圆形热图本质上就是学习如何用ComplexHeatmap的这些模块去“搭建”一个圆形布局。2. 复现前的核心准备数据、包与环境复现不是照猫画虎你得先把自己的“原料”准备好。这里分三步走数据整理、包安装、理解核心函数。2.1 数据整理从文献到你的数据框文献里的图很美但你的数据必须整理成R能识别的格式。ComplexHeatmap最常用的输入是一个数值矩阵matrix行和列通常是你要比较的实体比如基因和样本。假设你要复现一张展示10个基因在5个时间点表达量的圆形热图你的数据就应该是一个10行基因、5列时间点的矩阵。数据可以从你的实验中获得或者从文献的补充材料里提取有时作者会提供原始数据。用read.table()或read.csv()读入后用as.matrix()转换。# 示例创建一个模拟的表达矩阵 set.seed(123) expr_matrix - matrix(rnorm(50, mean0, sd1), nrow10, ncol5) rownames(expr_matrix) - paste0(Gene, 1:10) colnames(expr_matrix) - paste0(Time, 1:5) print(expr_matrix)关键点矩阵的行名和列名很重要它们会默认显示在热图上。确保你的数据没有缺失值NA或者你已想好如何处理NA比如用均值填充。2.2 包安装与加载别在依赖上卡住ComplexHeatmap依赖于circlize包来实现圆形布局。所以你需要安装这两个包。我强烈建议使用RStudio并通过CRAN镜像安装这是最稳的方式。# 安装核心包 install.packages(ComplexHeatmap) install.packages(circlize) # 加载包 library(ComplexHeatmap) library(circlize)如果安装ComplexHeatmap时遇到问题比如提示依赖包版本不对可以先尝试更新你的R版本到较新的稳定版如4.2.x以上然后单独安装报错的依赖包。网络问题可以尝试更换CRAN镜像Tools - Global Options - Packages。2.3 理解两个核心函数Heatmap与circos.heatmap这是最容易混淆的地方。ComplexHeatmap包的主函数是Heatmap()它生成的是矩形热图。而要实现真正的圆形热图你需要使用的是circlize包中的circos.heatmap()函数它是在环形布局circos layout上绘制热图。简单来说Heatmap()功能全面用于创建矩形热图及其所有高级注释是学习这个包的起点。circos.heatmap()专门用于在圆形轨道上绘制热图数据是复现“圆形热图”的直接工具。它通常需要配合circlize的circos.initialize()和circos.track()等函数来搭建整个圆形画布。很多教程一上来就讲Heatmap()的复杂参数但对于圆形热图复现你应该先聚焦于circos.heatmap()。不过ComplexHeatmap包中对颜色映射、数据分割split等概念的理解对使用circos.heatmap()同样至关重要。3. 从零搭建一个基础圆形热图我们现在用模拟数据走一遍绘制基础圆形热图的完整流程。这个过程我会拆解得很细包括每个参数的作用和常见坑点。3.1 第一步初始化圆形画布与轨道在circlize的逻辑里画图前必须先“初始化”扇区。对于热图我们通常把矩阵的每一行或每一列定义为一个扇区。这里我们以行为扇区每个基因一个扇区。# 继续使用之前的 expr_matrix # 1. 初始化扇区这里按行基因初始化 circos.par(gap.degree 5, start.degree 90) # 设置扇区间隔和起始角度 circos.initialize(factors rownames(expr_matrix), xlim c(0, ncol(expr_matrix))) # 2. 创建轨道用于绘制热图 circos.track(ylim c(0, 1), bg.border NA, track.height 0.3) # 先创建一个空轨道 # 3. 在轨道上添加热图 # circos.heatmap 需要矩阵数据以及颜色映射函数 col_fun - colorRamp2(c(min(expr_matrix), 0, max(expr_matrix)), c(blue, white, red)) circos.heatmap(expr_matrix, col col_fun) # 4. 添加轨道标签基因名 circos.track(track.index 1, panel.fun function(x, y) { circos.text(CELL_META$xcenter, CELL_META$cell.ylim[2] mm_y(2), CELL_META$sector.index, facing clockwise, niceFacing TRUE, adj c(0, 0.5), cex 0.7) }, bg.border NA) # 5. 结束绘图关闭设备 circos.clear()代码拆解与避坑circos.par(gap.degree5)gap.degree控制扇区之间的间隙角度。数值越大间隙越宽。如果基因很多比如上百个这个值要设小比如1或2否则圆环会断裂。start.degree90让圆环从12点钟方向开始这是比较符合阅读习惯的。circos.initializefactors参数定义了扇区这里用了行名。xlim定义了每个扇区在x轴上的范围这里对应了样本数5。colorRamp2这是定义颜色映射的关键函数。它接受一个数值向量断点和一个颜色向量将数据线性映射到颜色。这里最容易出问题你的数据分布可能不是对称的如果直接用min和max可能导致中间色白色不对应0值。更好的做法是根据数据实际范围调整例如c(quantile(expr_matrix, 0.1), 0, quantile(expr_matrix, 0.9))。circos.tracktrack.height控制轨道高度。多个轨道可以叠加用于放置热图、柱状图、折线图等。circos.clear()非常重要每次用circlize画完一个圆必须调用此函数重置布局否则下一个图会叠加在上一个图上。运行这段代码你应该能得到一个最基础的、环状的、按基因分扇区的热图。但这可能离文献里的精美图形还有距离。3.2 第二步添加行列注释与分割文献中的热图往往色彩丰富旁边还有各种注释条。在圆形布局中我们可以通过添加额外的轨道来实现类似效果。假设我们想给样本列加上分组注释比如“处理组”和“对照组”并给基因行加上类型注释比如“通路A”、“通路B”。# 准备注释信息 sample_annotation - data.frame( Group rep(c(Treatment, Control), times c(3, 2)) # 前3个时间点为处理组后2个为对照组 ) rownames(sample_annotation) - colnames(expr_matrix) gene_annotation - data.frame( Type sample(c(Pathway_A, Pathway_B), size10, replaceTRUE) ) rownames(gene_annotation) - rownames(expr_matrix) # 定义注释颜色 group_colors - c(Treatment darkorange, Control steelblue) type_colors - c(Pathway_A forestgreen, Pathway_B purple) # 重新绘图 circos.par(gap.degree 5, start.degree 90) circos.initialize(factors rownames(expr_matrix), xlim c(0, ncol(expr_matrix))) # 轨道1基因类型注释最外圈 circos.track(ylim c(0, 1), track.height 0.05, bg.border NA, panel.fun function(x, y) { sector.index - CELL_META$sector.index type - gene_annotation[sector.index, Type] circos.rect(0, 0, ncol(expr_matrix), 1, col type_colors[type], border type_colors[type]) }) # 轨道2样本分组注释以“轨道点”形式在热图内侧 # 我们需要在每个扇区基因内为每个样本画一个小矩形 circos.track(ylim c(0, 1), track.height 0.05, bg.border NA, panel.fun function(x, y) { sector.index - CELL_META$sector.index for (j in 1:ncol(expr_matrix)) { group - sample_annotation[colnames(expr_matrix)[j], Group] circos.rect(j-1, 0, j, 1, col group_colors[group], border group_colors[group]) } }) # 轨道3主热图 circos.track(ylim c(0, 1), track.height 0.25, bg.border NA) col_fun - colorRamp2(c(-2, 0, 2), c(blue, white, red)) # 固定颜色范围便于比较 circos.heatmap(expr_matrix, col col_fun) # 轨道4基因名称标签 circos.track(track.index 4, panel.fun function(x, y) { circos.text(CELL_META$xcenter, CELL_META$cell.ylim[2] mm_y(1), CELL_META$sector.index, facing clockwise, niceFacing TRUE, adj c(0, 0.5), cex 0.6) }, bg.border NA, track.height 0.05) # 添加图例 legend(x -1.2, y 1.2, legend names(group_colors), fill group_colors, title Sample Group, cex0.8) legend(x 1.0, y 1.2, legend names(type_colors), fill type_colors, title Gene Type, cex0.8) circos.clear()经验之谈轨道顺序circos.track是依次从外向内添加轨道的。所以先画的注释会在外圈后画的热图在内圈。规划好你的视觉层次。注释绘制逻辑在panel.fun里CELL_META$sector.index获取当前扇区名即基因名CELL_META$xlim获取当前扇区x轴范围。我们需要在这个函数内部根据这些信息绘制当前扇区内的图形元素。这是circlize编程的核心思维有点绕但理解后非常灵活。颜色映射固定在正式作图中我建议像上面一样用固定的数值范围如c(-2,0,2)来定义colorRamp2而不是用数据的min和max。这能保证多张图之间颜色意义一致便于比较。4. 向文献级复现迈进高级定制与问题排查基础图形出来后就需要抠细节了。文献里的图之所以好看往往在于这些细节。4.1 实现行或列的数据分割Clustering/Splitting文献热图常按行或列的聚类树进行分割。在圆形热图中虽然不能直接画树状图但我们可以通过预先聚类并重新排序数据来实现分割效果。# 对行基因进行层次聚类 row_dist - dist(expr_matrix) row_hclust - hclust(row_dist) row_order - row_hclust$order expr_matrix_ordered - expr_matrix[row_order, ] # 根据聚类结果手动定义分割例如切成3类 row_clusters - cutree(row_hclust, k 3) # 在初始化时按聚类后的顺序和分类定义扇区 # 我们需要创建一个因子其水平顺序是聚类后的顺序且包含分类信息 row_factors - paste0(Cluster, row_clusters[row_order], _, rownames(expr_matrix_ordered)) circos.par(gap.degree c(rep(2, sum(row_clusters1)-1), 5, # 簇内间隙小簇间间隙大 rep(2, sum(row_clusters2)-1), 5, rep(2, sum(row_clusters3)-1))) circos.initialize(factors row_factors, xlim c(0, ncol(expr_matrix_ordered))) # ... 后续绘制轨道的代码扇区将按聚类顺序和分组排列 ...关键点circos.par(gap.degree)这里传入了一个向量用来为每个扇区间隙设置不同的角度从而实现“簇内紧簇间松”的视觉效果。这是手动模拟聚类分割的常用技巧。4.2 添加连线Links展示关联圆形图强大的另一个功能是展示扇区之间的关联比如基因之间的共表达关系。这需要用到circos.link()函数。假设我们想强调Gene1和Gene5在所有样本中表达模式高度相关。# 在画完所有轨道后添加连线 # 假设 Gene1 和 Gene5 在新的排序矩阵中位于 sector A 和 sector B # 我们需要知道它们所在的扇区名和x轴位置 sector_gene1 - row_factors[which(rownames(expr_matrix_ordered) Gene1)] sector_gene5 - row_factors[which(rownames(expr_matrix_ordered) Gene5)] # 在扇区中间位置连线 circos.link(sector.index1 sector_gene1, point1 c(2.5, 2.5), # point1 定义连线在扇区1的起始和结束x坐标 sector.index2 sector_gene5, point2 c(2.5, 2.5), col grey50, lwd 2, lty 2)circos.link的参数需要精确计算尤其是point1和point2它们指定了连线在各自扇区x轴上的起始和结束位置。对于连接整个扇区通常取其中点。4.3 输出与美化分辨率、字体与格式用于发表的图对分辨率、字体和矢量格式有要求。# 保存为高清PDF矢量图适合投稿 pdf(My_Circular_Heatmap.pdf, width 10, height 10) # 在这里执行你所有的绘图代码 dev.off() # 保存为高清PNG位图用于PPT等 png(My_Circular_Heatmap.png, width 3000, height 3000, res 300) # 在这里执行你所有的绘图代码 dev.off()美化建议字体在circos.text()等函数中使用family参数指定字体如Arial、Helvetica。确保系统中已安装该字体。颜色主题不要默认使用红绿配色红绿色盲读者无法分辨。可以使用viridis、RColorBrewer包的颜色方案。例如col_fun - colorRamp2(breaks, viridis::viridis(3))。图例使用ComplexHeatmap包的Legend()和draw()函数可以创建更精美、布局更灵活的图例而不是用基础的legend()。4.4 常见问题排查清单绘图过程中遇到问题按这个顺序查图形不显示或错乱检查是否最后调用了circos.clear()没调用会导致后续图形叠加。检查circos.initialize中的factors是否与数据行名/列名完全匹配因子水平顺序决定了绘图顺序。检查xlim的范围是否覆盖了你的数据索引通常从0到列数颜色映射不对劲检查colorRamp2的断点breaks是否合理用summary(expr_matrix)或hist(expr_matrix)查看数据分布。检查输入expr_matrix是否是数值矩阵字符型矩阵会导致错误。轨道重叠或顺序不对回忆circos.track的调用顺序先调用的画在外圈。检查每个circos.track的track.height参数它们加起来不应超过1或你设定的总范围。比例失调会导致重叠。文字标签重叠或显示不全扇区太多基因太多时标签必然拥挤。可以减小cex字体大小。只显示部分重要标签通过条件判断在panel.fun中选择性绘制。增大画布尺寸pdf(width, height)。考虑是否真的需要显示所有行名有时用图例代替更清晰。性能问题数据量大时绘图慢圆形热图本身计算量较大。如果行数超过1000请慎重考虑是否必须用圆形。矩形热图Heatmap()效率更高。可以先在数据子集如前100行上调试代码和样式成功后再用全数据绘制最终图。复现文献图表尤其是ComplexHeatmap制作的复杂图形本质上是一个“逆向工程”加“创造性实现”的过程。最有效的路径不是寻找一模一样的代码而是理解原图的数据逻辑和视觉元素然后运用circlize和ComplexHeatmap的工具箱像搭积木一样把它构建出来。从最基础的单轨道热图开始逐步添加注释、调整颜色、设置分割、美化标签每一步都确保理解参数的意义这样即使下次遇到不同的图你也能举一反三快速实现。