4.5 The grid Package
grid package in R implements the primitive graphical functions that underlie the
ggplot2 plotting system. While one typically does not interact directly with the
grid package (it is imported by the
ggplot2 package), it is necessary to understand some aspects of the
grid package in order to build new geoms and graphical elements for
ggplot2. In this section we will discuss key elements of the
grid package that can be used in extending
While the grid package can be used to produce graphical output directly, it is seldom used for that purpose. Rather, the grid package provides a set of functions and classes that represent graphical objects or grobs, that can be manipulated like any other R object. With grobs, we can manipulate graphical elements (“edit” them) using standard R functions.
4.5.1 Overview of grid graphics
Functions in the
ggplot2 package allow extensive customization of many plotting elements. For example, as described in previous sections, you can use
ggplot2 functions to change the theme of a plot (and you can also change specific elements of the theme for a plot), to customize the colors used within the plot, and to create faceted “small multiple” graphs. You can even include mathematical annotation on ggplot objects using
ggplot2 functions (see https://github.com/tidyverse/ggplot2/wiki/Plotmath for some examples) and change coordinate systems (for example, a pie chart can be created by using polar coordinates on a barchart geom).
Grid graphics is a low-level system for plotting within R and is as a separate system from base R graphics. By “low-level”, we mean that grid graphics functions are typically used to modify very specific elements of a plot, rather than being functions to use for a one-line plotting call. For example, if you want to quickly plot a scatterplot of data, you should use ggplot2, but if you want to create a plot with an inset plot with tilted axis labels, then you may need to go to grid graphics.
ggplot2 package is built on top of grid graphics, so the grid graphics system “plays well” with ggplot2 objects. In particular, ggplot objects can be added to larger plot output using grid graphics functions, and grid graphics functions can be used to add elements to ggplot objects. Grid graphics functions can also be used to create almost any imaginable plot from scratch. A few other graphics packages, including the
lattice package, are also built using grid graphics.
Since it does take more time and code to create plots using grid graphics compared to plotting with
ggplot2, it is usually only worth using grid graphics when you need to create a very unusual plot that cannot be created using
ggplot2. As people add more geoms and other extensions to ggplot, there are more capabilities for creating customized plots directly in ggplot2 without needing to use the lower-level functions from the
grid package. However, they are useful to learn because they provide you the tools to create your own extensions, including geoms.
The grid package is now a base package, which means it is installed automatically when you install R. This means you won’t have to install it using
install.packages() before you use it. You will, however, have to load the package with
library() when you want to use it in an R session.
Grid graphics and R’s base graphics are two separate systems. You cannot easily edit a plot created using base graphics with grid graphics functions. If you have to integrate output from these two systems, you may be able to using the
gridBase package, but it will not be as straightforward as editing an object build using grid graphics (including ggplot objects). While we have focused on plotting using ggplot2 in this course, we have covered a few plots created using base R, specifically the maps created by running a plot call on a spatial object, like a SpatialPoints object.
The most critical concept of grid graphics to understand for extending ggplot2 it the concept of grobs. Grobs are graphical objects that you can make and change with grid graphics functions. For example, you may create a circle grob or points grobs. Once you have created one or more of these grobs, you can add them to or take them away from larger grid graphics objects, including ggplot objects. These grobs are the actual objects that get printed to a graphics device when you print a grid graphics plot; if you tried to create a grid graphics plot without any grobs, you would get a blank plot.
The grid package has a Grob family of functions that either make or change grobs. If you want to build a custom geom for ggplot that is unusual enough that you cannot rely on inheriting from an existing geom, you will need to use functions from the Grob family of functions to code your geom. You can create a variety of different types of grobs to plot different elements. Possible grobs that can be created using functions in the
grid package include circles, rectangles, points, lines, polygons, curves, axes, rasters, segments, and plot frames. You can create grobs using the functions from the
*Grob family of functions in the
grid package; these functions include
Other packages, including the
gridExtra package, provide functions that can be used to create addition grobs beyond those provided by the
grid package. For example, the
gridExtra package includes a function called
tableGrob to create a table grob that can be added to grid graphics objects.
Functions that create grobs typically include parameters to specify the location where the grobs should be placed. For example, the
pointsGrob function includes
y parameters, while the segmentsGrob includes parameters for the starting and ending location of each segment (
The grob family of functions also includes a parameter called
gp for setting graphical parameters like color, fill, line type, line width, etc., for grob objects. The input to this function must be a
gpar object, which can be created using the
gpar function. For example, to create a gray circle grob, you could run:
library(grid) my_circle <- circleGrob(x = 0.5, y = 0.5, r = 0.5, gp = gpar(col = "gray", lty = 3))
Aesthetics that you can set by specifying a
gpar object for the
gp parameter of a grob include color (
col), fill (
fill), transparency (
alpha), line type (
lty), line width (
lwd), line end and join styles (
linejoin, respectively), and font elements (
fontfamily). See the helpfile for
gpar for more on
Once you have created a grob object, you can use the
grid.draw function to plot it to the current graphics device. For example, to plot the circle grob we just created, you could run:
In this case, the circle will fill up the full graphics device and will be centered in the middle of the plot region. Later in this subsection, we’ll explain how to use coordinates and coordinate systems to place a grob and how to use viewports to move into subregions of the plotting space.
You can edit a grob after you have drawn it using the
grid.edit function. For example, the following code creates a circle grob, draws it, creates and draws a rectangle grob, and then goes back and edits the circle grob within the plot region to change the line type and color (run this code one line at a time within your R session to see the changes). Note that the grob is assigned a name to allow referencing it with the
my_circle <- circleGrob(name = "my_circle", x = 0.5, y = 0.5, r = 0.5, gp = gpar(col = "gray", lty = 3)) grid.draw(my_circle) my_rect <- rectGrob(x = 0.5, y = 0.5, width = 0.8, height = 0.3) grid.draw(my_rect)
grid.edit("my_circle", gp = gpar(col = "red", lty = 1))
As mentioned earlier,
ggplot2 was built using the grid system, which means that ggplot objects often integrate well into grid graphics plots. In many ways, ggplot objects can be treated as grid graphics grobs. For example, you can use the
grid.draw function from
grid to write a ggplot object to the current graphics device:
wc_plot <- ggplot(worldcup, aes(x = Time, y = Passes)) + geom_point() grid.draw(wc_plot)
This functionality means that ggplot objects can be added to plots with other grobs. For example, once you have defined
wc_plot using the code above, try running the following code (clear your graphics device first using the broom icon in the RStudio Plots panel):
In this case, the resulting plot is not very useful, but this functionality will be more interesting once we introduce how to use viewports and coordinate systems.
You can also edit elements of a ggplot object using grid graphics functions. First, you will need to list out all the graphics elements in the ggplot object, so you can find the name of the one you want to change. Then you can use
grid.edit to edit that element of the plot.
To find the names of the elements in this ggplot object, first plot the object to RStudio’s graphics device (as done with the last call), then run
grid.ls() to find the name of the element you want to change, then use
grid.edit to change it. As a note, the exact names of elements will change each time you print out the plot, so you will need to write the
grid.edit call based on the
grid.ls results for a specific plotting of the ggplot object.
For example, you can run this call to print out the World Cup plot coded earlier and list the names of all elements:
wc_plot grid.force() grid.ls()
layout background.1-7-10-1 panel.6-4-6-4 grill.gTree.1413 panel.background..rect.1404 panel.grid.minor.y..polyline.1406 panel.grid.minor.x..polyline.1408 panel.grid.major.y..polyline.1410 panel.grid.major.x..polyline.1412 NULL geom_point.points.1400 NULL panel.border..zeroGrob.1401 spacer.7-5-7-5 spacer.7-3-7-3 spacer.5-5-5-5 spacer.5-3-5-3 axis-t.5-4-5-4 axis-l.6-3-6-3 axis.line.y..zeroGrob.1432 axis axis.1-1-1-1 GRID.text.1429 axis.1-2-1-2 axis-r.6-5-6-5 axis-b.7-4-7-4 axis.line.x..zeroGrob.1425 axis axis.1-1-1-1 axis.2-1-2-1 GRID.text.1422 xlab-t.4-4-4-4 xlab-b.8-4-8-4 GRID.text.1416 ylab-l.6-2-6-2 GRID.text.1419 ylab-r.6-6-6-6 subtitle.3-4-3-4 title.2-4-2-4 caption.9-4-9-4
Then, you can change the color of the points to red and the y-axis label to be bold by using
grid.edit on those elements (note that if you are running this code yourself, you will need to get the exact names from the
grid.ls output on your device):
grid.edit("geom_point.points.1400", gp = gpar(col = "red")) grid.edit("GRID.text.1419", gp = gpar(fontface = "bold"))
You can use the
ggplotGrob function from the
ggplot2 package to explicitly make a ggplot grob from a ggplot object.
gTree grob contains one or more “children” grobs. It can very useful for creating grobs that need to contain multiple elements, like a boxplot, which needs to include a rectangle, lines, and points, or a labeling grob that includes text surrounded by a rectangle. For example, to create a grob that looks like a lollipop, you can run:
candy <- circleGrob(r = 0.1, x = 0.5, y = 0.6) stick <- segmentsGrob(x0 = 0.5, x1 = 0.5, y0 = 0, y1 = 0.5) lollipop <- gTree(children = gList(candy, stick)) grid.draw(lollipop)
You can use the
grid.ls function to list all the children grobs in a
GRID.gTree.8254 GRID.circle.8252 GRID.segments.8253
In a later section, we show how to make your own geoms to add to ggplot starting from grid grobs. Bob Rudis has created a
geom_lollipop in his
ggalt package that allows you to create “lollipop charts” as an alternative to bar charts. For more, see the GitHub repository for
ggalt and his blog post on
Much of the power of grid graphics comes from the ability to move in and out of working spaces around the full graph area. As an example, say you would like to create a map of the states of the US with a small pie chart added at the centroid of each state showing the distribution of population in that state by education level. This kind of plot is where grid graphics shines (although it appears that you now can create such a plot directly in ggplot2). In this case, you want to zoom in at the coordinates of a state centroid, have your own smaller working space at that location, add a pie chart showing data specific to that state within that working space, then zoom out and do the process again for a different state centroid.
In grid graphics, these smaller working spaces within the larger plot are called viewports. Viewports are the plotting windows that you can move into and out of to customize plots using grid graphics. You can navigate to one of the viewports, make some changes, and then pop back up and navigate deeply into another viewport in the plot. In short, viewports provide a way to navigate around and work within different subspaces on a plot.
Using grid graphics, you will create plots by making viewports, navigating into them, writing grobs, and then moving to a different viewport to continue plotting.
To start, you can make a new viewport with the
viewport function. For example, to create a viewport in the top right quarter of the full plotting area and write a rounded rectangle and the
lollipop grob we defined earlier in this section (we’ve written a rectangle grob before creating a using the viewport, so you can see the area of the full plotting area), you can run:
grid.draw(rectGrob()) 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()
This code creates a viewport using the
viewport function, navigates into it using
pushViewport, writes the grobs using
grid.draw, and the navigates out of the viewport using
In this code, the
y parameters of the
viewport function specify the viewport’s location, and the
just parameter specifies how to justify the viewport in relation to this location. By default, these locations are specified based on a range of 0 to 1 on each side of the plotting area, so
x = 0.5 and
y = 0.5 specifies the center of the plotting area, while
just = c("left", "bottom") specifies that the viewport should have this location at its bottom left corner. If you wanted to place the viewport in the center of the plotting area, for example, you could run:
grid.draw(rectGrob()) sample_vp <- viewport(x = 0.5, y = 0.5, width = 0.5, height = 0.5, just = c("center", "center")) pushViewport(sample_vp) grid.draw(roundrectGrob()) grid.draw(lollipop) popViewport()
height parameters specify the size of the viewport, again based on default units that 1 is the full width of one side of the plotting area (later in this section, we discuss how to use different coordinate systems). For example, if you wanted to make the viewport smaller, you could run:
grid.draw(rectGrob()) sample_vp <- viewport(x = 0.75, y = 0.75, width = 0.25, height = 0.25, just = c("left", "bottom")) pushViewport(sample_vp) grid.draw(roundrectGrob()) grid.draw(lollipop) popViewport()
You can only operate in one viewport at a time. Once you are in that viewport, you can write grobs within the viewport. If you want to place the next grob in a different viewport, you will need to navigate out of that viewport before you can do so. Notice that all the previous code examples use
popViewport to navigate out of the viewport after writing the desired grobs. We could then create a new viewport somewhere else and write new grobs there:
grid.draw(rectGrob()) 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()
You can also nest viewports inside each other. In this case, a new viewport is defined relative to the current viewport. For example, if you are in a viewport and position a new viewport at
x = 0.5 and
y = 0.5, this viewport will be centered in your current viewport rather than in the overall plotting area.
grid.draw(rectGrob()) sample_vp_1 <- viewport(x = 0.5, y = 0.5, width = 0.5, height = 0.5, just = c("left", "bottom")) sample_vp_2 <- viewport(x = 0.1, y = 0.1, width = 0.4, height = 0.4, just = c("left", "bottom")) pushViewport(sample_vp_1) grid.draw(roundrectGrob(gp = gpar(col = "red"))) pushViewport(sample_vp_2) grid.draw(roundrectGrob()) grid.draw(lollipop) popViewport(2)
Note that in this code we use the call
popViewport(2) to navigate back to the main plotting area. This is because we have navigated down to a viewport within a viewport, so we need to pop up two levels to get out of the viewports.
Given this ability to nest viewports, a grid graphics object can end up with a complex tree of viewports and grobs. Any of these elements can be customized, as long as you can navigate back down to the specific element you want to change.
You can use the
grid.ls function to list all the elements of the plot in the current graphics device, if it was created using grid graphics.
grid.draw(rectGrob()) sample_vp_1 <- viewport(x = 0.5, y = 0.5, width = 0.5, height = 0.5, just = c("left", "bottom")) pushViewport(sample_vp_1) grid.draw(roundrectGrob()) grid.draw(lollipop) popViewport()
GRID.rect.8267 GRID.roundrect.8268 GRID.gTree.8254 GRID.circle.8252 GRID.segments.8253
For ggplot objects, you can also use
grid.ls, but you should first run
grid.force to set the grobs as they appear in the current graph (or as they will appear when you plot this specific ggplot object), so you can see child grobs in the listing:
worldcup %>% ggplot(aes(x = Time, y = Passes)) + geom_point() grid.force()
layout background.1-7-10-1 panel.6-4-6-4 grill.gTree.8283 panel.background..rect.8274 panel.grid.minor.y..polyline.8276 panel.grid.minor.x..polyline.8278 panel.grid.major.y..polyline.8280 panel.grid.major.x..polyline.8282 NULL geom_point.points.8270 NULL panel.border..zeroGrob.8271 spacer.7-5-7-5 spacer.7-3-7-3 spacer.5-5-5-5 spacer.5-3-5-3 axis-t.5-4-5-4 axis-l.6-3-6-3 axis.line.y..zeroGrob.8302 axis axis.1-1-1-1 GRID.text.8299 axis.1-2-1-2 axis-r.6-5-6-5 axis-b.7-4-7-4 axis.line.x..zeroGrob.8295 axis axis.1-1-1-1 axis.2-1-2-1 GRID.text.8292 xlab-t.4-4-4-4 xlab-b.8-4-8-4 GRID.text.8286 ylab-l.6-2-6-2 GRID.text.8289 ylab-r.6-6-6-6 subtitle.3-4-3-4 title.2-4-2-4 caption.9-4-9-4
You can use ggplot objects in plots with viewports. For example, you can use the following code to add an inset map for the map we created earlier in this section of Baltimore County and Baltimore City in Maryland. The following code creates ggplot objects with the main plot and the inset map and uses viewports to create a plot showing both. Note that in the viewport we’ve added two rectangle grobs, one in white with some transparency to provide the background of the map inset, and one with no fill and color set to black to provide the inset border.
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)
Warning: `panel.margin` is deprecated. Please use `panel.spacing` property instead
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()
4.5.4 Grid graphics coordinate systems
Once you have created a grob and moved into the viewport in which you want to plot it, you need a way to specify where in the viewport to write the grob. The numbers you use to specify x- and y-placements for a grob will depend on the coordinate system you use. In grid graphics, you have a variety of options for the units to use in this coordinate system, and picking the right units for this coordinate system will make it much easier to create the plot you want.
There are several units that can be used for coordinate systems, and you typically will use different units to place objects. For example, you may want to add points to a plot based on the current x- and y-scales in that plot region, in which case you can use native units. The native unit is often the most useful when creating extensions for ggplot2, for example. The npc units are also often useful in designing new plots– these set the x- and y-ranges to go from 0 to 1, so you can use these units if you need to place an object in, for example, the exact center of a viewport (
c(0.5, 0.5) in npc units), or create a viewport in the top right quarter of the plot region. Grid graphics also allows the use of some units with absolute values, including inches (
inches), centimeters (
cm), and millimeters (
You can specify the coordinate system you would like to use when placing an object by with the
unit function (
unit([numeric vector], units = "native")). For example, if you create a viewport with the x-scale going from 0 to 100 and the y-scale going from 0 to 10 (specified using
yscale in the
viewport function), you can use
native when drawing a grob in that viewport to base the grob position on these scale values.
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()
gridExtra package provides useful extensions to the grid system, with an emphasis on higher-level functions to work with grid graphic objects, rather than the lower-level utilities in the grid package that are used to create and edit specific lower-level elements of a plot. This package has particularly useful functions that allow you to arrange and write multiple grobs to a graphics device and to include tables in grid graphics objects.
grid.arrange function from the
gridExtra package makes it easy to create a plot with multiple grid objects plotted on it. For example, you can use it to write out one or more grobs you’ve created to a graphics device:
library(gridExtra) grid.arrange(lollipop, circleGrob(), rectGrob(), lollipop, ncol = 2)
Note that this code is being used to arrange both grobs that were previously created and saved to an R object (the
lollipop grob created in earlier code in this section) and grobs that are created within the
grid.arrange call (the grobs created with
ncol parameter is used to specify the number of columns to include in the output.
ggplot2 was built on grid graphics, you can also use this function to plot multiple ggplot objects to a graphics device. For example, say you wanted to create a plot that has two plots based on the World Cup data side-by-side. To create this plot, you can assign the ggplot objects for each separate graph to objects in your R global environment (
player_positions in this example), and then input these objects to a
time_vs_shots <- ggplot(worldcup, aes(x = Time, y = Shots)) + geom_point() player_positions <- ggplot(worldcup, aes(x = Position)) + geom_bar() grid.arrange(time_vs_shots, player_positions, ncol = 2)
You can use the
layout_matrix parameter to specify different layouts. For example, if you want the scatterplot to take up one-third of the plotting area and the bar chart to take up two-thirds, you could specify that with a matrix with one “1” (for the first plot) and two “2s”, all in one row:
grid.arrange(time_vs_shots, player_positions, layout_matrix = matrix(c(1, 2, 2), ncol = 3))
You can fill multiple rows in the plotting device. To leave a space blank, use
NA in the layout matrix at that positions. For example:
grid.arrange(time_vs_shots, player_positions, layout_matrix = matrix(c(1, NA, NA, NA, 2, 2), byrow = TRUE, ncol = 3))
gridExtra also has a function,
tableGrob, that facilitates in adding tables to grid graphic objects. For example, to add a table with the average time and shots for players on the top four teams in the 2010 World Cup, you can create a table grob using
tableGrob and then add it to a larger plot created using grid graphics:
worldcup_table <- worldcup %>% filter(Team %in% c("Germany", "Spain", "Netherlands", "Uruguay")) %>% group_by(Team) %>% dplyr::summarize(`Average time` = round(mean(Time), 1), `Average shots` = round(mean(Shots), 1)) %>% tableGrob() grid.draw(ggplotGrob(time_vs_shots)) wc_table_vp <- viewport(x = 0.22, y = 0.85, just = c("left", "top"), height = 0.1, width = 0.2) pushViewport(wc_table_vp) grid.draw(worldcup_table) popViewport()
These tables can be customized with different trend and can be modified by adding other grob elements (for example, to highlight certain cells). To find out more, see the vignette for table grobs.
4.5.6 Find out more about grid graphics
Grid graphics provide an extensive graphics system that can allow you to create almost any plot you can imagine in R. In this section, we have only scraped the surface of the grid graphics system, so you might want to study grid graphics in greater depth, especially if you often need to create very tailored, unusual graphs.
There are a number of resources you can use to learn more about grid graphics. The most comprehensive is the R Graphics book by Paul Murrell, the creator of grid graphics. This book is now in its second edition, and its first edition was written before ggplot2 became so popular. It is worth try to get the second edition, which includes some content specifically on ggplot2 and how that package relates to grid graphics. The vignettes that go along with the grid package are also by Paul Murrell and give a useful introduction to grid graphics, and the vignettes for the gridExtra package are also a useful next step for finding out more.
- Links to pdfs of vignettes for the grid graphics package are available at https://stat.ethz.ch/R-manual/R-devel/library/grid/doc/index.html
- Links to pdfs of vignettes for the gridGraphics package are available on the package’s CRAN page: https://cran.r-project.org/web/packages/gridExtra/index.html