常见于单细胞状态、CNV 信号、克隆结构或模块相似性分析。核心不是单纯画热图而是把矩阵信号、模块分块、功能标签和 marker gene 注释整合到同一张图里让读者一眼看到哪些细胞程序之间更接近。图片来源项目内容文章Single-cell omics analysis reveals tumor microenvironment rewiring after arsenic trioxide therapy in acute promyelocytic leukemia期刊/年份Science Bulletin2025图号原文截图面板 iDOI/链接https://doi.org/10.1016/j.scib.2025.09.014这张图来自一篇急性早幼粒细胞白血病治疗前后肿瘤微环境单细胞组学研究。截图中展示的是一种模块相似性热图左侧是 MP1-MP10 不同模块主体是模块内/模块间相似性矩阵右侧补充每个模块对应的代表性基因。图片解读这张图可以拆成四个视觉层次主体热图每个小格表示两个细胞/模块单元之间的相似性颜色越深表示相似性越高。对角线分块MP1-MP10 沿对角线排列每个黑色边框对应一个模块。左侧模块条用不同颜色标记每个 MP 模块便于快速定位。右侧 marker gene列出每个模块对应的代表性基因增强图的生物学解释性。这种图适合展示 CNV 后处理结果、细胞状态相似性、模块识别结果、Jaccard index、共现关系矩阵等数据。输入数据这里建议准备 3 个输入文件。1. 相似性矩阵similarity_matrix.csv第一列是细胞/窗口/模块单元 ID后面每一列对应一个相同顺序的单元。列名含义cell行 IDcell_001至cell_xxx与每个单元之间的相似性数值2. 模块信息module_info.csv列名含义module模块编号例如 MP1、MP2state模块对应的状态或功能名称module_color左侧模块条颜色size该模块包含的矩阵单元数量3. marker gene 注释marker_genes.csv列名含义module模块编号genes该模块代表性基因多个基因用逗号连接library(tidyverse)library(patchwork)sim_mat-read_csv(similarity_matrix.csv,show_col_typesFALSE)module_info-read_csv(module_info.csv,show_col_typesFALSE)marker_genes-read_csv(marker_genes.csv,show_col_typesFALSE)需要示例数据的后台添加小编领取调整好数据结构以下代码可以直接复制粘贴运行。第一步整理模块位置先根据每个模块的size计算模块在矩阵中的起点、终点和中心位置。后面画分块边框、模块标签、右侧基因注释都要用到这些坐标。cell_order-sim_mat$cell n-length(cell_order)module_ranges-module_info|mutate(startlag(cumsum(size),default0)1,endcumsum(size),mid(startend)/2,modulefactor(module,levelsmodule_info$module))第二步把矩阵转成长格式ggplot2画热图更适合使用长格式数据所以需要把宽矩阵转换为x-y-value结构。heat_df-sim_mat|pivot_longer(-cell,names_tocell2,values_tosimilarity)|mutate(xmatch(cell2,cell_order),ymatch(cell,cell_order))第三步准备分块、标签和基因注释这一部分主要用于还原原图中的几个关键元素左侧色条、虚线分隔、对角模块边框、模块名称以及右侧 gene list。cell_module-module_ranges|rowwise()|reframe(ystart:end,modulemodule,module_colormodule_color)|ungroup()block_df-module_ranges|transmute(module,state,size,xminstart-0.5,xmaxend0.5,yminstart-0.5,ymaxend0.5,xmidmid,ymidmid)sep_df-module_ranges|filter(row_number()n())|transmute(yend0.5)marker_df-marker_genes|left_join(module_ranges,bymodule)|mutate(ymid,ylineend0.5)state_label_df-block_df|mutate(label_xxmax1.1,label_yymidif_else(size12,0.9,size/10))第四步绘制主体热图主体图层用geom_raster()画矩阵。这里用黄-橙-红-紫的渐变色比较接近原图中低相似性到高相似性的过渡效果。heat_panel-ggplot()geom_raster(dataheat_df,aes(x,y,fillsimilarity),interpolateFALSE)geom_tile(datacell_module,aes(x-3.1,yy),fillcell_module$module_color,width2.4,height1)geom_rect(datablock_df,aes(xminxmin,xmaxxmax,yminymin,ymaxymax),fillNA,color#222222,linewidth0.45)geom_segment(datasep_df,aes(x-4.6,xendn0.5,yy,yendy),linetype22,linewidth0.22,color#222222,alpha0.75)geom_text(datastate_label_df,aes(xlabel_x,ylabel_y,labelstate),hjust0,size2.05,color#222222)geom_text(datamodule_ranges,aes(x-5.6,ymid,labelmodule),hjust1,size2.35,color#111111)scale_fill_gradientn(coloursc(#fffdf0,#fff1a8,#fdae61,#d7301f,#54278f,#1b004f),valuesscales::rescale(c(0,5,10,15,20,25)),limitsc(0,25),guidenone)coord_equal(xlimc(-6.2,n12),ylimc(0.5,n0.5),clipoff)theme_void()theme(plot.marginmargin(4,0,4,8))第五步添加右侧 marker gene 注释右侧 gene list 其实也是一个独立 panel。这样做比直接在主图上硬写文字更稳定也更容易控制对齐和留白。gene_panel-ggplot(marker_df)geom_segment(aes(x0,xend1,yyline,yendyline),linewidth0.34,color#333333)geom_text(aes(x0.03,yy,labelgenes),hjust0,size1.9,lineheight0.92,color#222222)coord_cartesian(xlimc(0,1),ylimc(0.5,n0.5),clipoff)theme_void()theme(plot.marginmargin(4,8,4,0))第六步添加色标并组合图形最后用patchwork把主体热图、右侧基因注释和底部色标拼在一起。legend_df-tibble(xseq(5,25,length.out220),y1,similarityx)legend_panel-ggplot(legend_df,aes(x,y,fillsimilarity))geom_tile(width0.12,height0.16)scale_fill_gradientn(coloursc(#fffdf0,#fff1a8,#fdae61,#d7301f,#54278f,#1b004f),valuesscales::rescale(c(0,5,10,15,20,25)),limitsc(0,25),guidenone)scale_x_continuous(breaksc(5,10,15,20,25),positiontop)annotate(text,x15,y0.74,labelSimilarity (Jaccard index),size1.65)coord_cartesian(xlimc(5,25),ylimc(0.68,1.18),clipoff)theme_void()theme(axis.text.xelement_text(size5,colorblack),plot.marginmargin(0,10,0,0))top_panel-heat_panelgene_panelplot_layout(widthsc(3.7,1.3))p-top_panel/(plot_spacer()legend_panelplot_layout(widthsc(4.3,1.15)))plot_layout(heightsc(1,0.065))ggsave(module_similarity_heatmap.png,p,width8.0,height4.85,dpi360,bgwhite)ggsave(module_similarity_heatmap.pdf,p,width8.0,height4.85,bgwhite)完整代码library(tidyverse)library(patchwork)sim_mat-read_csv(similarity_matrix.csv,show_col_typesFALSE)module_info-read_csv(module_info.csv,show_col_typesFALSE)marker_genes-read_csv(marker_genes.csv,show_col_typesFALSE)cell_order-sim_mat$cell n-length(cell_order)module_ranges-module_info|mutate(startlag(cumsum(size),default0)1,endcumsum(size),mid(startend)/2,modulefactor(module,levelsmodule_info$module))heat_df-sim_mat|pivot_longer(-cell,names_tocell2,values_tosimilarity)|mutate(xmatch(cell2,cell_order),ymatch(cell,cell_order))cell_module-module_ranges|rowwise()|reframe(ystart:end,modulemodule,module_colormodule_color)|ungroup()block_df-module_ranges|transmute(module,state,size,xminstart-0.5,xmaxend0.5,yminstart-0.5,ymaxend0.5,xmidmid,ymidmid)sep_df-module_ranges|filter(row_number()n())|transmute(yend0.5)marker_df-marker_genes|left_join(module_ranges,bymodule)|mutate(ymid,ylineend0.5)state_label_df-block_df|mutate(label_xxmax1.1,label_yymidif_else(size12,0.9,size/10))heat_panel-ggplot()geom_raster(dataheat_df,aes(x,y,fillsimilarity),interpolateFALSE)geom_tile(datacell_module,aes(x-3.1,yy),fillcell_module$module_color,width2.4,height1)geom_rect(datablock_df,aes(xminxmin,xmaxxmax,yminymin,ymaxymax),fillNA,color#222222,linewidth0.45)geom_segment(datasep_df,aes(x-4.6,xendn0.5,yy,yendy),linetype22,linewidth0.22,color#222222,alpha0.75)geom_text(datastate_label_df,aes(xlabel_x,ylabel_y,labelstate),hjust0,size2.05,color#222222)geom_text(datamodule_ranges,aes(x-5.6,ymid,labelmodule),hjust1,size2.35,color#111111)scale_fill_gradientn(coloursc(#fffdf0,#fff1a8,#fdae61,#d7301f,#54278f,#1b004f),valuesscales::rescale(c(0,5,10,15,20,25)),limitsc(0,25),guidenone)coord_equal(xlimc(-6.2,n12),ylimc(0.5,n0.5),clipoff)theme_void()theme(plot.marginmargin(4,0,4,8))gene_panel-ggplot(marker_df)geom_segment(aes(x0,xend1,yyline,yendyline),linewidth0.34,color#333333)geom_text(aes(x0.03,yy,labelgenes),hjust0,size1.9,lineheight0.92,color#222222)coord_cartesian(xlimc(0,1),ylimc(0.5,n0.5),clipoff)theme_void()theme(plot.marginmargin(4,8,4,0))legend_df-tibble(xseq(5,25,length.out220),y1,similarityx)legend_panel-ggplot(legend_df,aes(x,y,fillsimilarity))geom_tile(width0.12,height0.16)scale_fill_gradientn(coloursc(#fffdf0,#fff1a8,#fdae61,#d7301f,#54278f,#1b004f),valuesscales::rescale(c(0,5,10,15,20,25)),limitsc(0,25),guidenone)scale_x_continuous(breaksc(5,10,15,20,25),positiontop)annotate(text,x15,y0.74,labelSimilarity (Jaccard index),size1.65)coord_cartesian(xlimc(5,25),ylimc(0.68,1.18),clipoff)theme_void()theme(axis.text.xelement_text(size5,colorblack),plot.marginmargin(0,10,0,0))top_panel-heat_panelgene_panelplot_layout(widthsc(3.7,1.3))p-top_panel/(plot_spacer()legend_panelplot_layout(widthsc(4.3,1.15)))plot_layout(heightsc(1,0.065))ggsave(module_similarity_heatmap.png,p,width8.0,height4.85,dpi360,bgwhite)ggsave(module_similarity_heatmap.pdf,p,width8.0,height4.85,bgwhite)复现结果参考链接文章链接https://www.sciencedirect.com/science/article/pii/S2095927325009272DOIhttps://doi.org/10.1016/j.scib.2025.09.014