百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

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

xsobi 2024-11-23 10:46 1 浏览

前言

R 中主要存在两种绘图系统:

  • base R 传统图像系统
  • grid 图像系统

传统的图像系统是由 graphics 包所提供的一系列函数组成,grid 系统是 grid 包提供的

grid 包是一个底层的绘图系统,提供的都是底层的绘图函数,没有用于绘制复杂图形的高级函数。

ggplot2lattice 两个顶层的绘图包都是基于 grid 系统的,所以,了解 grid 包对于理解 ggplot2 的顶层函数的工作方式是很有帮助的

同时,也可以使用 grid 包来灵活地控制图形的外观和布局

安装导入

install.packages("grid")
library(grid)

grid 图像模型

1. 图形原语

grid 提供了一些函数用于绘制简单的图形,例如

这些函数被称为图形原语,使用这些函数可以直接绘制对应的图形,例如

grid.text(label = "Let's us begin!")
grid.circle(
  x=seq(0.1, 0.9, length=100),
  y=0.5 + 0.4*sin(seq(0, 2*pi, length=100)),
  r=abs(0.1*cos(seq(0, 2*pi, length=100)))
)

2. 坐标系统

grid 的坐标系统是用来确定数值的单位,同样的数值在不同的单位中表示不同的大小,看起来叫单位系统应该会更恰当些

坐标系统如下

使用 unit 函数来设置不同的系统

> unit(1, "cm")
[1] 1cm
> unit(1:4, "mm")
[1] 1mm 2mm 3mm 4mm
> unit(1:4, c("npc", "mm", "native", "lines"))
[1] 1npc    2mm     3native 4lines 

坐标系统之间的运算将会以表达式的方式返回

> unit(1:4, "mm")[1] - unit(1:4, "mm")[4]
[1] 1mm-4mm
> unit(1, "npc") - unit(1:4, "mm")
[1] 1npc-1mm 1npc-2mm 1npc-3mm 1npc-4mm
> max(unit(1:4, c("npc", "mm", "native", "lines")))
[1] max(1npc, 2mm, 3native, 4lines)

对于字符串及对象长度坐标系统

> unit(1, "strwidth", "some text")
[1] 1strwidth
> unit(1, "grobwidth", textGrob("some text"))
[1] 1grobwidth

有对应的简便函数可以使用

> stringHeight("some text")
[1] 1strheight
> grobHeight(textGrob("some text"))
[1] 1grobheight

可以使用 convertWidthconvertHeight 实现单位之间的转换

> convertHeight(unit(1, "cm"), "mm")
[1] 10mm
> convertHeight(unit(1, "dida"), "points")
[1] 1.07000864304235points
> convertHeight(unit(1, "cicero"), "points")
[1] 12.8401037165082points
> convertHeight(unit(1, "cicero"), "dida")
[1] 12dida
> convertHeight(unit(1, "points"), "scaledpts")
[1] 65536scaledpts
> convertWidth(stringWidth("some text"), "lines")
[1] 3.61246744791667lines
> convertWidth(stringWidth("some text"), "inches")
[1] 0.722493489583333inches

对于一个图形对象,如果修改了图形对象属性,则对应的大小也会改变

> grid.text("some text", name="tgrob")
> convertWidth(grobWidth("tgrob"), "inches")
[1] 0.722493489583333inches
# 修改图形对象的 fontsize 属性
> grid.edit("tgrob", gp=gpar(fontsize=18))
> convertWidth(grobWidth("tgrob"), "inches")
[1] 1.083740234375inches

我们可以使用不同的单位系统来绘制一个矩形

grid.rect(
  x=unit(0.5, "npc"), 
  y=unit(1, "inches"),
  width=stringWidth("very snug"), 
  height=unit(1, "lines"), 
  just=c("left", "bottom")
)

3. gpar

所有的图形原语函数都有一个 gp(graphical parameters) 参数,用来接收一个 gpar 对象,该对象包含一些图形参数用于控制图像的输出

gpar 对象可以使用 gpar() 函数来生成,例如

> gpar(col="red", lty="dashed")
$col
[1] "red"

$lty
[1] "dashed"

这些图形参数包括

使用 get.gpar 可以获取当前图形参数的值,如果未指定要获取的参数,将会返回所有的参数值

> get.gpar(c("lty", "fill"))
$lty
[1] "solid"

$fill
[1] "white"

因此,我们可以在绘制图像时,传递 gp 参数来设置图像参数

grid.rect(
  x=0.66, 
  height=0.7, 
  width=0.2,
  gp=gpar(fill="blue")
)

grid.rect(
  x=0.33, 
  height=0.7, 
  width=0.2
)

grid 中,cex 参数是累积的,也就是说当前的 cex 值等于当前设置的值乘上之前的 cex

例如

pushViewport(viewport(gp=gpar(cex=0.5)))

grid.text("How small do you think?", gp=gpar(cex=0.5))

在一个 viewport 中设置了 cex = 0.5,之后的文本又设置了 cex = 0.5,最后文本的大小就是 0.5*0.5 = 0.25

alpha 参数与 cex 类似,也是累积的

注意: 这些图形参数都可以接受一个向量值,比如,你可以将一个颜色向量传递给 colfill 参数,如果向量的长度小于绘制的图形的个数,则参数会进行循环赋值

如,我们绘制 100 个圆形,但是只传递了一个长度为 50 的颜色向量给 col 参数

grid.circle(
  x = seq(0.1, 0.9, length=100),
  y = 0.5 + 0.4*sin(seq(0, 2*pi, length=100)),
  r = abs(0.1*cos(seq(0, 2*pi, length=100))),
  gp = gpar(col=rainbow(50))
  )

对于多边形 grid.polygon() 函数,有一个 id 参数可以将多边形的点进行分组,如果某一分组点中包含 NA 值,则又会将在 NA 处将点分为两组

# 设置均等分的角度,并删除最后一个角度
angle <- seq(0, 2*pi, length=11)[-11]

grid.polygon(
  x = 0.25 + 0.2*cos(angle), 
  y = 0.5 + 0.3*sin(angle),
  id = rep(1:2, c(7, 3)),
  gp = gpar(
    fill=c("grey", "white")
    )
  )

# 将其中一个角度设置为 NA
angle[4] <- NA

grid.polygon(
  x = 0.75 + 0.2*cos(angle), 
  y = 0.5 + 0.3*sin(angle),
  id = rep(1:2, c(7, 3)),
  gp = gpar(
    fill=c("grey", "white")
    )
  )

从图中可以看出,本来根据 id 值分为两组,第一组为灰色填充,第二组为白色填充。

但是在添加 NA 之后,在 NA 处将 id1 的分组又一分为二,但是填充色还是灰色,并不是接续白色

4. viewport

grid 中,图像的绘制需要在画布中执行,也就是在绘制图像时需要新建一个画布

grid.newpage()

通常使用 grid.newpage() 函数来新建一个空白画布

在画布中,又可以定义很多个独立的矩形绘图窗口,在每个矩形窗口中都可以绘制任意你想要绘制的内容,这样的窗口就是 viewport

默认情况下,整个画布就是一个 viewport,如果新增一个 viewport,那么默认会继承所有默认的图形参数值

使用 viewport() 函数来新建一个 viewport,并接受位置参数(xy) 和大小参数(widthheight),以及对齐方式(just)

> viewport(
+   x = unit(0.4, "npc"), 
+   y = unit(1, "cm"),
+   width = stringWidth("very very snug indeed"), 
+   height = unit(6, "lines"), 
+   just = c("left", "bottom")
+   )
viewport[GRID.VP.4] 

viewport() 函数返回的是一个 viewport 对象,但其实你会发现,什么东西都没有画出来

因为,创建了一个 viewport 对象区域之后,需要将其 push 到图像设备中

其位置大致应该是这样的

4.1 viewport 的切换

pushViewport() 函数可以将一个 viewport 对象 push 到图像设备中,例如

grid.text(
  "top-left corner", 
  x=unit(1, "mm"),
  y=unit(1, "npc") - unit(1, "mm"),
  just=c("left", "top")
  )

pushViewport(
  viewport(
    width=0.8, 
    height=0.5, 
    angle=10,
    name="vp1"
    )
  )

grid.rect()

grid.text(
  "top-left corner", 
  x = unit(1, "mm"),
  y = unit(1, "npc") - unit(1, "mm"),
  just = c("left", "top")
  )

我们在最外层画布的左上角添加一串文本,然后添加一个 viewport,同时绘制外侧矩形框,并旋转 10 度,也在左上角添加一串文本

在当前 viewport 的基础上,还可以在新建 viewport,新 pushviewport 将会相对于当前 viewport 的位置来放置

pushViewport(
  viewport(
    width=0.8, 
    height=0.5, 
    angle=10,
    name="vp2"
    )
  )

grid.rect()

grid.text(
  "top-left corner", 
  x = unit(1, "mm"),
  y = unit(1, "npc") - unit(1, "mm"),
  just = c("left", "top")
  )

每次 push 一个 viewport 之后,都会将该 viewport 作为当前活动的窗口,如果要回滚到之前的 viewport,可以使用 popViewport() 函数,该函数会将当前活动窗口删除

popViewport()

grid.text(
  "bottom-right corner",
  x=unit(1, "npc") - unit(1, "mm"),
  y=unit(1, "mm"), 
  just=c("right", "bottom")
  )

从图片中可以看到,活动窗口已经切换到第二个 viewport,并将文本绘制在其右下角

popViewport() 还可接受一个参数 n,用于指定需要 pop 几个 viewport。默认 n = 1,传递更大的值可以跳转到更上层的 viewport,如果设置为 0 则会返回到最外层图形设备上。

另一个更改活动窗口的方法是,使用 upViewport()downViewport() 函数。

upViewport() 函数与 popViewport() 类似,不同之处在于,upViewport() 函数不会删除当前活动 viewport

这样,在重新访问之前的 viewport 时,不用再 push 一遍,而且能够提升访问的速度。

重新访问 viewport 使用的是 downViewport() 函数,通过 name 参数来选择指定的 viewport

# 切换到最外层
upViewport()
# 在右下角添加文本
grid.text(
  "bottom-right corner",
  x=unit(1, "npc") - unit(1, "mm"),
  y=unit(1, "mm"), 
  just=c("right", "bottom")
  )
# 返回 vp1
downViewport("vp1")
# 添加外侧框线
grid.rect(
  width=unit(1, "npc") + unit(2, "mm"), 
  height=unit(1, "npc") + unit(2, "mm"),
  gp = gpar(fill = NA)
  )

如果想要访问 vp2 会报错,不存在该 viewport

> downViewport("vp2")
Error in grid.Call.graphics(C_downviewport, name$name, strict) : 
  Viewport 'vp2' was not found

还可以直接使用 seekViewport() 函数来切换到指定名称的 viewport

4.2 裁剪 viewport

我们可以将图形限制在当前 viewport 之内,如果绘制的图形大小超过了当前 viewport 则不会显示,我们可以使用 clip 参数

该参数接受三个值:

  • on:输出的图形必须保持在当前 viewport 内,超出的部分会被裁剪
  • inherit:继承上一个 viewportclip
  • off:不会被裁剪

例如

grid.newpage()
# 在画布中心添加一个 viewport,并设置允许剪切
pushViewport(viewport(w=.5, h=.5, clip="on"))
# 添加矩形框和线条很粗的圆形
grid.rect(
  gp = gpar(fill = "#8dd3c7")
  )
grid.circle(
  r = .7, 
  gp = gpar(
    lwd = 20,
    col = "#fdb462"
    )
)

# 在当前 viewport 中添加一个 viewport,继承方式
pushViewport(viewport(clip="inherit"))
# 添加线条更细一点的圆形
grid.circle(
  r = .7, 
  gp = gpar(
    lwd = 10, 
    col = "#80b1d3",
    fill = NA)
)
# 关闭裁剪
pushViewport(viewport(clip="off"))
# 显示整个圆形
grid.circle(
  r=.7,
  gp = gpar(
    fill = NA,
    col = "#fb8072"
  )
)

只有最后一个圆显示出了全部,前面两个圆形只显示在 viewport 内的部分

4.3 viewport 的排列

viewport 的排布方式有三种:

  • vpListviewport 列表,以平行的方式排列各 viewport
  • vpStack:以堆叠的方式排列,俗称套娃,与使用 pushViewport 功能相似
  • vpTree:以树的方式排列,一个根节点可以有任意个子节点

例如,我们新建三个 viewport

vp1 <- viewport(name="A")
vp2 <- viewport(name="B")
vp3 <- viewport(name="C")

然后,我们以列表的方式将这些 viewport push 到图形设备中

pushViewport(vpList(vp1, vp2, vp3))

可以使用 current.vpTree 函数来查看当前的 viewport 排列树

> current.vpTree()
viewport[ROOT]->(viewport[A], viewport[B], viewport[C]) 

可以看到,这三个 viewport 是并列的关系

我们再看看以堆叠的方式放置

> grid.newpage()
> pushViewport(vpStack(vp1, vp2, vp3))
> current.vpTree()
viewport[ROOT]->(viewport[A]->(viewport[B]->(viewport[C]))) 

可以看到,根节点是整个画布,画布的子节点是 AA 的子节点是 BB 的子节点是 C,这就是堆叠的方式,一个套一个

那对于树形排列也就不难理解了

> grid.newpage()
> pushViewport(vpTree(vp1, vpList(vp2, vp3)))
> current.vpTree()
viewport[ROOT]->(viewport[A]->(viewport[B], viewport[C]))

根节点是整个画布,然后是子节点 AA 的子节点是 BC

我们知道,画布中的所有 viewport 是以树的方式存储的,那么我们就可以根据 viewport 的父节点来定位某一个 viewport

例如,我们想查找名称 Cviewport,其父节点为 B,再上层父节点为 A,则可以使用 vpPath 函数来构造检索路径

> vpPath("A", "B", "C")
A::B::C 

同时也可以消除同名 viewport 的干扰

4.4 将 viewport 作为图形原语的参数

每个原语函数都有一个 vp 参数

例如,在一个 viewport 中绘制文本

vp1 <- viewport(width=0.5, height=0.5, name="vp1")
pushViewport(vp1)

grid.text("Text drawn in a viewport")
popViewport()

也可以下面的代码代替,将文本绘制到指定的 viewport

grid.text("Text drawn in a viewport", vp=vp1)

4.5 viewport 的图形参数

viewport 也有一个 gp 参数,用来设置图形属性,设置的值将会作为 viewport 中所有的图形对象的默认值

grid.newpage()

pushViewport(
  viewport(
    gp = gpar(fill="grey")
    )
  )

grid.rect(
  x = 0.33, 
  height = 0.7, 
  width = 0.2
  )
grid.rect(
  x = 0.66, 
  height = 0.7, 
  width = 0.2,
  gp = gpar(fill="black")
  )

popViewport()

4.6 布局

viewportlayout 参数可以用来设置布局,将 viewport 区域分割成不同的行和列,行之间可以有不同的高度,列之间可以有不同的宽度。

grid 布局使用 grid.layout() 函数来构造,例如

vplay <- grid.layout(
  nrow = 3, 
  ncol = 3, 
  respect=rbind(
    c(0, 0, 0),
    c(0, 1, 0),
    c(0, 0, 0))
  )

我们构造了一个 33 列的布局,中间的位置是一个正方形

构造了布局之后,就可以添加到 viewport 中了

pushViewport(viewport(layout=vplay))

我们可以使用 layout.pos.collayout.pos.row 参数来指定 viewport 放置的位置

# 新建一个 viewport 并放置在第二列
pushViewport(
  viewport(
    layout.pos.col = 2, 
    name = "col2")
  )
grid.rect(
  gp = gpar(
    lwd = 10,
    col = "black",
    fill = NA
  ))
grid.text(
  label = "col2",
  x = unit(1, "mm"),
  y = unit(1, "npc") - unit(1, "mm"),
  just = c("left", "top")
  )

upViewport()

# 新建一个 viewport 并放置在第二行
pushViewport(
  viewport(
    layout.pos.row = 2, 
    name = "row2")
  )

grid.rect(
  gp = gpar(
    lwd = 10,
    col = "grey",
    fill = NA
  ))
grid.text(
  x = unit(1, "mm"),
  y = unit(1, "npc") - unit(1, "mm"),
  label = "row2",
  just = c("left", "top")
)

也可以使用 unit 来设置行列的高度和宽度,例如

unitlay <- grid.layout(
  nrow = 3, 
  ncol = 3, 
  widths = unit(
    c(1, 1, 2),
    c("inches", "null", "null")
  ),
  heights = unit(
    c(3, 1, 1),
    c("lines", "null", "null"))
  )

我们定义了一个 33 列的布局,列宽通过 widths 分配,即第一列宽度为 1 inches,剩下的两列的宽度的占比为 1:2

行高通过 heights 分配,第一行为 3lines 单位,剩下的两行高度为 1:1

布局应该是下图这样子的

grid 布局也可以嵌套

假设我们有这样一个,12 列的 viewport

gridfun <- function() { 
  # 1*2 的布局
  pushViewport(viewport(layout=grid.layout(1, 2))) 
  # 第一行第一列的 viewport
  pushViewport(viewport(layout.pos.col=1)) 
  # 绘制矩形和文本
  grid.rect(gp = gpar(fill = "#80b1d3")) 
  grid.text("black")
  grid.text("&", x=1)
  popViewport()
  # 第一行第二列的 viewport
  pushViewport(viewport(layout.pos.col=2, clip="on"))
  
  grid.rect(gp=gpar(fill="#fb8072"))
  grid.text("white", gp=gpar(col="white"))
  grid.text("&", x=0, gp=gpar(col="white"))
  
  popViewport(2)
}

新建一个 55 列的 viewport

pushViewport( 
  viewport(
    layout = grid.layout(
      nrow = 5, 
      ncol = 5, 
      widths=unit(
        c(5, 1, 5, 2, 5), 
        c("mm", "null", "mm", "null", "mm")),
      heights=unit(
        c(5, 1, 5, 2, 5), 
        c("mm", "null", "mm", "null", "mm"))
      )
    )
  )

然后,分别在 22 列和 44 列 中放置一个 viewport

pushViewport(
  viewport(
    layout.pos.col=2, 
    layout.pos.row=2)
  )
gridfun()
popViewport()

pushViewport(
  viewport(
    layout.pos.col=4, 
    layout.pos.row=4)
  )
gridfun()
popViewport(2)



相关推荐

好用的云函数!后端低代码接口开发,零基础编写API接口

前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...

快速上手:Windows 平台上 cURL 命令的使用方法

在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...

使用 Golang net/http 包:基础入门与实战

简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...

#小白接口# 使用云函数,人人都能编写和发布自己的API接口

你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...

极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...

window使用curl命令的注意事项 curl命令用法

cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...

Linux 系统curl命令使用详解 linuxctrl

curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...

Tornado 入门:初学者指南 tornados

Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...

PHP Curl的简单使用 php curl formdata

本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...

Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介

本章涵盖使用Actix提供静态网页...

我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些

这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...

linux文件下载、服务器交互(curl)

基础环境curl命令描述...

curl简单使用 curl sh

1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...

常用linux命令:curl 常用linux命令大全

//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...

三十七,Web渗透提高班之hack the box在线靶场注册及入门知识

一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...