You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让ggplot2中的矩形图与直方图宽度精准对齐?

问题:如何让ggplot中的矩形图与上方柱状图宽度精准对齐

我正在制作一个ggplot可视化,需要让下方的矩形图(geom_rect)与上方的柱状图(geom_col)宽度完全对齐。当前的绘图效果、代码及数据集如下:

当前绘图效果

当前代码

sunburst <- ggplot() + 
  scale_x_continuous(name="x") +
  ylim(-0.003540187, 0.004720250) +
  geom_rect(data=stroma_comp, mapping=aes(xmin=lower_lim, xmax=upper_lim,
                                          ymin=ymin,ymax=ymax,
                                          fill = stroma_bins), color='black', alpha=1) +
  geom_col(data = plot_sample,
           mapping = aes(x = row_id, y = cd8_density_total, fill = '#D3D3D3'),
           color='dark gray',
           alpha=1,
           position = position_dodge(0)) + # columns on outer level
  annotate("text", x = 1, y = -0.003540187, label = paste(sample_name, "
", indication, "
", pheno), size = 5,
           color = "black") +
  theme_void() +
  scale_fill_manual(values = c("0-10%" = "#FF7100",
                               "10-20%" = "#eb8e00",
                               "20-30%" = "#d4a600",
                               "30-40%" = "#bbb908",
                               "40-50%" = "#a1ca3c",
                               "50-60%" = "#85d865",
                               "60-70%" = "#66e58e",
                               "70-80%" = "#41efb6",
                               "80-90%" = "#0df8dd",
                               "90-100%" = "#00ffff"), guide = "none") +
  scale_color_discrete(guide = "none") +
  theme(
    panel.background = element_rect(fill='transparent'),
    plot.background = element_rect(fill='transparent', color=NA),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    legend.background = element_rect(fill='transparent'),
    legend.box.background = element_rect(fill='transparent')
  )

数据集

# stroma_comp
structure(list(stroma_bins = structure(9:10, levels = c("0-10%", 
"10-20%", "20-30%", "30-40%", "40-50%", "50-60%", "60-70%", "70-80%", 
"80-90%", "90-100%"), class = "factor"), n = c(1.09, 6.09), upper_lim = c(1.09, 
7.18), lower_lim = c(0, 1.09), ymax = c(0, 0), ymin = c(-0.002360125, 
-0.002360125)), class = "data.frame", row.names = c(NA, -2L))

# plot_sample
structure(list(Image = c("MW055 IF_Scan1.qptiff - resolution #1", 
"MW055 IF_Scan1.qptiff - resolution #1", "MW055 IF_Scan1.qptiff - resolution #1", 
"MW055 IF_Scan1.qptiff - resolution #1", "MW055 IF_Scan1.qptiff - resolution #1", 
"MW055 IF_Scan1.qptiff - resolution #1", "MW055 IF_Scan1.qptiff - resolution #1"
), Name = c("Tile 6", "Tile 3", "Tile 5", "Tile 2", "Tile 7", 
"Tile 4", "Tile 1"), tumor_stroma_epi_nsclc_v2..Epithelium.area.µm.2 = c(98.9314, 
8.9938, 78.9453, 165.885, 43.9696, 82.9425, 55.9612), tumor_stroma_epi_nsclc_v2..Necrosis.area.µm.2 = c(18.9868, 
0, 1.9986, 116.919, 0, 0, 0), tumor_stroma_epi_nsclc_v2..Stroma.area.µm.2 = c(50773.8184, 
3895.3008, 29928.2617, 71677.3322, 58207.6645, 15441.3, 21588.0416
), tumor_stroma_epi_nsclc_v2..Tumor.area.µm.2 = c(7216, 56.9605, 
340.7639, 3572.5244, 1564.9156, 1187.1774, 1162.1947), Area.µm.2 = c(94309.7, 
11209.7, 46866.9, 102262.6, 88348.2, 17987.8, 57560.1), ID = c("MW055 IF_Scan1.qptiff - resolution #1-Tile 6", 
"MW055 IF_Scan1.qptiff - resolution #1-Tile 3", "MW055 IF_Scan1.qptiff - resolution #1-Tile 5", 
"MW055 IF_Scan1.qptiff - resolution #1-Tile 2", "MW055 IF_Scan1.qptiff - resolution #1-Tile 7", 
"MW055 IF_Scan1.qptiff - resolution #1-Tile 4", "MW055 IF_Scan1.qptiff - resolution #1-Tile 1"
), num_tumor_detections = c(52, 0, 0, 29, 11, 6, 12), tumor_cd45 = c(13, 
0, 0, 16, 8, 6, 8), tumor_cd8 = c(4, 0, 0, 0, 0, 0, 1), tumor_epi = c(35, 
0, 0, 12, 3, 0, 3), tumor_stromal = c(0, 0, 0, 1, 0, 0, 0), num_stroma_detections = c(290, 
16, 172, 350, 298, 105, 117), stroma_cd45 = c(163, 14, 117, 168, 
184, 72, 80), stroma_cd8 = c(11, 0, 5, 16, 13, 4, 5), stroma_epi = c(10, 
1, 3, 19, 0, 3, 2), stroma_stromal = c(106, 1, 47, 147, 101, 
26, 30), num_epi_detections = c(0, 0, 0, 0, 0, 0, 0), epi_cd45 = c(0, 
0, 0, 0, 0, 0, 0), epi_cd8 = c(0, 0, 0, 0, 0, 0, 0), epi_epi = c(0, 
0, 0, 0, 0, 0, 0), epi_stromal = c(0, 0, 0, 0, 0, 0, 0), num_necro_detections = c(0, 
0, 0, 0, 0, 0, 0), necro_cd45 = c(0, 0, 0, 0, 0, 0, 0), necro_cd8 = c(0, 
0, 0, 0, 0, 0, 0), necro_epi = c(0, 0, 0, 0, 0, 0, 0), necro_stromal = c(0, 
0, 0, 0, 0, 0, 0), total_cells = c(342, 16, 172, 379, 309, 111, 
129), total_cd8 = c(15, 0, 5, 16, 13, 4, 6), total_epithelial = c(45, 
1, 3, 31, 3, 3, 5), tissue_area = c(58107.7366, 3961.2551, 30349.9695, 
75532.6606, 59816.5497, 16711.4199, 22806.1975), cd8_percent = c(0.043859649122807, 
0, 0.0290697674418605, 0.0422163588390501, 0.0420711974110032, 
0.036036036036036, 0.0465116279069767), cd8_density_stroma = c(0.000216647089910417, 
0, 0.000167066168096225, 0.000223222593655627, 0.000223338285630752, 
0.000259045546683246, 0.000231609707478051), cd8_density_tumor = c(0.000554323725055432, 
0, 0, 0, 0, 0, 0.000860441025931369), cd8_density_total = c(0.000258141185282374, 
0, 0.000164744811358048, 0.000211828894585503, 0.000217331157768199, 
0.000239357279269848, 0.000263086382550182), stroma_percentage = c(0.873787577539202, 
0.983350150814574, 0.986105165608157, 0.948958127922744, 0.973103008982145, 
0.923996889097377, 0.94658662848114), cd8_density_ratio = c(2.55864837734328, 
NaN, 0, 0, 0, 0, 3.71504733243062), stroma_bins = structure(c(9L, 
10L, 10L, 10L, 10L, 10L, 10L), levels = c("0-10%", "10-20%", 
"20-30%", "30-40%", "40-50%", "50-60%", "60-70%", "70-80%", "80-90%", 
"90-100%"), class = "factor"), cd8_percent_bins = structure(c(3L, 
1L, 2L, 3L, 3L, 2L, 3L), levels = c("0-2% CD8+ Cells", "2-4% CD8+ Cells", 
"4-6% CD8+ Cells", "6-8% CD8+ Cells", "8-10% CD8+ Cells", "10-15% CD8+ Cells", 
"15-20% CD8+ Cells", ">20% CD8+ Cells"), class = "factor"), row_id = 1:7), row.names = c(1584L, 
1581L, 1583L, 1580L, 1585L, 1582L, 1579L), class = "data.frame")

解决方案

要让geom_rect和geom_col宽度精准对齐,核心是统一两者的x轴范围,让矩形的总宽度与柱状图的总宽度完全匹配,具体步骤如下:

1. 计算柱状图的实际x轴边界

默认geom_col的柱子宽度为0.9,每个柱子中心对应row_id(1到7),因此:

  • 柱状图左边界:min(plot_sample$row_id) - 0.45 = 0.55
  • 柱状图右边界:max(plot_sample$row_id) + 0.45 = 7.45
  • 柱状图总宽度:7.45 - 0.55 = 6.9

2. 调整stroma_comp的x范围匹配柱状图

根据stroma_comp中n的比例,拆分柱状图的总宽度,计算每个矩形的新x边界:

library(dplyr)

col_left <- min(plot_sample$row_id) - 0.45
col_right <- max(plot_sample$row_id) + 0.45
total_width <- col_right - col_left

# 计算每个区间的宽度比例,生成新的xmin/xmax
stroma_comp <- stroma_comp %>%
  mutate(proportion = n / sum(n),
         new_xmin = col_left + cumsum(c(0, head(proportion, -1)))*total_width,
         new_xmax = col_left + cumsum(proportion)*total_width)

3. 修改绘图代码,使用新的x边界

将geom_rect中的lower_lim/upper_lim替换为计算出的new_xmin/new_xmax,同时统一x轴范围:

sunburst <- ggplot() + 
  scale_x_continuous(name="x", limits = c(col_left, col_right), expand = c(0,0)) +
  ylim(-0.003540187, 0.004720250) +
  geom_rect(data=stroma_comp, mapping=aes(xmin=new_xmin, xmax=new_xmax,
                                          ymin=ymin,ymax=ymax,
                                          fill = stroma_bins), color='black', alpha=1) +
  geom_col(data = plot_sample,
           mapping = aes(x = row_id, y = cd8_density_total, fill = '#D3D3D3'),
           color='dark gray',
           alpha=1,
           position = position_dodge(0), width = 0.9) + # 显式指定宽度,避免默认变化
  annotate("text", x = col_left + 0.5, y = -0.003540187, label = paste(sample_name, "\n", indication, "\n", pheno), size = 5,
           color = "black") +
  theme_void() +
  scale_fill_manual(values = c("0-10%" = "#FF7100",
                               "10-20%" = "#eb8e00",
                               "20
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 23:16:17