4 Manipulating Plots with grid
Okay, so now we have seen how to produce a variety of widely used plot types using both lattice and ggplot2. We hope that, apart from the specifics, you also obtained a general idea of how these two packages work and how you may use the various things we’ve touched upon in scenarios other than the ones provided here.
Now, we’re moving on to a more basic and much more flexible level of modifying, constructing and arranging graphs. Both lattice and ggplot2 are based on the grid package. This means that we can use this package to fundamentally modify whatever we’ve produced (remember, we’re always storing our plots in objects) in a much more flexible way than provided by any of these packages.
With his grid package, Paul Murrell has achieved nothing less than a highly sophisticated and, in our opinion, much more flexible and powerful plotting framework for R. This has also been ‘officially’ recognized by the R Core Team as Paul is now a member of the very same (at least to our knowledge) and his grid package is shipped with the base version of R. This means that we don’t have to install the package anymore (however, we still have to load it via library(grid)
).
In order to fully appreciate the possibilities of the grid package, it helps to think of this package as a package for drawing things. Yes, we’re not producing statistical plots as such (for this we have lattice and ggplot2), we’re actually drawing things!
The fundamental features of the grid package are the “viewports”. By default, the whole plotting area, may it be the standard R or any other such plotting device (e.g. png()
), is considered as the root viewport (basically like the home/<username>
folder on Linux or the C:\Users\<username>
folder on Windows). In this viewport we now have the possibility to specify other viewports which are relative to the root viewport (just like the Users\<username>\Documents
folder on Windows or home/<username>/Downloads
under Linux).
The very important thing to realize here is that in order to do anything in this folder (be it creating another sub-folder, or simply saving a file, or whatever), we first need to create the folder and then we need to navigate into it. If you keep this in mind, you will quickly understand the fundamental principle of grid.
When we start using the grid package, we always start with the ‘root’ viewport. This is already available, it is created for us, so we don’t need to do anything. This is our starting point. The really neat thing about grid is that each viewport is, by default, defined as ranging from 0 to 1 in both x and y direction. For example, the lower left corner is defined as x = 0
and y = 0
. Accordingly, the lower right corner is x = 1
and y = 0
, the upper right corner is x = 1
and y = 1
and so on…
It is, however, possible to specify a myriad of different unit systems (type ?grid::unit
to get an overview of what is available). We usually stick to the default settings called npc
(‘Normalised Parent Coordinates’) which range from 0 to 1 in each direction, as this makes setting up viewports very intuitive (as demonstrated above).
A viewport needs some basic specifications for it to be located somewhere in the plotting area (the current viewport). These are:
x
- location along the x-axisy
- location along the y -axiswidth
- width of the viewportheight
- height of the viewportjust
- justification of the viewport in both x and y directions
Here, width
and height
should be rather self-explanatory. On the other hand, x
, y
and just
are a bit more mind-bending. By default, x = y = 0.5
and just = cemtre
. This means that the new viewport will be positioned at x = 0.5
and y = 0.5
. The default of just
is centre
which means that a new viewport will be created at the midpoint of the current viewport (x, y
) and centered on this point. It helps to think of the just
specification in the same way that you provide your text justification in an Office software (left, right, centre & justified). Let’s try a first example which should highlight the way this works.
library(grid)
grid.newpage() # open a new (i.e. empty) 'root' viewport
grid.rect()
grid.text("this is the root vp", x = 0.5, y = 0.95,
just = c("centre", "top"))
our_first_vp <- viewport(x = 0.5, y = 0.5,
height = 0.5, width = 0.5,
just = c("centre", "centre"))
pushViewport(our_first_vp)
grid.rect(gp = gpar(fill = "pink"))
grid.text("this is our first vp", x = 0.5, y = 0.95,
just = c("centre", "top"))
Okay, so now we have created a new viewport in the middle of the current (i.e. ‘root’) viewport that is half the height and half the width of its root. Afterwards, we navigated into this new viewport using pushViewport()
and drew a pink-filled rectangle spanning the entire area using grid.rect()
.
Note that we didn’t leave the viewport yet. This means that, whatever we do now, will happen in the currently active viewport (i.e. the pink one). To illustrate this, we will simply repeat the exact same code from above once more.
our_first_vp <- viewport(x = 0.5, y = 0.5,
height = 0.5, width = 0.5,
just = c("centre", "centre"))
pushViewport(our_first_vp)
grid.rect(gp = gpar(fill = "cornflowerblue"))
In more practical terms, this means that whatever viewport we are currently in, this one defines our reference system (0 to 1). In case you don’t believe that, we can repeat this procedure five times more…
for (i in 1:5) {
our_first_vp <- viewport(x = 0.5, y = 0.5,
height = 0.5, width = 0.5,
just = c("centre", "centre"))
pushViewport(our_first_vp)
grid.circle(gp = gpar(fill = colors()[i*3]))
}
Now that should be proof enough! We are cascading down viewports always creating a rectangle that fills half the ‘mother’ viewport at each step. Yet, as the ‘mother’ viewport becomes smaller and smaller, our rectangles also become smaller along the way (programmers would actually call these steps iterations, but we won’t be bothered here…).
So, how do we navigate back? If we counted correctly, we went down 7 rabbit holes. In order to get out of these again, we need to use upViewport(7)
and, in order to verify that we are back in ‘root’, we may ask grid what viewport we are currently in.
upViewport(7)
current.viewport()
## viewport[ROOT]
Sweet, we’re back in the ‘root’ viewport… (by the way, upViewport(0)
would have taken us right up to the ‘root’ viewport without the requirement for counting, see ?grid::upViewport
).
Now, let’s see how this just
parameter works. As you have seen we are now in the ‘root’ viewport. Let’s try to draw another rectangle that sits right at the top left corner of the pink one. In theory the lower right corner of this viewport should be located at x = 0.25
and y = 0.75
. If we specify it like this, we need to adjust the justification, because we do not want to center it on these coordinates. If these coordinates are the point of origin, this viewport should be justified right horizontally and bottom vertically. And the space we have to plot should be 0.25
vertically and 0.25
horizontally. Let’s try this…
top_left_vp <- viewport(x = 0.25, y = 0.75,
height = 0.25, width = 0.25,
just = c("right", "bottom"))
pushViewport(top_left_vp)
grid.rect(gp = gpar(fill = "grey", alpha = 0.5))
You should have understood two things now:
- How to create and navigate between viewports, and
- Why it was said earlier that grid is a package for drawing.
Assuming that you have understood these two points, let’s make use of the first one and use this incredibly flexible plotting framework for arranging multiple plots on one page.