R 数据可视化——grid 系统(一)
xsobi 2024-11-23 10:46 1 浏览
前言
R 中主要存在两种绘图系统:
- base R 传统图像系统
- grid 图像系统
传统的图像系统是由 graphics 包所提供的一系列函数组成,grid 系统是 grid 包提供的
grid 包是一个底层的绘图系统,提供的都是底层的绘图函数,没有用于绘制复杂图形的高级函数。
像 ggplot2 和 lattice 两个顶层的绘图包都是基于 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
可以使用 convertWidth 和 convertHeight 实现单位之间的转换
> 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 类似,也是累积的
注意: 这些图形参数都可以接受一个向量值,比如,你可以将一个颜色向量传递给 col 或 fill 参数,如果向量的长度小于绘制的图形的个数,则参数会进行循环赋值
如,我们绘制 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 处将 id 为 1 的分组又一分为二,但是填充色还是灰色,并不是接续白色
4. viewport
在 grid 中,图像的绘制需要在画布中执行,也就是在绘制图像时需要新建一个画布
grid.newpage()
通常使用 grid.newpage() 函数来新建一个空白画布
在画布中,又可以定义很多个独立的矩形绘图窗口,在每个矩形窗口中都可以绘制任意你想要绘制的内容,这样的窗口就是 viewport
默认情况下,整个画布就是一个 viewport,如果新增一个 viewport,那么默认会继承所有默认的图形参数值
使用 viewport() 函数来新建一个 viewport,并接受位置参数(x 和 y) 和大小参数(width 和 height),以及对齐方式(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,新 push 的 viewport 将会相对于当前 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:继承上一个 viewport 的 clip 值
- 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 的排布方式有三种:
- vpList:viewport 列表,以平行的方式排列各 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])))
可以看到,根节点是整个画布,画布的子节点是 A,A 的子节点是 B,B 的子节点是 C,这就是堆叠的方式,一个套一个
那对于树形排列也就不难理解了
> grid.newpage()
> pushViewport(vpTree(vp1, vpList(vp2, vp3)))
> current.vpTree()
viewport[ROOT]->(viewport[A]->(viewport[B], viewport[C]))
根节点是整个画布,然后是子节点 A,A 的子节点是 B、C
我们知道,画布中的所有 viewport 是以树的方式存储的,那么我们就可以根据 viewport 的父节点来定位某一个 viewport
例如,我们想查找名称 C 的 viewport,其父节点为 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 布局
viewport 的 layout 参数可以用来设置布局,将 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))
)
我们构造了一个 3 行 3 列的布局,中间的位置是一个正方形
构造了布局之后,就可以添加到 viewport 中了
pushViewport(viewport(layout=vplay))
我们可以使用 layout.pos.col 和 layout.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"))
)
我们定义了一个 3 行 3 列的布局,列宽通过 widths 分配,即第一列宽度为 1 inches,剩下的两列的宽度的占比为 1:2
行高通过 heights 分配,第一行为 3 个 lines 单位,剩下的两行高度为 1:1
布局应该是下图这样子的
grid 布局也可以嵌套
假设我们有这样一个,1 行 2 列的 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)
}
新建一个 5 行 5 列的 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"))
)
)
)
然后,分别在 2 行 2 列和 4 行 4 列 中放置一个 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...
- 一周热门
- 最近发表
-
- 好用的云函数!后端低代码接口开发,零基础编写API接口
- 快速上手:Windows 平台上 cURL 命令的使用方法
- 使用 Golang net/http 包:基础入门与实战
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
- window使用curl命令的注意事项 curl命令用法
- Linux 系统curl命令使用详解 linuxctrl
- Tornado 入门:初学者指南 tornados
- PHP Curl的简单使用 php curl formdata
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- strcat (25)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- webgl开发 (24)
- parse (24)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)