在先前的内容中[[101-R可视化29-底层绘图系统grid学习之使用grid作图]],我们说过,如果可以结合grid 与ggplot 绘图就好了:一方面,通过ggplot 绘图的高级语法,可以省去许多绘图中复杂的代码设置;另一方面,通过grid 底层的调用,我们也可以实现更加灵活的图层设置。
这里有两个思路。
既然ggplot 本质也是grid,那我把ggplot 拆成最底层,再慢慢处理,不也是可以的吗?
比如这里我心血来潮,想要在本来的ggplot 图形侧边放一个的棒棒糖图:
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))
其实这里都不要转换ggplot 对象,直接在原有图层上叠加即可:
p1 <- ggplot(iris) + geom_point(
aes(Sepal.Length, Sepal.Width)
) + theme_void()
p1
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))
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(lollipop)
# popViewport()
但如果,我们想要在md_inset 视图下,结合ggplot 对象呢?
就需要我们将其拆解成grid 对象了:
p1 <- ggplot(iris) + geom_point(
aes(Sepal.Length, Sepal.Width)
) + theme_void()
p2 <- ggplot(iris) + geom_bar(
aes(Species)
)
p3 <- ggplotGrob(p1)
p2
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(p1))
而这个转换后的ggplot 对象,我已经认不得了:
> names(p3)
[1] "grobs" "layout" "widths" "heights" "respect"
[6] "rownames" "colnames" "name" "gp" "vp"
[11] "children" "childrenOrder"
> class(p3)
[1] "gtable" "gTree" "grob" "gDesc"
如果我们想要进入指定的ggplot 相关的图层绘图,那就需要更加暴力的手段,grid.force
,将ggplot 的视图可以获取使用。接着通过grid.grep
获得名称,以进入对应viewport。
上面的例子中,当我们想要实现两个ggplot 结果的叠加显示时,使用的方法是,将被叠加的ggplot 对象转为grid,从而实现视图上的控制:
我们也可以在打印时声明ggplot 不创建newpage,从而实现类似的效果。
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")))
print(p1, newpage = F)
其实哈德雷也非常有远见的提供了这个方法annotation_custom
:
★This is a special geom intended for use as static annotations that are the same in every panel. These annotations will not affect scales (i.e. the x and y axes will not grow to cover the range of the grob, and the grob will not be modified by any ggplot settings or mappings). ”
参数如下:
使用起来也非常简单,以上面的为例:
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))
rect1 <- rectGrob(gp = gpar(alpha = 0.5, col = "white"))
rect2 <- rectGrob(gp = gpar(fill = NA, col = "black"))
rect_tree <- gTree(children = gList(rect1, rect2))
p2 + annotation_custom(rect_tree,
xmin=0,xmax=1.5,ymin=0,ymax=10) +
annotation_custom(lollipop,
xmin=0,xmax=1.5,ymin=0,ymax=10)
也不难发现这个方法的缺点,一个是对于分类数据的位置设定,即使不是分类数据,其位置也是按照坐标轴来确定,而非一个grid 舒舒服服的units 系统。
即使我们直接对grob 对象设置:
rect1 <- rectGrob(gp = gpar(alpha = 0.5, col = "white"),
x = 0, y = 0,
just = c("left", "bottom"),
width = 0.35, height = 0.35)
rect2 <- rectGrob(gp = gpar(fill = NA, col = "black"),
x = 0, y = 0,
just = c("left", "bottom"),
width = 0.35, height = 0.35)
rect_tree <- gTree(children = gList(rect1, rect2))
p2 + annotation_custom(rect_tree)
反正我挺不喜欢的。
gggrid 也就是R 绘图系统作者Paul Murrel 写的用于grid 融入ggplot 体系的R 包,其一共只有两个函数:
grid_panel()
grid_group()
p2 + gggrid::grid_panel(rect_tree)
这不是跟annotation_custom 差不多吗?
虽然grid_panel 也限定在了坐标轴的范围内,但其厉害之处在于可以接受函数作为grob 输入。
rectFun <- function(data,coords){
left=min(coords$x)
bottom=min(coords$y)
width=diff(range(coords$x))
height=diff(range(coords$y))
rectGrob(left,bottom,width,height,
just=c("left","bottom"),
gp=gpar(fill=NA,lwd=1))
}
ggplot(mtcars,aes(disp,mpg))+
geom_point()+
grid_panel(rectFun)
比如我们可以根据对应坐标的结果进行绘图:
有意思的是,在[[106-R可视化30-底层绘图系统grid学习之重头创建ggplot对象1]]中,coords 并不是直接获取的:
coords <- coord$transform(data, panel_scales)
原来文档早已说明:
`grob`:Either a grid grob or a function. The function must accept two arguments (`data` and `coords`) and must return a grid grob.
我们还可以通过函数的调试,以了解ggplot 的值:
debugHead <- function(data,coords){
print(head(data))
print(head(coords))
}
ggplot(mtcars,aes(disp,mpg))+
geom_point()+
grid_panel(debug=debugHead)
x y PANEL group xmin xmax
1 160 21.0 1 -1 -Inf -Inf
2 160 21.0 1 -1 -Inf -Inf
3 108 22.8 1 -1 -Inf -Inf
4 258 21.4 1 -1 -Inf -Inf
5 360 18.7 1 -1 -Inf -Inf
6 225 18.1 1 -1 -Inf -Inf
x y PANEL group xmin xmax
1 0.2470464 0.4555126 1 -1 0 0
2 0.2470464 0.4555126 1 -1 0 0
3 0.1291299 0.5251451 1 -1 0 0
4 0.4692737 0.4709865 1 -1 0 0
5 0.7005714 0.3665377 1 -1 0 0
6 0.3944421 0.3433269 1 -1 0 0
其实这个总结真不好写。
如果是更加自由地使用,还是选择拆成grob 再各自处理的方案。
但如果你的grid 使用并不熟练,且需要的功能并不复杂,那么直接打印,也不失为一个对策。
而如果对于ggplot 的元素有所互动,比如通过其坐标判断以增加额外元素,那么gggrid 包,必然是不二选择了。
比如:
rectFun <- function(data,coords){
left=min(coords$x)
bottom=min(coords$y)
width=diff(range(coords$x))
height=diff(range(coords$y))
rectGrob(left,bottom,width,height,
just=c("left","bottom"),
gp=gpar(fill=NA,lwd=1))
}
ggplot(mtcars,aes(disp,mpg))+
geom_point()+
grid_panel(rectFun)
就有个更成熟的R包 ggforce
可以替代。
它的底层代码,也是依靠gggrid吗?还是自己从[[106-R可视化30-底层绘图系统grid学习之重头创建ggplot对象1]] 这样更加底层的方式实现的吗?
其实无非就是获得coords 的结果,再结合[[110-R可视化34-通过seurat包中的LabelClusters给散点图中心添加文本]] 的一些思路实现的吧。
此外,如果是简单的绘图,其实利用图层的叠加关系,也可以实现,并不需要你有任何的grid 基础。
比如[[87-R可视化19-利用其他图层映射自由的控制背景的颜色]]:
随着R 包越来越多,我们还需不需要底层呢?甚至如此之多的shiny 可视化工具,我们还需不需要编程呢?
[1]
【R>>>gggrid】ggplot2中实现grid功能 - 简书 (jianshu.com): https://www.jianshu.com/p/eb5a2f7299ff