R graphics follows a “painters model”: when we draw a shape, it is drawn over the top of previous drawing. For example, the following code draws a circle followed by a rectangle. Both shapes have an opaque border, but a semitransparent fill, which allows us to see that the rectangle is drawn on top of the circle.
library(grid)
grid.circle(.6, .6, r=.2,
gp=gpar(col=4, lwd=5, fill=adjustcolor(4, alpha=.5)))
grid.rect(.4, .4, width=.4, height=.4,
gp=gpar(col=2, lwd=5, fill=adjustcolor(2, alpha=.5)))
R graphics output is sent to a graphics device either to draw to the screen, or to record the drawing in a file. For example, the postscript()
device records drawing in a PostScript file.
The painters model means that a graphics devices is only asked to draw one shape at a time, e.g., draw a circle then draw a rectangle. For example, the PostScript to draw a circle and a rectangle could look like the following. The important part of the code is the clear separation into two shapes, each starting with a newpath
and ending with a fill
(to fill the shape).
%%BoundingBox: 0 0 200 200
newpath
120 120 40 360 0 arcn
.13 .59 .90 setrgbcolor
fill
newpath
40 40 moveto
40 120 lineto
120 120 lineto
120 40 lineto
closepath
.87 .33 .42 setrgbcolor
fill
showpage
However, many graphics devices support a more sophisticated drawing model based on paths. In this model, a single path can be constructed from more than on shape. For example, the PostScript code below draws the same two shapes as before, but draws them within a single path (and then fills that single path).
%%BoundingBox: 0 0 200 200
newpath
120 120 40 360 0 arcn
40 40 moveto
40 120 lineto
120 120 lineto
120 40 lineto
closepath
.87 .33 .42 setrgbcolor
fill
showpage
The result in this case is a single shape that is the union of the two individual shapes. We can get even fancier results by controlling how “inside” of the path is interpreted. For example, the following PostScript code creates exactly the same single path from the two shapes, but this time runs eofill
(rather than fill
). That changes to an “even-odd” fill rule (from the default “non-zero winding” rule), so the overlap between the two shapes within the path is now “outside” the overall path and we get a shape with a hole in it.
%%BoundingBox: 0 0 200 200
newpath
120 120 40 360 0 arcn
40 40 moveto
40 120 lineto
120 120 lineto
120 40 lineto
closepath
.87 .33 .42 setrgbcolor
eofill
showpage
Another element of the more sophisticated path-drawing model is the ability to construct a path from Bezier curves. For example, the following PostScript code describes a path from a series of straight lines and Bezier curves.
%%BoundingBox: 0 0 200 200
newpath
40 40 moveto
40 120 lineto
80 120 80 120 80 160 curveto
160 160 lineto
160 80 lineto
120 80 120 80 120 40 curveto
closepath
.87 .33 .42 setrgbcolor
eofill
showpage
R provides functions to draw more complex shapes, that it calls “paths”. However, we will refer to these as R-paths because they are not as sophisticated as the paths available on some graphics devices.
The following code draws the R version of the combination of circle and rectangle with a hole where they overlap.
circle <- function(x, y, r) {
t <- seq(0, 2*pi, length.out=50)
list(x=x + r*cos(t), y=y + r*sin(t))
}
c <- circle(.6, .6, .2)
grid.path(c(c$x, .2, .2, .6, .6),
c(c$y, .2, .6, .6, .2),
id.lengths=c(50, 4), rule="evenodd",
gp=gpar(col=2, lwd=5, fill=adjustcolor(2, alpha=.5)))
Although this shows that some aspects of real paths are available in R graphics, there are some limitations:
We cannot construct the path from shapes; we have to generate a series of points along the outline of the path ourselves (e.g., the circle()
function above).
We cannot construct a path from curves. Even though it is possible to draw a stand-alone Bezier curve (e.g., with the ‘gridBezier’ package), we cannot build a path by adding a Bezier curve to other shapes. Furthermore, R graphics never asks a graphics device to draw a curve; it draws curves by drawing lots of short straight lines that approximate the curve.
The addition of clipping paths to the graphics engine actually creates situation where we can accidentally access some of the more sophisticated path functionality of some graphics devices.
Note that there are a couple of ways that we can generate a more sophisticated path by combining simple shapes in R graphics
grid::grobCoords()
function.