美文网首页R ggplot
R 数据可视化 —— grid 系统(二)

R 数据可视化 —— grid 系统(二)

作者: 名本无名 | 来源:发表于2021-05-10 18:00 被阅读0次

    前言

    在前面一节中,我们主要介绍了如何使用 grid 来生成图形输出,以及图形窗口的布局。利用这些知识,可以很容易地为图形添加注释,编写一些简单的绘图函数

    这一节,我们将重点介绍如何使用 grid 函数来创建和操作图形对象。利用这些信息,可以交互式地编辑和修改图形输出

    图形对象

    1. 控制图像输出

    我们可以使用图形原语来绘制图形输出,并返回一个图形对象(grobs),例如

    library(RColorBrewer)
    
    grid.circle(
      name = "circles",
      x = seq(0.1, 0.9, length = 40),
      y = 0.5 + 0.4 * sin(seq(0, 2 * pi, length = 40)),
      r = abs(0.1 * cos(seq(0, 2 * pi, length = 40))),
      gp = gpar(col = brewer.pal(40, "Set2"))
    )
    

    这段代码将会绘制一串圆形


    同时也会生成一个 circle grob,该对象保存了当前绘制的这些圆形的信息

    grid 保留了一个显示列表,用于记录当前画布中的所有的 viewportgrobs。因此,grid.circle() 函数构造的对象也会保存在显示列表中,意味着我们可以根据对象的名称 circles 来获取、修改该对象

    使用 grid.get() 函数,可以获取该 circle 对象的拷贝

    > grid.get("circles")
    circle[circles]
    

    使用 grid.edit() 可以用来修改该 circle 对象的图像属性

    grid.edit(
      "circles",
      gp = gpar(
        col = brewer.pal(10, "RdBu")
        )
      )
    

    修改颜色属性之后,会直接显示在图形输出中

    还可以使用 grid.remove() 函数,从显示列表中删除图形对象的输出

    grid.remove("circles")
    

    一片空白,什么也没有

    1.1 标准的函数及参数

    控制 grobs 的函数包括:

    所有图像输出函数的第一个参数都是图像对象的名称,如果参数 grep = TRUE,可以接受正则表达式对象名称

    如果 global = TRUE,则会返回显示列表中所有匹配的对象,例如

    suffix <- c("even", "odd")
    
    for (i in 1:8)
      grid.circle(
        name = paste0("circle.", suffix[i %% 2 + 1]),
        r = (9 - i) / 20,
        gp = gpar(
          col = NA, 
          fill = grey(i / 10)
          )
      )
    

    我们绘制了 8 个同心圆,并根据奇偶顺序将 circle grob 命名为 circle.oddcircle.even

    然后,我们可以使用 grid.edit() 函数,修改所有名为 circle.oddgrobs 的颜色

    grid.edit(
      "circle.odd", 
      gp = gpar(
        fill = brewer.pal(4, "Set3")[4]),
      global = TRUE
      )
    

    或者,用正则表达式来匹配以 circle 开头的 grob

    grid.edit(
      "circle", 
      gp = gpar(
        col = "#80b1d3", 
        fill = "#fdb462"
        ),
      grep=TRUE, 
      global=TRUE
      )
    

    只要我们知道了 grobs 的名称,就可以对其获取、修改或删除

    getNames() 函数,可以帮助我们获取当前图形中所有 grobs 的名称

    2. grob 排布结构

    grob 的排布结果包括:

    • gList:包含多个 grobslist
    • gTreegrobs 的树形结构,即一个 grob 中包含其他的 grob

    例如,对于 xaxis grob

    pushViewport(viewport(y = 0.5, height = 0.5, width = 0.5))
    grid.rect()
    pushViewport(viewport(y = 0.5, height = 0.5, width = 0.5))
    
    grid.xaxis(name="axis1", at=1:4/5)
    

    会包含有多个子 grob,如线条,文本等

    > childNames(grid.get("axis1"))
    [1] "major"  "ticks"  "labels"
    

    如果把 xaxis grob 看作为一棵树的根,那么它包含三个子 grob

    其中 majortickslines groblabelstext grob

    其中 at 参数设置了轴刻度,我们可以使用 grid.edit 来修改

    grid.edit("axis1", at=1:3/4)
    

    那想要修改 labels 的格式,怎么办?即如何访问一个对象的子对象呢?

    可以使用 gPath(grob path) 函数,类似于 vpPath,可以使用父节点名称加子节点名称来访问

    grid.edit(gPath("axis1", "labels"), rot=45)
    

    或者,也可以使用 axis1::labels 方式来访问

    注意grobs 的搜索是深度优先,也就是说,如果在显示列表中遍历到了一个 gTree grob,且未找到匹配项,则会对该 grob 执行深度优先遍历

    这种 gTree 结构对象,也包含 gpvp 参数。

    在父节点上设置对应的 gp 参数值,会作为默认值传递给子对象。例如

    grid.xaxis(gp=gpar(col="grey"))
    

    也可以将一个 viewport 直接作为参数值传递

    grid.xaxis(vp=viewport(y=0.75, height=0.5))
    

    3. 图形对象

    在前面的章节中,我们介绍的都是如何使用函数直接生成图形输出并返回图形对象(grob

    在这一节,我们将介绍如果创建 grob,但不绘制图形,通过对 grob 创建及修改,并在最后使用 grid.draw() 函数来绘制出图形。

    每个能产生图形输出和图形对象的 grid 函数都有一个对应的只创建图形对象,没有图形输出的函数

    例如,grid.circle() 对应于 circleGrob()grid.edit() 对应于 editGrob(),在前面的函数表中都有列出

    例如

    grid.newpage()
    pushViewport(viewport(width = 0.5, height = 0.5))
    # 创建 x 轴对象
    ag <- xaxisGrob(at=1:4/5)
    # 修改对象,将标签的字体变为斜体
    ag <- editGrob(ag, "labels", gp=gpar(fontface="italic"))
    # 绘图
    grid.draw(ag)
    

    我们可以将不同的 grob 组合在一起,生成一个复杂的图形。比如

    grid.newpage()
    
    tg <- textGrob("sample text")
    
    rg <- rectGrob(
      width = 1.2*grobWidth(tg),
      height = 1.5*grobHeight(tg)
      )
    
    boxedText <- gTree(
      children = gList(rg, tg)
      )
    

    我们构建一个名为 boxedTextgTree 对象,包含其子对象包括一个文本和一个矩形

    我们直接可以绘制组合对象

    grid.draw(boxedText)
    

    而对该对象的图形属性的修改,会反映到具体的子对象中

    grid.draw(
      editGrob(
        boxedText, 
        gp=gpar(col="skyblue")
        )
      )
    

    指定 viewport

    grid.draw(
      editGrob(
        boxedText, 
        vp = viewport(angle=45), 
        gp = gpar(fontsize=18)
        )
      )
    

    3.1 捕获输出

    在上面的例子中,我们先构建了一个组合对象,然后绘制该对象

    还可以反着来,先绘制图形对象,然后对它们进行组合。

    使用 grid.grab() 函数,可以获取当前画布中所有输出的图形对象,并以 gTree 的形式返回

    例如,我们使用 ggplot2 绘制一个直方图,并获取所有图形对象

    ggplot(mpg) + geom_histogram(aes(displ, fill = class), bins = 10, position = "dodge")
    
    histTree <- grid.grab()
    

    然后,你可以尝试运行下面的代码

    grid.newpage()
    grid.draw(histTree)
    

    你会发现,可以绘制出一张一模一样的图


    也可以使用 grid.grabExpr 来获取表达式的输出图形对象

    grid.grabExpr(
      print(
        ggplot(mpg) + 
          geom_histogram(
            aes(displ, fill = class), 
            bins = 10, 
            position = "dodge")
        )
      )
    

    4. 图形对象的放置

    假设我们有一个复杂图形

    # 文本对象
    label <- textGrob(
      "A\nPlot\nLabel ",
      x = 0, 
      just = "left"
      )
    
    x <- seq(0.1, 0.9, length=50)
    y <- runif(50, 0.1, 0.9)
    
    # gTree 结构图形对象,包括矩形、线图、点图
    gplot <- gTree(
      children = gList(
        rectGrob(
          gp = gpar(
            col = "grey60",
            fill = "#cbd5e8",
            alpha = 0.3)
        ),
        linesGrob(
          x, 
          y,
          gp = gpar(
            col = "#33a02c"
          )),
        pointsGrob(
          x, y, 
          pch = 16, 
          size = unit(5, "mm"),
          gp = gpar(
            col = "#fb8072"
          ))
      ),
      vp = viewport(
        width = unit(1, "npc") - unit(5, "mm"),
        height = unit(1, "npc") - unit(5, "mm")
      )
    )
    

    我们可以使用上一章节提到的布局方法,将该图像设计为 12 列的布局

    layout <- grid.layout(
      nrow = 1, 
      ncol = 2, 
      widths = unit(
        c(1, 1), 
        c("null", "grobwidth"), 
        list(NULL, label)
        )
      )
    

    然后将图形绘制到指定位置中

    pushViewport(viewport(layout=layout))
    pushViewport(viewport(layout.pos.col=2))
    grid.draw(label)
    popViewport()
    
    pushViewport(viewport(layout.pos.col=1))
    grid.draw(gplot)
    popViewport(2)
    

    但其实,grid 提供了更简便的函数用于放置 grobs

    grid.frame() 函数创建一个没有子对象的 gTree,可以使用 grid.pack() 向其中添加子对象,同时确保为每个子对象保留足够的绘图空间

    上面的代码可以改写成

    grid.newpage()
    
    # 新建一个空 frame
    grid.frame(name="frame1")
    # 放置 gplot 对象,在这一阶段,gplot 会占据整个 frame
    grid.pack("frame1", gplot)
    # 在 frame 的右边放置 label 对象
    grid.pack("frame1", label, side="right")
    

    这种动态的方式很简便,但是也带来了时间上的花费,随着需要放置的对象越来越多,速度会越来越慢。

    另一种替代的方式是,先定义一个布局,然后再放置对象

    grid.frame(name="frame1", layout=layout)
    
    grid.place("frame1", gplot, col=1)
    grid.place("frame1", label, col=2)
    

    4.1 安静模式

    在上面两个例子中,每次放置一个 grob 都会更新一遍图形输出。所以,一个更好的方式是,在安静模式下创建一个 frame,然后放置 grobs

    安静模式,即使用对象函数 frameGrob()placeGrob()/packGrob 创建 frame、放置 grobs,但是不会输出图形,只有在所有设置完成之后,使用 grid.draw 一次性绘制

    # 创建 frame
    fg <- frameGrob(layout=layout)
    # 添加 grob
    fg <- placeGrob(fg, gplot, col=1)
    fg <- placeGrob(fg, label, col=2)
    # 一次性绘制
    grid.draw(fg)
    

    相关文章

      网友评论

        本文标题:R 数据可视化 —— grid 系统(二)

        本文链接:https://www.haomeiwen.com/subject/gombdltx.html