96-R可视化25-底层绘图系统grid学习之viewports

2022-04-05 14:57:17 浏览数 (1)

  • 参考:
    • 4.5 The grid Package | Mastering Software Development in R (bookdown.org)[1]
    • R实战:grid包 - 悦光阴 - 博客园 (cnblogs.com)[2]
    • (11条消息) R语言grid包使用笔记——viewport_数据之美-CSDN博客_r语言grid包[3]
    • 书本《R 绘图系统》

前言

接着前面[[91-R可视化23-底层绘图系统grid学习之grob对象]] 继续介绍。

1-我的第一个Viewports

如果说grob 对象是画布上一个个具体的图形,那么Viewports就是画布上的具体的画图区域。

我们可以选定不同的Viewports 区域,在这些不同的区域内进行作图。

我们可以简单理解为,Viewports 可以将整个绘图画布拆分成不同的区域,通过设定不同的区域,我们可以更加方便的对我们的绘图进行管理和个性化的设置。

默认下,如果我们不特别的设置,这创建的grid 对象会绘制在全画布尺寸的Viewports 上的:

代码语言:javascript复制
grid.draw(rectGrob(gp = gpar(col = "pink",
                              lwd = 4)))

类似grob 对象,通过xxGrob创建,我们可以通过viewport创建Viewports 对象:

代码语言:javascript复制
sample_vp <- viewport(x = 0.5, y = 0.5, 
                      width = 0.5, height = 0.5,
                      just = c("left", "bottom"))

viewport 在创建时,主要有几个参数:

  • x,y 指定viewport 在大画布上的位置,可以使用unit 单位,亦或是数字型,对应这个画布的数值是从0到1,本例中x,y 均为0.5 则在画布中央开始;
  • width, height 则同理,0.5 则占据画布一般距离;
  • just 用于指定viewport 方位,left bottom 表示画布方向来自左与下,则其对应区域是向右和上展开的。

我们可以查看viewport:

代码语言:javascript复制
vp <- viewport(x = 0.5, y = 0.5, width = 0.5, height = 0.25, angle=45)
grid.show.viewport(vp)

Viewports 对象创建后,我们就可以进入对应的viewport 区域了。不同于一般的对象我们直接操作,viewport 有点类似于层级的概念,我们需要从原本大的画布进入对应的viewport: pushViewport(),并且完成之后需要退出 popViewport()

代码语言:javascript复制
candy <- circleGrob(r = 0.1, x = 0.5, y = 0.6,
                    gp = gpar(col = "pink",
                              lty = 3,
                              lwd = 2))
stick <- segmentsGrob(x0 = 0.5, x1 = 0.5, y0 = 0, y1 = 0.5, gp = gpar(lwd = 2))
lollipop <- gTree(children = gList(candy, stick))
sample_vp <- viewport(x = 0.5, y = 0.5, 
                      width = 0.5, height = 0.5,
                      just = c("left", "bottom"))
pushViewport(sample_vp)
grid.draw(roundrectGrob())
grid.draw(lollipop)
popViewport()

当我们通过pushViewport() 进入到对应的viewport 区域后,则后续的grid.draw 绘制grob 对象,则都是在该区域里进行的了。

我们也可以将画布放在中央。

代码语言:javascript复制
sample_vp <- viewport(x = 0.5, y = 0.5, 
                      width = 0.5, height = 0.5,
                      just = c("center", "center"))

亦或是自由调整:

代码语言:javascript复制
sample_vp2 <- viewport(x = 0.5, y = 0.5, 
                      width = 0.5, height = 0.5,
                      just = c("center", "bottom"))
pushViewport(sample_vp2)
grid.draw(roundrectGrob())
grid.draw(lollipop)
popViewport()

just 接受的向量中,第一个元素对应横向,第二个对应纵向。

2-多个viewport 应该不会打架吧

我们来尝试一下,在一个画布上,创建两个viewport,并进入它们的区域:

代码语言:javascript复制
candy <- circleGrob(r = 0.1, x = 0.5, y = 0.6,
                    gp = gpar(col = "pink",
                              lty = 3,
                              lwd = 2))
stick <- segmentsGrob(x0 = 0.5, x1 = 0.5, y0 = 0, y1 = 0.5, gp = gpar(lwd = 2))
lollipop <- gTree(children = gList(candy, stick))

grid.draw(rectGrob(gp = gpar(col = "pink",
                             lwd = 4)))

sample_vp_1 <- viewport(x = 0.75, y = 0.75, 
                        width = 0.25, height = 0.25,
                        just = c("left", "bottom"))
pushViewport(sample_vp_1)
grid.draw(roundrectGrob())
grid.draw(lollipop)
popViewport()

sample_vp_2 <- viewport(x = 0, y = 0, 
                        width = 0.5, height = 0.5,
                        just = c("left", "bottom"))
pushViewport(sample_vp_2)
grid.draw(roundrectGrob())
grid.draw(lollipop)
popViewport()

3-套娃式viewport

如果我们进入了一个viewplot,但没有退出呢?

代码语言:javascript复制
candy <- circleGrob(r = 0.1, x = 0.5, y = 0.6,
                    gp = gpar(col = "pink",
                              lty = 3,
                              lwd = 2))
stick <- segmentsGrob(x0 = 0.5, x1 = 0.5, y0 = 0, y1 = 0.5, gp = gpar(lwd = 2))
lollipop <- gTree(children = gList(candy, stick))

sample_vp <- viewport(x = 0.5, y = 0.5, 
                       width = 0.5, height = 0.5,
                       just = c("center", "bottom"))
pushViewport(sample_vp)

grid.draw(rectGrob(gp = gpar(col = "pink",
                             lwd = 4)))

sample_vp_1 <- viewport(x = 0.75, y = 0.75, 
                        width = 0.25, height = 0.25,
                        just = c("left", "bottom"))
pushViewport(sample_vp_1)
grid.draw(roundrectGrob())
grid.draw(lollipop)
popViewport()

sample_vp_2 <- viewport(x = 0, y = 0, 
                        width = 0.5, height = 0.5,
                        just = c("left", "bottom"))
pushViewport(sample_vp_2)
grid.draw(roundrectGrob())
grid.draw(lollipop)
popViewport()

那么所有的内容都是相对的,你可以理解这时候的操作,就是把内层小的画布,当成了之前处理的大画布。

这里的操作非常的像base 包里的逻辑,如果我们不清除这个画布上的内容,还可以把上述两个大图结合起来:

4-结合grob 对象

代码语言:javascript复制
library(ggmap)
balt_counties <- map_data("county", region = "maryland") %>%
  mutate(our_counties = subregion %in% c("baltimore", "baltimore city"))
balt_map <- get_map("Baltimore County", zoom = 10) %>%
  ggmap(extent = "device")   
  geom_polygon(data = filter(balt_counties, our_counties == TRUE),
               aes(x = long, y = lat, group = group),
               fill = "red", color = "darkred", alpha = 0.2)
maryland_map <- balt_counties %>%
  ggplot(aes(x = long, y = lat, group = group, fill = our_counties))   
  geom_polygon(color = "black")   
  scale_fill_manual(values = c("white", "darkred"), guide = FALSE)   
  theme_void()   
  coord_map()

grid.draw(ggplotGrob(balt_map))
md_inset <- viewport(x = 0, y = 0, 
                     just = c("left", "bottom"),
                     width = 0.35, height = 0.35)
pushViewport(md_inset)
grid.draw(rectGrob(gp = gpar(alpha = 0.5, col = "white")))
grid.draw(rectGrob(gp = gpar(fill = NA, col = "black")))
grid.draw(ggplotGrob(maryland_map))
popViewport()

这一步可能会因为get_map 调用谷歌地图出错,可以参见:(11条消息) R语言ggmap包的安装及使用_Pylady的博客-CSDN博客_ggmap怎么用[4]Google Maps Platform | Google Developers[5]

5-viewport用法详说

通过上面的介绍,我们知道,可以通过popViewport() 跳出所在的Viewports 区域。但是,如果你是一个套娃的狂热爱好者呢?难道只能依托循环来反复执行退出吗?那代码也太丑了。

popViewport 提供了方法。比如 popViewport(2),就表示跳出两个层级的Viewports,并将之前的viewports 删除。不过问题也来了,我们能否获取所在Viewports 的层级呢?

通过current.viewport 函数:

代码语言:javascript复制
> grid::current.viewport()
viewport[vp_plot] 

参考:R实战:grid包 - 悦光阴 - 博客园 (cnblogs.com)[6]viewport 的遍历一共包括以下几个函数:

  • pushViewport()函数:向活跃viewport中添加一个viewport,作为树中的活跃viewport,原活跃viewport变成父viewport,这意味着,当一个viewport被push到树中时,该viewport变成活跃viewport,是原活跃viewport的子viewport。
  • popViewport()函数:把活跃viewport从树中删除,其父viewport变成活跃viewport。
  • upViewport()函数:导航到活跃viewport的父viewport,当前viewport变成活跃viewport,原viewport不会被删除;
  • downViewport()函数:导航到活跃viewport的父viewport,当前viewport变成活跃viewport,原viewport不会被删除;
  • searchViewport()函数:根据viewport的名字,导航到任意viewport,当前viewport变成活跃viewport,原viewport不会被删除。

此外,我们也可以利用grid.ls 获得画布的全部grob 对象名称:

代码语言:javascript复制
> grid.ls()
GRID.roundrect.361
GRID.gTree.359
  GRID.circle.357
  GRID.segments.358
GRID.rect.365
GRID.roundrect.366
GRID.gTree.364
  GRID.circle.362
  GRID.segments.363
GRID.roundrect.367
GRID.gTree.364
  GRID.circle.362
  GRID.segments.363
GRID.rect.371
GRID.roundrect.372
GRID.gTree.370
  GRID.circle.368
  GRID.segments.369
GRID.roundrect.373
GRID.gTree.370
  GRID.circle.368
  GRID.segments.369
GRID.rect.377
GRID.roundrect.378
GRID.gTree.376
  GRID.circle.374
  GRID.segments.375
GRID.roundrect.379
GRID.gTree.376
  GRID.circle.374
  GRID.segments.375

接着edit 它们就可以啦。

之前我们说过,viewport 在创建时,主要有几个参数:

  • x,y 指定viewport 在大画布上的位置,可以使用unit 单位,亦或是数字型,对应这个画布的数值是从0到1,本例中x,y 均为0.5 则在画布中央开始;
  • width, height 则同理,0.5 则占据画布一般距离;
  • just 用于指定viewport 方位,left bottom 表示画布方向来自左与下,则其对应区域是向右和上展开的。

这个画布的数值,默认是使用 npcunits 的,也就是原始的绘图单位,此外,还可以使用诸如:inches (inches), centimeters (cm), and millimeters (mm) 等等。

此外,我们还可以通过另外两个参数,调整该默认单位的尺度(scale):

代码语言:javascript复制
ex_vp <- viewport(x = 0.5, y = 0.5, 
                  just = c("center", "center"),
                  height = 0.8, width = 0.8,
                  xscale = c(0, 100), yscale = c(0, 10))
pushViewport(ex_vp)
grid.draw(rectGrob())
grid.draw(circleGrob(x = unit(20, "native"), y = unit(5, "native"),
                     r = 0.1, gp = gpar(fill = "lightblue")))
grid.draw(circleGrob(x = unit(85, "native"), y = unit(8, "native"),
                     r = 0.1, gp = gpar(fill = "darkred")))
popViewport()

如上,xscale = c(0, 100), yscale = c(0, 10) 就重新将本来的0-1 范围,调整成了上述信息范围,接着通过unit(20, "native") 重新调节grob 对象中的元素即可。

虽然但是啊,感觉没什么大用。

参考资料

[1]

4.5 The grid Package | Mastering Software Development in R (bookdown.org): https://bookdown.org/rdpeng/RProgDA/the-grid-package.html#overview-of-grid-graphics

[2]

R实战:grid包 - 悦光阴 - 博客园 (cnblogs.com): https://www.cnblogs.com/ljhdo/p/4874785.html

[3]

(11条消息) R语言grid包使用笔记——viewport_数据之美-CSDN博客_r语言grid包: https://blog.csdn.net/vivihe0/article/details/47188329

[4]

(11条消息) R语言ggmap包的安装及使用_Pylady的博客-CSDN博客_ggmap怎么用: https://blog.csdn.net/Pylady/article/details/86480104

[5]

Google Maps Platform | Google Developers: https://developers.google.cn/maps/documentation/

[6]

R实战:grid包 - 悦光阴 - 博客园 (cnblogs.com): https://www.cnblogs.com/ljhdo/p/4874785.html

0 人点赞