R 数据可视化 —— 聚类热图 ComplexHeatmap(三)热图列表

前言

我们继续介绍 ComplexHeatmap,今天要介绍的内容是热图列表。

ComplexHeatmap 包的一个重要的功能是,能够在水平或竖直方向连接多个热图和注释,以图形化的方式展示各信息之间的关联

通常水平方向的连接比较常用,我们介绍的重点也是水平连接,竖直的连接原理基本是一致的。

在进行水平连接时,热图和注释的行数要相同

例如,我们要绘制三个热图列表,数据如下

set.seed(123)
mat1 <- matrix(rnorm(80, 2), 8, 10)
mat1 <- rbind(mat1, matrix(rnorm(40, -2), 4, 10))
rownames(mat1) <- paste0("R", 1:12)
colnames(mat1) <- paste0("C", 1:10)

mat2 <- matrix(runif(60, max = 3, min = 1), 6, 10)
mat2 <- rbind(mat2, matrix(runif(60, max = 2, min = 0), 6, 10))
rownames(mat2) <- paste0("R", 1:12)
colnames(mat2) <- paste0("C", 1:10)

le <- sample(letters[1:3], 12, replace = TRUE)
names(le) <- paste0("R", 1:12)

使用 + 以水平方向连接热图

ht1 <- Heatmap(mat1, name = "rnorm")
ht2 <- Heatmap(mat2, name = "runif")
ht3 <- Heatmap(le, name = "letters")

ht1 + ht2 + ht3

默认情况下,第二幅热图的行树状图会删除,并且其行顺序与第一幅热图一样,同时第一、二幅图的行名也被删除

连接操作会返回 HeatmapList 对象,直接打印 ht_list 会调用 draw 函数

> ht_list <- ht1 + ht2 + ht3
> class(ht_list)
[1] "HeatmapList"
attr(,"package")
[1] "ComplexHeatmap"

可以连接任意数量的热图,或者往热图列表中添加热图

ht1 + ht_list
ht_list + ht1
ht_list + ht_list

还可以将 NULL 添加到热图列表,适用于在循环外部设置一个空热图列表

ht_list <- NULL 
for(s in sth) {
    ht_list = ht_list + Heatmap(...)
}

Heatmap(...) + NULL 将会返回一个热图列表对象

1. 标题

单个热图的标题,可以在 Heatmap 函数内置指定,而热图列表的标题,需要在 draw 中进行设置

例如

# 首先,我们分别为三个热图设置颜色
col_rnorm <- colorRamp2(c(-3, 0, 3), c("#f46d43", "#ffffbf", "#3288bd"))
col_runif <- colorRamp2(c(0, 1.5, 3), c("#de77ae", "#f7f7f7", "#7fbc41"))
col_letters <- c("a" = "#33a02c", "b" = "#fb9a99", "c" = "#e31a1c")

# 分别在 Heatmap 函数内设置单个热图标题
ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm,
  row_title = "Heatmap 1",
  column_title = "Heatmap 1")

ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif,
  row_title = "Heatmap 2",
  column_title = "Heatmap 2")

ht3 <- Heatmap(le, name = "letters", 
              col = col_letters)

ht_list <- ht1 + ht2 + ht3

# 设置热图列表的标题
draw(
  ht_list, 
  row_title = "Three heatmaps, row title", 
  row_title_gp = gpar(col = "red"),
  column_title = "Three heatmaps, column title", 
  column_title_gp = gpar(fontsize = 16)
)

我们可以看到,第二张热图的行名并未显示

2. 设置大小

我们在之前的文章中已经介绍过如何设置热图的大小,即 widthheight 控制单个热图主体的宽度和高度,heatmap_widthheatmap_height 控制所有热图(包括注释)的总宽度和高度

可以指定一个或多个热图的宽度

ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif, 
  width = unit(4, "cm")
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters, 
  width = unit(5, "mm")
  )
ht1 + ht2 + ht3

如果 width 设置为数值,将会转换为 null 单位

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  width = 6
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif, 
  width = 4
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters, 
  width = 1
  )
ht1 + ht2 + ht3

3. 设置间隔

ht_gap 参数用于控制两个热图之间的间隔,可以是一个单位值或向量单位值

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters
  )
ht_list <- ht1 + ht2 + ht3
draw(ht_list, 
     ht_gap = unit(1, "cm"))
draw(ht_list, 
     ht_gap = unit(c(3, 10), "mm"))

4. 自动调整热图主体

在热图列表中始终有一个主热图,用于控制全局的行顺序,其他所有的热图会根据主热图的配置自动进行调整,调整方式为:

  • 不对行进行聚类,按照主热图的行顺序排列
  • 删除行标题
  • 如果主热图进行了分割,也会进行分割
  • 主热图的高度就是所有热图的高度

默认第一张热图为主热图,例如

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm,
  row_km = 2
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters
  )
ht2 + ht1 + ht3

现在,ht2 为主热图,所以 ht1row_km 被忽略了

可以在 draw 函数中使用 main_heatmap 参数指定主热图,可以是热图名或数值索引

ht_list <- ht2 + ht1 + ht3
draw(ht_list, main_heatmap = "rnorm")
# draw(ht_list, main_heatmap = 2)

默认只在主热图的边上绘制其行树状图和行名,可以使用 row_dend_siderow_sub_title_side 参数来设置其放置位置,例如

ht_list <- ht2 + ht1 + ht3
draw(ht_list, main_heatmap = "rnorm", 
     row_dend_side = "right", 
     row_sub_title_side = "left")

如果主热图不聚类,其他热图还是不会聚类

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm,
  row_km = 2, cluster_rows = FALSE
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters
  )

ht1 + ht2 + ht3

如果要显示所有行名,可以设置 auto_adjust = FALSE

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm,
  row_km = 2
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters
  )

ht_list <- ht1 + ht2 + ht3
draw(ht_list, main_heatmap = "rnorm", 
     auto_adjust = FALSE)

5. 使用 draw 函数控制主热图

主热图的设置可以放在 Heatmap() 函数内部,但是在 draw() 函数内设置会更方便些,避免更改主热图时,需要重新设置参数,且 draw 的优先级更高

draw 函数中控制主热图行顺序的参数有:

  • cluster_rows
  • clustering_distance_rows
  • clustering_method_rows
  • row_dend_width
  • show_row_dend
  • row_dend_reorder
  • row_dend_gp
  • row_order

控制行切片的参数

  • row_gap
  • row_km
  • row_km_repeats
  • row_split

控制高度

  • height
  • heatmap_height

例如

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  row_km = 2, cluster_rows = FALSE)
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif)
ht3 <- Heatmap(
  le, name = "letters", col = col_letters)

ht_list <- ht1 + ht2 + ht3
draw(
  ht_list, row_km = 1, row_split = le, 
  cluster_rows = TRUE)

Heatmap 函数内的 row_km = 2, cluster_rows = FALSE 会被 draw 覆盖

6. 注释的调整

如果热图列表中某些热图带有注释,并且,不同热图的注释的高度是不同的。

热图注释及树状图的高度会自动进行调整,但是简单注释的大小通常不会调整,除非设置 anno_simple_size 参数,或者设置全局变量 ht_opt$anno_simple_size

例如

ht1 = Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  top_annotation = HeatmapAnnotation(
    foo1 = 1:10, 
    annotation_name_side = "left")
  )
ht2 = Heatmap(
  mat2, name = "runif", col = col_runif)
ht3 = Heatmap(
  le, name = "letters", col = col_letters)
ht1 + ht2 + ht3

第二个热图的树状图变高了

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  top_annotation = HeatmapAnnotation(
    foo1 = 1:10, bar1 = anno_points(1:10), 
    annotation_name_side = "left")
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif,
  top_annotation = HeatmapAnnotation(
    bar2 = anno_barplot(1:10)))
ht3 <- Heatmap(
  le, name = "letters", col = col_letters)
ht_list <- ht1 + ht2 + ht3
draw(ht_list, ht_gap = unit(c(6, 2), "mm"))

在这个例子中,两幅热图都有注释,由于简单注视的大小不变,所有调整了第二幅热图的复杂热图的高度,使两幅热图的注释高度一致

如果第一幅热图只有简单注释,则会调整树状图的高度

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  top_annotation = HeatmapAnnotation(
    foo1 = 1:10,
    annotation_name_side = "left")
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif,
  top_annotation = HeatmapAnnotation(
    bar2 = anno_barplot(1:10)))
ht3 <- Heatmap(
  le, name = "letters", col = col_letters)
ht_list <- ht1 + ht2 + ht3
draw(ht_list, ht_gap = unit(c(6, 2), "mm"))

如果两幅热图都只包含简单注释,但是注释的数量不同,则还是会调整树状图的高度

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  top_annotation = HeatmapAnnotation(
    foo1 = 1:10, 
    annotation_name_side = "left")
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif,
  top_annotation = HeatmapAnnotation(
    bar2 = cbind(
      b1 = 1:10, b2 = 11:20, b3 = 21:30))
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters)
ht_list <- ht1 + ht2 + ht3
draw(ht_list, ht_gap = unit(c(6, 2), "mm"))

如果你想让简单注释的大小也自动调整,可以在 HeatmapAnnotation() 函数中设置 simple_anno_size_adjust = TRUE

如果第一幅热图的注释在底部,第二幅热图没有注释,则第二幅热图的列名直接放置在热图主体下方

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  bottom_annotation = HeatmapAnnotation(
    foo1 = 1:10, bar1 = anno_points(1:10),
    annotation_name_side = "left")
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif
  )
ht3 <- Heatmap(
  le, name = "letters", col = col_letters)
ht_list <- ht1 + ht2 + ht3
draw(ht_list, ht_gap = unit(c(6, 2), "mm"))

7. 热图与注释串联

行注释可以添加到水平热图列表中,例如

ha1 <- rowAnnotation(
  foo = 1:12, bar = anno_barplot(
    1:12, width = unit(4, "cm"))
  )
ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  row_km = 2)
ht1 + ha1

上面的注释可以分割为两个独立的行注释

Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  row_km = 2) + 
  rowAnnotation(foo = 1:12) +
  rowAnnotation(
    bar = anno_barplot(1:12, width = unit(4, "cm"))
    )

输出的结果是一样的

可是,行名怎么没了?我要怎么显示行名呢?

有两种方法:

  1. 将注释放在 Heatmap 函数中
ha1 <- rowAnnotation(
  foo = 1:12, bar = anno_barplot(
    1:12, width = unit(4, "cm"))
  )

Heatmap(
  mat1, name = "rnorm", col = col_rnorm, 
  row_km = 2, right_annotation = ha1
  )
  1. 或者将行名作为文本注释添加
ht1 + ha1 + 
  rowAnnotation(rn = anno_text(
    rownames(mat1), just = "left",
    location = unit(0, "npc")))

通常,热图和行注释可以以任意的次序连接。例如,将注释添加到左边和中间

rowAnnotation(foo = 1:12) +
  Heatmap(
    mat1, name = "rnorm", 
    col = col_rnorm, row_km = 2) + 
  rowAnnotation(
    bar = anno_barplot(1:12, 
    width = unit(4, "cm"))) +
  Heatmap(mat2, name = "runif", 
          col = col_runif)

8. 注释之间的串联

可以只串联注释,而不需要热图参与

rowAnnotation(foo = 1:12) +
  rowAnnotation(
    bar = anno_barplot(
      1:12, width = unit(4, "cm"))
    )

如果只有一个注释,必须要添加一个 NULL

rowAnnotation(
  bar = anno_barplot(
    1:12, width = unit(4, "cm"))
  ) + NULL

事实上,注释列表本质上是一个热图列表

anno_list <- rowAnnotation(foo = 1:12) +
  rowAnnotation(
    bar = anno_barplot(
      1:12, width = unit(4, "cm"))
  )

> class(anno_list)
[1] "HeatmapList"
attr(,"package")
[1] "ComplexHeatmap"

所以,你可以使用 draw() 函数来绘制

draw(anno_list, 
     row_split = rep(c("A", "B"), each = 6))

9. 竖直连接

使用 %v% 操作,可以以竖直的方式连接热图和注释,其他设置基本上是类似的

下面,列举几个例子

mat1t <-t(mat1)
mat2t <- t(mat2)
ht1 <- Heatmap(
  mat1t, name = "rnorm", col = col_rnorm, 
  row_title = "rnorm")
ht2 <- Heatmap(
  mat2t, name = "runif", col = col_runif, 
  row_title = "runif")
ht3 <- Heatmap(
  rbind(letters = le), name = "letters", 
  col = col_letters)
ht_list <- ht1 %v% ht2 %v% ht3
draw(ht_list)

设置分块

draw(ht_list, column_km = 2)

注释放中间

ha <- HeatmapAnnotation(
  foo = anno_barplot(1:12, height = unit(2, "cm"))
  )
ht_list = ht1 %v% ha %v% ht2 %v% ht3
draw(ht_list, column_km = 2)

将热图分块

ht1 <- Heatmap(
  mat1t, name = "rnorm", col = col_rnorm, 
  row_km = 2)
ht2 <- Heatmap(
  mat2t, name = "runif", col = col_runif, 
  row_km = 2)
ht3 <- Heatmap(
  rbind(letters = le), name = "letters", 
  col = col_letters)

ha <- HeatmapAnnotation(
  foo = anno_barplot(1:12, height = unit(2, "cm"))
  )

ht_list <- ht1 %v% ha %v% ht2 %v% ht3
draw(ht_list, column_km = 2)

行注释大小的调整与列注释一样

ht1 <- Heatmap(
  mat1t, name = "rnorm", col = col_rnorm, 
  row_km = 2, left_annotation = rowAnnotation(
    foo1 = 1:10, bar1 = anno_barplot(1:10))
  )
ha <- HeatmapAnnotation(
  foo = anno_barplot(
    1:12, height = unit(2, "cm"), 
    axis_param = list(side = "right"))
  )
ht2 <- Heatmap(
  mat2t, name = "runif", col = col_runif, 
  row_km = 2, left_annotation = rowAnnotation(foo2 = 1:10))

ht3 <- Heatmap(
  rbind(letters = le), name = "letters", 
  col = col_letters)
ht_list = ht1 %v% ha %v% ht2 %v% ht3
draw(ht_list, column_km = 2)

10. 热图列表子集

Heatmap 对象类似,热图列表也可以进行切片操作。

对于水平热图列表,行索引对应所有热图和注释的行,列索引为相应的热图或注释名称。例如

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm,
  left_annotation = rowAnnotation(
    foo1 = 1:12, bar1 = anno_points(1:12))
  )
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif
  )
ha <- rowAnnotation(
  foo2 = anno_barplot(1:12), bar2 = 12:1
  )
ht_list <- ht1 + ht2 + ha

获取热图列表包含的热图和注释名称

> names(ht_list)
[1] "rnorm" "runif" "foo2"  "bar2" 

获取子集

ht_list[1:6, c("rnorm", "bar2")]

对于竖直热图列表也是类似的。

11. 获取次序和树状图

row_order()column_order() 可以获取热图列表的行列顺序。

记住:是对 draw 函数的返回对象进行操作

例如,对于如下热图列表

ht1 <- Heatmap(
  mat1, name = "rnorm", col = col_rnorm)
ht2 <- Heatmap(
  mat2, name = "runif", col = col_runif)
ht_list <- ht1 + ht2
ht_list <- draw(ht_list)

行顺序

> row_order(ht_list)
 [1]  3  6  4  5  2  1  7  8 10  9 11 12

列顺序

> column_order(ht_list)
$rnorm
 [1]  5  2  7  6 10  1  9  8  4  3

$runif
 [1]  4 10  2  5  7  6  1  3  8  9

如果未分块热图,返回的是 list

row_dend()column_dend() 可以获取行列树状图.

12. 全局参数

ht_opt() 函数用于控制全局参数,可以使用该函数为所有热图和注释设置参数值

获取所有全局参数

> ht_opt
 Option                  Value
 -----------------------:-------
 heatmap_row_names_gp    NULL 
 heatmap_column_names_gp NULL 
 heatmap_row_title_gp    NULL 
 heatmap_column_title_gp NULL 
 legend_title_gp         NULL 
 legend_title_position   NULL 
 legend_labels_gp        NULL 
 legend_grid_height      NULL 
 legend_grid_width       NULL 
 legend_border           NULL 
 heatmap_border          NULL 
 annotation_border       NULL 
 fast_hclust             FALSE
 show_parent_dend_line   TRUE 
 verbose                 FALSE
 show_vp                 FALSE
 simple_anno_size        5mm  
 DENDROGRAM_PADDING      0.5mm
 DIMNAME_PADDING         1mm  
 TITLE_PADDING           2.5mm
 COLUMN_ANNO_PADDING     1mm  
 ROW_ANNO_PADDING        1mm  

这些参数都是见名知意的,不需要再做说明了

获取这些参数值的方式,可以是

> ht_opt("heatmap_row_names_gp")
NULL
> ht_opt$heatmap_row_names_gp
NULL

设置参数值

ht_opt("heatmap_row_names_gp" = gpar(fontsize = 8))
ht_opt$heatmap_row_names_gp <- gpar(fontsize = 8)

实例

ht_opt(
  heatmap_column_names_gp = gpar(fontface = "italic"), 
  heatmap_column_title_gp = gpar(fontsize = 10),
  legend_border = "black",
  heatmap_border = TRUE,
  annotation_border = TRUE
)
ht1 <- Heatmap(
  mat1, name = "ht1", column_title = "Heatmap 1",
  top_annotation = HeatmapAnnotation(foo = 1:10)
  )
ht2 <- Heatmap(
  mat2, name = "ht2", column_title = "Heatmap 2",
  top_annotation = HeatmapAnnotation(bar = 1:10)
  )
ht1 + ht2

绘制完之后,应该重置参数值

ht_opt(RESET = TRUE)

13. 设置边距

draw 函数中的 padding 参数可以设置绘图块四周的边距,padding 参数接受长度为 4 的向量,分别代表图像的下、左、上、右的边距

例如,对于行名较长的热图,会出现显示不全的问题

m <- matrix(rnorm(100), 10)
rownames(m) <- paste0("R", 1:10)
rownames(m)[1] = "a long long long long long row name"

ht <- Heatmap(
  m, name = "mat", row_names_side = "left", 
  show_row_dend = FALSE
  )
  
draw(ht)

添加边距

draw(ht, padding = unit(c(2, 20, 2, 2), "mm"))
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容