前言
在前面一節(jié)中,我們主要介紹了如何使用 grid
來生成圖形輸出萄焦,以及圖形窗口的布局扇商。利用這些知識凤瘦,可以很容易地為圖形添加注釋,編寫一些簡單的繪圖函數(shù)
這一節(jié)案铺,我們將重點介紹如何使用 grid
函數(shù)來創(chuàng)建和操作圖形對象蔬芥。利用這些信息,可以交互式地編輯和修改圖形輸出
圖形對象
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 保留了一個顯示列表,用于記錄當前畫布中的所有的 viewport
和 grobs
乎婿。因此测僵,grid.circle()
函數(shù)構造的對象也會保存在顯示列表中,意味著我們可以根據(jù)對象的名稱 circles
來獲取谢翎、修改該對象
使用 grid.get()
函數(shù)捍靠,可以獲取該 circle
對象的拷貝
> grid.get("circles")
circle[circles]
使用 grid.edit()
可以用來修改該 circle
對象的圖像屬性
grid.edit(
"circles",
gp = gpar(
col = brewer.pal(10, "RdBu")
)
)
修改顏色屬性之后,會直接顯示在圖形輸出中
還可以使用 grid.remove()
函數(shù)森逮,從顯示列表中刪除圖形對象的輸出
grid.remove("circles")
一片空白榨婆,什么也沒有
1.1 標準的函數(shù)及參數(shù)
控制 grobs
的函數(shù)包括:
所有圖像輸出函數(shù)的第一個參數(shù)都是圖像對象的名稱,如果參數(shù) 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
個同心圓闷供,并根據(jù)奇偶順序將 circle grob
命名為 circle.odd
和 circle.even
然后拖吼,我們可以使用 grid.edit()
函數(shù),修改所有名為 circle.odd
的 grobs
的顏色
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()
函數(shù)唾糯,可以幫助我們獲取當前圖形中所有 grobs
的名稱
2. grob 排布結構
grob
的排布結果包括:
-
gList
:包含多個grobs
的list
-
gTree
:grobs
的樹形結構怠硼,即一個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
舟误。
其中 major
和 ticks
是 lines grob
葡秒,labels
為 text grob
。
其中 at
參數(shù)設置了軸刻度嵌溢,我們可以使用 grid.edit
來修改
grid.edit("axis1", at=1:3/4)
那想要修改 labels
的格式眯牧,怎么辦?即如何訪問一個對象的子對象呢赖草?
可以使用 gPath(grob path)
函數(shù)学少,類似于 vpPath
,可以使用父節(jié)點名稱加子節(jié)點名稱來訪問
grid.edit(gPath("axis1", "labels"), rot=45)
或者秧骑,也可以使用 axis1::labels
方式來訪問
注意:grobs
的搜索是深度優(yōu)先版确,也就是說扣囊,如果在顯示列表中遍歷到了一個 gTree grob
,且未找到匹配項绒疗,則會對該 grob
執(zhí)行深度優(yōu)先遍歷
這種 gTree
結構對象侵歇,也包含 gp
和 vp
參數(shù)。
在父節(jié)點上設置對應的 gp
參數(shù)值吓蘑,會作為默認值傳遞給子對象盒至。例如
grid.xaxis(gp=gpar(col="grey"))
也可以將一個 viewport
直接作為參數(shù)值傳遞
grid.xaxis(vp=viewport(y=0.75, height=0.5))
3. 圖形對象
在前面的章節(jié)中,我們介紹的都是如何使用函數(shù)直接生成圖形輸出并返回圖形對象(grob
)
在這一節(jié)士修,我們將介紹如果創(chuàng)建 grob
枷遂,但不繪制圖形,通過對 grob
創(chuàng)建及修改棋嘲,并在最后使用 grid.draw()
函數(shù)來繪制出圖形酒唉。
每個能產生圖形輸出和圖形對象的 grid
函數(shù)都有一個對應的只創(chuàng)建圖形對象,沒有圖形輸出的函數(shù)
例如沸移,grid.circle()
對應于 circleGrob()
痪伦,grid.edit()
對應于 editGrob()
,在前面的函數(shù)表中都有列出
例如
grid.newpage()
pushViewport(viewport(width = 0.5, height = 0.5))
# 創(chuàng)建 x 軸對象
ag <- xaxisGrob(at=1:4/5)
# 修改對象雹锣,將標簽的字體變?yōu)樾斌w
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)
)
我們構建一個名為 boxedText
的 gTree
對象蕊爵,包含其子對象包括一個文本和一個矩形
我們直接可以繪制組合對象
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()
函數(shù)饲齐,可以獲取當前畫布中所有輸出的圖形對象,并以 gTree
的形式返回
例如咧最,我們使用 ggplot2
繪制一個直方圖捂人,并獲取所有圖形對象
ggplot(mpg) + geom_histogram(aes(displ, fill = class), bins = 10, position = "dodge")
histTree <- grid.grab()
然后,你可以嘗試運行下面的代碼
grid.newpage()
grid.draw(histTree)
你會發(fā)現(xiàn)矢沿,可以繪制出一張一模一樣的圖
也可以使用 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")
)
)
我們可以使用上一章節(jié)提到的布局方法论熙,將該圖像設計為 1
行 2
列的布局
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
提供了更簡便的函數(shù)用于放置 grobs
grid.frame()
函數(shù)創(chuàng)建一個沒有子對象的 gTree
摄狱,可以使用 grid.pack()
向其中添加子對象脓诡,同時確保為每個子對象保留足夠的繪圖空間
上面的代碼可以改寫成
grid.newpage()
# 新建一個空 frame
grid.frame(name="frame1")
# 放置 gplot 對象,在這一階段媒役,gplot 會占據(jù)整個 frame
grid.pack("frame1", gplot)
# 在 frame 的右邊放置 label 對象
grid.pack("frame1", label, side="right")
這種動態(tài)的方式很簡便祝谚,但是也帶來了時間上的花費,隨著需要放置的對象越來越多酣衷,速度會越來越慢交惯。
另一種替代的方式是,先定義一個布局穿仪,然后再放置對象
grid.frame(name="frame1", layout=layout)
grid.place("frame1", gplot, col=1)
grid.place("frame1", label, col=2)
4.1 安靜模式
在上面兩個例子中席爽,每次放置一個 grob
都會更新一遍圖形輸出。所以啊片,一個更好的方式是只锻,在安靜模式下創(chuàng)建一個 frame
,然后放置 grobs
紫谷。
安靜模式齐饮,即使用對象函數(shù) frameGrob()
和 placeGrob()/packGrob
創(chuàng)建 frame
、放置 grobs
笤昨,但是不會輸出圖形祖驱,只有在所有設置完成之后,使用 grid.draw
一次性繪制
# 創(chuàng)建 frame
fg <- frameGrob(layout=layout)
# 添加 grob
fg <- placeGrob(fg, gplot, col=1)
fg <- placeGrob(fg, label, col=2)
# 一次性繪制
grid.draw(fg)