R 数据可视化 —— gtable 介绍

前言

多次有人问我,如何使用 R 来绘制多个 Y 轴。那我就研究了下,稍微介绍一下学习所得。

当我们需要展示的数据,包含相同的 X 轴,但是具有两个不同的 Y 轴变量时,我们会选择在右侧添加一条轴,在 ggplot2 中,可以使用 sec_axis 来设置。

那如果 Y 轴变量有多个呢?我们该如何绘制?

一种简单的思路就是,在图像绘图区域的左右两侧添加两条轴,多余的轴可以添加到右侧,图像还是填充到绘制区域。

那如何实现这一功能呢?下面我们将介绍今天的主角 —— gtable,搭配 grid 包,可以轻松的为 ggplot 添加上多个 Y

gtable

1. 介绍

gtable 是基于 grid 包的布局引擎,可以用来抽象化地创建网格视图,每个网格内都可以放置不同的图形对象

gtable 可以很容易地将图形元素的对齐,组合成复杂的图形。同时,完美兼容 ggplot2 图形

首先,当然是安装和导入了,不必多说

# 从 CRAN 上安装
install.packages("gtable")

# 从 GitHub 上安装
# install.packages("remotes")
remotes::install_github("r-lib/gtable")

导入

library(gtable)

library(ggplot2)
library(grid)

2. 创建布局

创建布局的函数主要有如下几个:

  • gtable: 创建 grobgrid object) 的 table 布局
  • gtable_matrix: 矩阵布局
  • gtable_col: 单列
  • gtable_row: 单行
  • gtable_row_spacer/gtable_col_spacer:

2.1 gtable

table 布局可以将 grob 以表的排列形式放置。它支持跨行、跨列,还提供了一些工具来自动计算出正确的尺寸

gtable(widths = list(), heights = list(), respect = FALSE,
  name = "layout", rownames = NULL, colnames = NULL, vp = NULL)

其中前两个参数指定了表格布局,例如

> a <- gtable(unit(1:3, c("cm")), unit(5, "cm"))
> a
TableGrob (1 x 3) "layout": 0 grobs
> class(a)
[1] "gtable" "gTree"  "grob"   "gDesc"

创建了一个宽度分别为 1cm2cm3cm,长度为 5cm 的一行三列的表格布局,,返回了一个 gtable 对象

使用 gtable_show_layout 函数可以绘制布局结构

gtable_show_layout(a)

每个绘图对象(grob)都放置在自己的视图中,并占满整个视图,因此对齐方式在这里不会发挥作用

布局中包含三个基础组件,表格的规格(单元格高度和宽度)、布局(对每个 grob 来说就是它的位置,名称及其他属性)和全局参数

每个单元格内可以有 01 或多个 grob。每个 grob 必须至少属于一个单元格,但可以跨越多个单元格。

布局的细节被存储在一个数据框中,每行表示一个 grob,列包括:

  • t: grob 的顶部位置
  • r: grob 的右边位置
  • b: grob 的底部位置
  • l: grob 的左边位置
  • z: grob 的次序
  • clip: grob 的裁剪方式,可以是:"on""off""inherit"
  • name: 每个 grob 和视图的名称

不能直接修改这个数据框,需要使用 gtable_add_grob 等操作函数来修改

2.2 gtable_matrix

gtable_matrix 可以创建矩阵布局,允许为每个单元格设置不同的的高度和宽度

gtable_matrix(name, grobs, widths = NULL, heights = NULL, z = NULL,
  respect = FALSE, clip = "on", vp = NULL)

首先,创建图形对象

a <- rectGrob(gp = gpar(fill = "red"))
b <- circleGrob()
c <- linesGrob()

row <- matrix(list(a, b, c), nrow = 1)
col <- matrix(list(a, b, c), ncol = 1)
mat <- matrix(list(a, b, c, nullGrob()), nrow = 2)

设置布局,一行三列

> gtable_matrix("demo", row, unit(c(1, 2, 3), "null"), unit(1, "null"))
TableGrob (1 x 3) "demo": 3 grobs
  z     cells name                    grob
1 1 (1-1,1-1) demo     rect[GRID.rect.143]
2 2 (1-1,2-2) demo circle[GRID.circle.144]
3 3 (1-1,3-3) demo   lines[GRID.lines.145]

三行一列

> gtable_matrix("demo", col, unit(1, "null"), unit(c(1, 2, 3), "null"))
TableGrob (3 x 1) "demo": 3 grobs
  z     cells name                    grob
1 1 (1-1,1-1) demo     rect[GRID.rect.143]
2 2 (2-2,1-1) demo circle[GRID.circle.144]
3 3 (3-3,1-1) demo   lines[GRID.lines.145]

两行两列

> gtable_matrix("demo", mat, unit(c(1, 1), "null"), unit(c(1, 1), "null"))
TableGrob (2 x 2) "demo": 4 grobs
  z     cells name                    grob
1 1 (1-1,1-1) demo     rect[GRID.rect.143]
2 2 (2-2,1-1) demo circle[GRID.circle.144]
3 3 (1-1,2-2) demo   lines[GRID.lines.145]
4 4 (2-2,2-2) demo     null[GRID.null.146]

设置绘制顺序

> z <- matrix(c(3, 1, 2, 4), nrow = 2)
> gtable_matrix("demo", mat, unit(c(1, 1), "null"), unit(c(1, 1), "null"), z = z)
TableGrob (2 x 2) "demo": 4 grobs
  z     cells name                    grob
1 3 (1-1,1-1) demo     rect[GRID.rect.143]
2 1 (2-2,1-1) demo circle[GRID.circle.144]
3 2 (1-1,2-2) demo   lines[GRID.lines.145]
4 4 (2-2,2-2) demo     null[GRID.null.146]

2.3 gtable_col 和 gtable_row

相较于前两个布局,gtable_colgtable_row 就比较简单,只是把所有 grob 绘制在一列或一行

gtable_col(name, grobs, width = NULL, heights = NULL, z = NULL,
  vp = NULL)

gtable_row(name, grobs, height = NULL, widths = NULL, z = NULL,
  vp = NULL)

也是通过 widthheight 控制宽度和高度,例如,绘制成一列

> gt <- gtable_col("demo", list(a, b, c))
> gt
TableGrob (3 x 1) "demo": 3 grobs
  z     cells name                    grob
1 1 (1-1,1-1) demo     rect[GRID.rect.143]
2 2 (2-2,1-1) demo circle[GRID.circle.144]
3 3 (3-3,1-1) demo   lines[GRID.lines.145]
> plot(gt)
gtable_show_layout(gt)

绘制成一行

> gt <- gtable_row("demo", list(a, b, c))
> plot(gt)
gtable_show_layout(gt)

3. 布局操作

  • gtable_add_grob: 添加一个 grob,可以跨越多行或多列
  • gtable_add_cols: 在指定位置添加新列
  • gtable_add_rows: 在指定位置添加新行
  • gtable_add_padding: 在 table 边界添加填充
  • gtable_add_col_spacegtable_add_row_space: 添加行/列间距
  • gtable_trim: 删除空单元格
  • gtable_filter: 通过名称来筛选单元格

3.1 gtable_add_grob

gtable_add_grob 可以把一个 grob 添加到 table 布局中,但是不会影响 table 布局

gtable 中,添加的对象会全部覆盖整个单元格,所以,如果你要设置对齐方式,则需要为 grob 设置绝对大小

使用方式

gtable_add_grob(x, grobs, t, l, b = t, r = l, z = Inf, clip = "on",
  name = x$name)

来看下面这个例子,新建一个一行三列的 table 布局,每列的宽度分别为 123

a <- gtable(unit(1:3, c("cm")), unit(5, "cm"))
gtable_show_layout(a)

为第一列添加一个矩形

> rect <- rectGrob(gp = gpar(fill = "black"))
> a <- gtable_add_grob(a, rect, 1, 1)
> a
TableGrob (1 x 3) "layout": 1 grobs
  z     cells   name                grob
1 1 (1-1,1-1) layout rect[GRID.rect.236]
> plot(a)

可以使用 t 对布局进行转置

> dim(a)
[1] 1 3
> t(a)
TableGrob (3 x 1) "layout": 1 grobs
  z     cells   name                grob
1 1 (1-1,1-1) layout rect[GRID.rect.236]
> dim(t(a))
[1] 3 1
> plot(t(a))

布局索引

> b <- gtable(unit(c(2, 2, 2), "cm"), unit(c(2, 2, 2), "cm"))
> b <- gtable_add_grob(b, rect, 2, 2)
> b[1, ]
TableGrob (1 x 3) "layout": 0 grobs
> b[, 1]
TableGrob (3 x 1) "layout": 0 grobs
> b[2, 2]
TableGrob (1 x 1) "layout": 1 grobs
  z     cells   name                grob
1 1 (1-1,1-1) layout rect[GRID.rect.236]
> plot(b)

访问 gtable 对象的名称

> rownames(b) <- 1:3
> rownames(b)[2] <- 200
> colnames(b) <- letters[1:3]
> dimnames(b)
[[1]]
[1]   1 200   3

[[2]]
[1] "a" "b" "c"

3.2 gtable_add_cols

gtable_add_cols 函数可以为 gtable 对象插入新的列,并会调整相应的 grob 的位置。

如果在一个横跨多列的 grob 中间添加列,grob 将继续横跨所有列。如果列被添加到 grob 的左边或右边,grob 将不会跨越新的列。

gtable_add_cols(x, widths, pos = -1)

创建一个 33 列的 table 布局,并添加三个矩形对象

rect <- rectGrob(gp = gpar(fill = "#00000080"))
tab <- gtable(unit(rep(1, 3), "null"), unit(rep(1, 3), "null"))
# 矩形占据第一行
tab <- gtable_add_grob(tab, rect, t = 1, l = 1, r = 3)
# 矩形占据第一列
tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 1)
# 矩形占据第三列
tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 3)

plot(tab)

在中间添加一列

> tab2 <- gtable_add_cols(tab, unit(1, "null"), 1)
> dim(tab2)
[1] 3 4
> plot(tab2)

指定 pos0 在左边添加列,-1(默认)在右边添加列

tab3 <- gtable_add_cols(tab, unit(1, "null"))
tab3 <- gtable_add_cols(tab3, unit(1, "null"), 0)
> dim(tab3)
[1] 3 5
> plot(tab3)

gtable_add_rows 的左右是添加列,这里就不再赘述了

3.3 gtable_add_padding

gtable_add_padding 可以为 gtable 四周添加 padding,使用方式

gtable_add_padding(x, padding)

padding 为长度为 4 的向量,分别表示上、右、下、左的 padding,如果长度不足 4,则会循环使用

例如,对于这样一个只包含一个以矩形对象填充的单元格的布局

gt <- gtable(unit(1, "null"), unit(1, "null"))
gt <- gtable_add_grob(gt, rectGrob(gp = gpar(fill = "black")), 1, 1)

plot(gt)

可以使用 rbindcbind 来连接两个 gtable 对象,绘制的图形根本看不出有两个图形

plot(cbind(gt, gt))

gtable 添加 padding,四周的 padding 都是 1cm

pad <- gtable_add_padding(gt, unit(1, "cm"))
plot(pad)
plot(rbind(pad, pad))
plot(cbind(pad, pad))

3.4 gtable_add_col/row_space

为网格的单元格之间添加行距和列距

gtable_add_col_space(x, width)
gtable_add_row_space(x, height)

创建矩阵布局,每个单元格放置一个矩形对象

rect <- rectGrob()
rect_mat <- matrix(rep(list(rect), 9), nrow = 3)

gt <- gtable_matrix(
  "rects", rect_mat, widths = unit(rep(1, 3), "null"),
  heights = unit(rep(1, 3), "null")
)

plot(gt)

添加行列间距

# 行距 0.5cm
gt <- gtable_add_row_space(gt, unit(0.5, "cm"))
# 列距分别为 0.5cm、1cm
gt <- gtable_add_col_space(gt, unit(c(0.5, 1), "cm"))

plot(gt)

3.5 gtable_trim

gtable_trim 函数用于删除不包含 grob 的行或列,如果删除的行和列的高度或宽度为 0,则有可能会改变整个布局

rect <- rectGrob(gp = gpar(fill = "black"))
base <- gtable(unit(c(2, 2, 2), "cm"), unit(c(2, 2, 2), "cm"))

center <- gtable_add_grob(base, rect, 2, 2)
plot(center)
gtable_show_layout(center)

删除空行、空列

plot(gtable_trim(center))

最后只剩下一个单元格

gtable_show_layout(gtable_trim(center))

3.6 gtable_filter

通常来说,gtable 对象在索引时可以将其当做一个矩阵,gtable_filter 可以根据名称对 grob 进行筛选

gtable_filter(x, pattern, fixed = FALSE, trim = TRUE, invert = FALSE)
  • pattern: 可以是正则表达式,匹配 grob 名称
  • fixed: 如果为 TRUE,关闭正则匹配
  • trim: 如果为 TRUE,调用 gtable_trim
  • invert: 是否逆向匹配

创建 13 列的布局,并在第一列绘制矩形,第三列绘制圆形

gt <- gtable(unit(rep(5, 3), c("cm")), unit(5, "cm"))
rect <- rectGrob(gp = gpar(fill = "black"))
circ <- circleGrob(gp = gpar(fill = "red"))

gt <- gtable_add_grob(gt, rect, 1, 1, name = "rect")
gt <- gtable_add_grob(gt, circ, 1, 3, name = "circ")

plot(gt)

只显示矩形

plot(gtable_filter(gt, "rect"))

只显示圆形

plot(gtable_filter(gt, "circ"))

不删除空列

plot(gtable_filter(gt, "circ", trim = FALSE))

介绍完了 gtable 之后,下一节将介绍如何绘制多个 Y

你可能感兴趣的:(R 数据可视化 —— gtable 介绍)