egg/ 0000755 0001762 0000144 00000000000 13512271573 011022 5 ustar ligges users egg/inst/ 0000755 0001762 0000144 00000000000 13512266054 011775 5 ustar ligges users egg/inst/NEWS.md 0000644 0001762 0000144 00000002221 13512262111 013056 0 ustar ligges users # egg 0.5 (2019-07-13) ## NEW FEATURES * added `theme_presentation` * improved the settings in `theme_article` * added `tag_facet_outside` to tag facet rows and columns ## BUG FIX * corrected logic in `ggarrange` which was failing for non-trivial layouts with more than 4 plots * News file was referring to gridExtra * `symmetrise_scale` no longer worked (again) due to internal changes in ggplot2; this functionality is not achieved through `symmetric_range` passed as limits in the scale # egg 0.4.2 (2018-11-02) ## NEW FEATURES * added theme_article # egg 0.4.1 (2018-07-23) ## NEW FEATURES * added tag_facet ## BUG FIX * fixed aspect ratio for facetted plots was causing a problem in ggarrange; it now works again but the aspect ratio is not fully respected, as space between panels is ignored in the calculation # egg 0.4.0 (2018-06-18) ## NEW FEATURES * added geom_custom * ggarrange gains labels ## CLEANUP * improved vignettes ## BUG FIX * fixed aspect ratio somewhat better respected for facetted plots in ggarrange (not perfect, as space between panels is ignored in the calculation) # egg 0.2.0 (2017-09-12) * initial CRAN release egg/inst/doc/ 0000755 0001762 0000144 00000000000 13512266054 012542 5 ustar ligges users egg/inst/doc/Ecosystem.R 0000644 0001762 0000144 00000014701 13512266043 014641 0 ustar ligges users ## ----setup, echo=FALSE, results='hide', message=FALSE-------------------- library(knitr) library(gridExtra) library(egg) library(gtable) library(grid) opts_chunk$set( message = FALSE, fig.width = 6, fig.height = 3, cache = FALSE ) ## ----basic, echo=2:6, fig.height=5, fig.cap="A few plots that we want to organise on a page."---- set.seed(123) library(ggplot2) p1 <- qplot(mpg, wt, data = mtcars, colour = cyl) p2 <- qplot(mpg, data = mtcars) + ggtitle("title") p3 <- qplot(mpg, data = mtcars, geom = "dotplot") p4 <- p1 + facet_wrap( ~ carb, nrow = 1) + theme(legend.position = "none") + ggtitle("facetted plot") library(gridExtra) grid.arrange(p1, p2, p3, p4, layout_matrix = rbind(c(1, 2, 3), c(4, 4, 4)), widths = c(1.2, 1, 1)) ## ----arrange1, echo=TRUE, fig.height=3, fig.cap="Basic usage of `grid.arrange()`"---- grid.arrange(p1, p2, nrow = 1) ## ----arrange2, echo=-c(1:2), fig.width=4, fig.height=3, fig.cap="Illustrating further arguments of `grid.arrange()`, namely `layout_matrix` and relative widths."---- cols <- c( "#FBB4AE", "#B3CDE3", "#CCEBC5", "#DECBE4", "#FED9A6", "#FFFFCC", "#E5D8BD", "#FDDAEC" ) gl <- lapply(1:4, function(x) grobTree(rectGrob(gp = gpar(fill = cols[x])), textGrob(paste("plot", x)))) grid.arrange( grobs = gl, widths = c(2, 1, 1), layout_matrix = rbind(c(1, 2, NA), c(3, 3, 4)) ) ## ----inset, fig.width=4, fig.height=3, fig.cap="Plot inset."------------- g <- ggplotGrob(qplot(1, 1) + theme(plot.background = element_rect(colour = "black"))) qplot(1:10, 1:10) + annotation_custom( grob = g, xmin = 1, xmax = 5, ymin = 5, ymax = 10 ) + annotation_custom( grob = rectGrob(gp = gpar(fill = "white")), xmin = 7.5, xmax = Inf, ymin = -Inf, ymax = 5 ) ## ----plotstructure, echo=FALSE, fig.width=6, fig.height=3, fig.cap="Colour-coded structure of examplar ggplot layouts. Note how the panels (red) vary in size from plot to plot, as they accommodate the other graphical components."---- pl <- lapply(list(p1, p2, p3, p4), expose_layout, FALSE, FALSE) layouts <- arrangeGrob( grobs = pl, widths = c(1.2, 1, 1), layout_matrix = rbind(c(1, 2, 3), c(4, 4, 4)) ) ids <- c("background", "panel", "axis", "lab", "guide", "strip", "title") cols <- c( "grey95", "#FBB4AE", "#B3CDE3", "#CCEBC5", "#DECBE4", "#FED9A6", "#FFFFCC", "#E5D8BD", "#FDDAEC" ) leg <- lapply(ids, textGrob, hjust = 0, x = 0.1) legend <- gtable_matrix( "legend", matrix(leg, ncol = 1), widths = 1.2 * grobWidth(leg[[1]]), heights = unit(rep(1, length(leg)), "line") ) legend <- gtable_add_cols(legend, unit(1, "line"), 0) legend <- gtable_add_grob(legend, t = seq_along(leg), l = 1, lapply(cols[seq_along(ids)], function(x) rectGrob(gp = gpar(fill = x, col = NA)))) grid.arrange(layouts, legend, widths = unit.c(unit(1, "null"), 1.2 * sum(legend$widths))) ## ----rbind, fig.height=4, fig.width=4, fig.cap="Aligning plot panels. Note that the two y axes have different widths."---- library(gtable) g2 <- ggplotGrob(p2) g3 <- ggplotGrob(p3) g <- rbind(g2, g3, size = "first") g$widths <- unit.pmax(g2$widths, g3$widths) grid.newpage() grid.draw(g) ## ----egg, fig.height=3, fig.width=6-------------------------------------- p1 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point()+ theme_article() + theme(legend.position = 'top') p2 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point() + facet_wrap(~ cyl, ncol = 2, scales = "free") + guides(colour = "none") + theme_article() ggarrange(p1, p2, widths = c(1.5,2)) ## ----titles-------------------------------------------------------------- grid.arrange( p3, p3, p3, nrow = 1, top = "Title of the page", bottom = textGrob( "this footnote is right-justified", gp = gpar(fontface = 3, fontsize = 9), hjust = 1, x = 1 ) ) ## ----legend, echo=TRUE, fig.height=4------------------------------------- grid_arrange_shared_legend <- function(..., ncol = length(list(...)), nrow = 1, position = c("bottom", "right")) { plots <- list(...) position <- match.arg(position) g <- ggplotGrob(plots[[1]] + theme(legend.position = position))$grobs legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]] lheight <- sum(legend$height) lwidth <- sum(legend$width) gl <- lapply(plots, function(x) x + theme(legend.position = "none")) gl <- c(gl, ncol = ncol, nrow = nrow) combined <- switch( position, "bottom" = arrangeGrob( do.call(arrangeGrob, gl), legend, ncol = 1, heights = unit.c(unit(1, "npc") - lheight, lheight) ), "right" = arrangeGrob( do.call(arrangeGrob, gl), legend, ncol = 2, widths = unit.c(unit(1, "npc") - lwidth, lwidth) ) ) grid.newpage() grid.draw(combined) # return gtable invisibly invisible(combined) } grid_arrange_shared_legend(p1, p2) ## ----tables-------------------------------------------------------------- grid.arrange( tableGrob(mtcars[1:4, 1:4]), p2, ncol = 2, widths = c(1.5, 1), clip = FALSE ) ## ----comparison, echo=FALSE, message=FALSE, warnings=FALSE, results='asis'---- tabl <- " | Package | Function(s) | ggsave compat. | alignment | |-------------------|:-------------:|:------:|:----------:| | grid | `viewport`, `grid.layout` | no | no | | [gridExtra][1] | `grid.arrange` | yes | no | | [(r cookbook)][2] | `multiplot` | no | no | | [gtable][3] | `rbind`, `cbind`| yes | yes | | [cowplot][4] | `plot_grid` | yes* | yes* | | [multipanelfigure][5] | `multi_panel_figure` | yes | yes | | [egg][6] | `ggarrange` | yes | yes | | [patchwork][7] | `plot_layout` | yes | yes | " cat(tabl) ## ----comp---------------------------------------------------------------- pushViewport(viewport(layout = grid.layout(2, 2))) vplayout <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y) print(p1, vp = vplayout(1, 1:2)) print(p2, vp = vplayout(2, 1)) print(p3, vp = vplayout(2, 2)) egg/inst/doc/Overview.R 0000644 0001762 0000144 00000013074 13512266054 014500 0 ustar ligges users ## ----setup, echo=FALSE, results='hide', message=FALSE-------------------- library(egg) library(grid) library(gridExtra) library(gtable) library(knitr) opts_chunk$set(message = FALSE, fig.width = 7, fig.height = 3) ## ----layout-------------------------------------------------------------- p1 <- qplot(mpg, wt, data = mtcars, colour = cyl) p2 <- qplot(mpg, data = mtcars) + ggtitle("title") p3 <- qplot(mpg, data = mtcars, geom = "dotplot") p4 <- p1 + facet_wrap( ~ carb, nrow = 1) + theme(legend.position = "none") + ggtitle("facetted plot") pl <- lapply(list(p1, p2, p3, p4), expose_layout, FALSE, FALSE) grid.arrange( grobs = pl, widths = c(1.2, 1, 1), layout_matrix = rbind(c(1, 2, 3), c(4, 4, 4)) ) ## ----panel, fig.height=3.5----------------------------------------------- p1 <- qplot(mpg, wt, data = mtcars, colour = cyl) p2 <- p1 + facet_wrap( ~ carb, nrow = 1) grid.arrange(grobs = lapply( list(p1, p2), set_panel_size, width = unit(2, "cm"), height = unit(1, "in") )) ## ----frame--------------------------------------------------------------- p1 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point() p2 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point() + facet_wrap(~ cyl, ncol = 2, scales = "free") + guides(colour = "none") + theme() p3 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point() + facet_grid(. ~ cyl, scales = "free") g1 <- ggplotGrob(p1) g2 <- ggplotGrob(p2) g3 <- ggplotGrob(p3) fg1 <- gtable_frame(g1, debug = TRUE) fg2 <- gtable_frame(g2, debug = TRUE) fg12 <- gtable_frame(gtable_rbind(fg1, fg2), width = unit(2, "null"), height = unit(1, "null")) fg3 <- gtable_frame( g3, width = unit(1, "null"), height = unit(1, "null"), debug = TRUE ) grid.newpage() combined <- gtable_cbind(fg12, fg3) grid.draw(combined) ## ----ggarrange----------------------------------------------------------- p1 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point()+ theme_article() + theme(legend.position = 'top') p2 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point() + facet_wrap(~ cyl, ncol = 2, scales = "free") + guides(colour = "none") + theme_article() ggarrange(p1, p2, widths = c(1.5,2)) ## ----ggarrangelayout----------------------------------------------------- p <- ggplot() ggarrange(p, p, p, widths = c(3, 1), heights = c(5, 1)) ## ----ggarrangelabels----------------------------------------------------- ggarrange(p1, p2, p3, ncol=2, labels = c("A", "b)", "iii."), label.args = list(gp=gpar(font=4), x=unit(1,"line"), hjust=0)) ## ----tagfacet------------------------------------------------------------ d = data.frame( x = 1:90, y = rnorm(90), red = rep(letters[1:3], 30), blue = c(rep(1, 30), rep(2, 30), rep(3, 30))) p <- ggplot(d) + geom_point(aes(x = x, y = y)) + facet_grid(red ~ blue) tag_facet(p) tag_facet_outside(p) ## ----themes-------------------------------------------------------------- d = data.frame( x = 1:90, y = rnorm(90), red = rep(letters[1:3], 30), blue = c(rep(1, 30), rep(2, 30), rep(3, 30))) p <- ggplot(d) + geom_point(aes(x = x, y = y)) + facet_grid(red ~ blue) p + theme_article() ## ----symmetrise---------------------------------------------------------- df = data.frame(x = c(1, 2), y = c(5, 0.2), group = c(1, 2)) p <- ggplot(df, aes(x = x, y = y)) + geom_point() + facet_wrap( ~ group, scale = "free") p + scale_y_continuous(limits = symmetric_range) ## ----custompics---------------------------------------------------------- codes <- data.frame(country = c("nz","ca","ar","fr","gb","es")) codes$y <- runif(nrow(codes)) gl <- lapply(codes$country, function(.x) png::readPNG(system.file("flags", paste0(.x,".png"), package="egg"))) codes$raster <- I(gl) ggplot(codes, aes(x = country, y = y)) + geom_point() + geom_custom(data = codes, aes(data=raster), grob_fun = rasterGrob, fun_params = list(height=unit(1,"cm"))) + scale_y_continuous(breaks=NULL, "") + theme(panel.grid = element_blank()) ## ----customgrobs--------------------------------------------------------- codes$raster <- I(lapply(codes$raster, function(x) rasterGrob(x, height=unit(1,"cm")))) ggplot(codes, aes(x = country, y = y)) + geom_point() + geom_custom(data = codes, aes(data=raster), grob_fun = identity) ## ----customgrobcoord----------------------------------------------------- custom_grob <- function(data, x=0.5,y=0.5){ grob(data=data,x=x,y=y, cl="custom") } preDrawDetails.custom <- function(x){ pushViewport(viewport(x=x$x,y=x$y)) } postDrawDetails.custom <- function(x){ upViewport() } drawDetails.custom <- function(x, recording=FALSE, ...){ grid.rect(mean(x$data$x), mean(x$data$y), width=diff(range(x$data$x)), height=diff(range(x$data$y))) grid.lines(x$data$x, x$data$y, gp=gpar(col=x$data$col,lwd=2), default.units = "native") } d <- data.frame(x=rep(1:3, 4), f=rep(letters[1:4], each=3)) gl <- lapply(1:4, function(ii){ data.frame(x=seq(0.4,0.6,length=10), y = runif(10,0.45,0.55), col = hcl(h = seq(0,300,length=nrow(d)))[ii], stringsAsFactors = FALSE) }) subplots <- data.frame(f=letters[1:4], data = I(gl)) str(subplots) ggplot(d, aes(f,x)) + facet_wrap(~f, nrow=1)+ coord_polar() + geom_point()+ geom_custom(data = subplots, aes(data = data, x = f, y = 2), grob_fun = custom_grob) egg/inst/doc/Ecosystem.html 0000644 0001762 0000144 00001611470 13512266044 015414 0 ustar ligges users
An individual ggplot object contains multiple pieces – axes, plot panel(s), titles, legends –, and their layout is defined and enforced via the gtable
package, itself built around the lower-level grid
package. Plots themselves become graphical objects, which can be arranged on a page using e.g. the gridExtra
or egg
packages, which provide helper functions for such multi-object layouts. The following schematic illustrates the main relations between these packages.
Schematic illustration of the links between packages ggplot2
, gtable
, grid
, egg
and gridExtra
.
To begin, we’ll create four example plots that we can experiment with.
library(ggplot2)
p1 <- qplot(mpg, wt, data = mtcars, colour = cyl)
p2 <- qplot(mpg, data = mtcars) + ggtitle("title")
p3 <- qplot(mpg, data = mtcars, geom = "dotplot")
p4 <-
p1 + facet_wrap( ~ carb, nrow = 1) + theme(legend.position = "none") +
ggtitle("facetted plot")
A few plots that we want to organise on a page.
The easiest approach to assemble multiple plots on a page is to use the grid.arrange()
function from the gridExtra
package; in fact, that’s what we used for the previous figure. With grid.arrange()
, one can reproduce the behaviour of the base functions par(mfrow=c(r,c))
, specifying either the number of rows or columns,
Basic usage of grid.arrange()
If layout parameters are ommitted altogether, grid.arrange()
will calculate a default number of rows and columns to organise the plots.
More complex layouts can be achieved by passing specific dimensions (widths or heights), or a layout matrix defining the position of each plot in a rectangular grid. For the sake of clarity, we’ll use a list gl
of dummy rectangles, but the process is identical for plots.
Illustrating further arguments of grid.arrange()
, namely layout_matrix
and relative widths.
Further examples are available in a dedicated gridExtra
vignette.
A special case of layouts is where one of the plots is to be placed within another, typically as an inset of the plot panel. In this case, grid.arrange()
cannot help, as it only provides rectangular layouts with non-overlapping cells. Instead, a simple solution is to convert the plot into a grob, and place it using annotation_custom()
within the plot panel. Note the related geom_custom()
function, suitable when different facets should display different annotations.
g <- ggplotGrob(qplot(1, 1) +
theme(plot.background = element_rect(colour = "black")))
qplot(1:10, 1:10) +
annotation_custom(
grob = g,
xmin = 1,
xmax = 5,
ymin = 5,
ymax = 10
) +
annotation_custom(
grob = rectGrob(gp = gpar(fill = "white")),
xmin = 7.5,
xmax = Inf,
ymin = -Inf,
ymax = 5
)
Plot inset.
In the second annotation, we used the convenient shorthand +/-Inf
to signify the edge of the plot, irrespective of the data range.
An alternative way to place custom annotations within the plots is to use raw grid commands, which we will present at the end of this document. However, note that an advantage of using annotation_custom
is that the inset plot is embedded in the main plot, therefore the whole layout can be saved with ggsave()
, which will not be the case for plot modifications at the grid
level.
A common request for presenting multiple plots on a single page is to align the plot panels. Often, facetting the plot solves this issue, with a flexible syntax, and in the true spirit of the Grammar of Graphics that inspired the ggplot2 design. However, in some situations, the various plot panels cannot easily be combined in a unique plot; for instance when using different geoms, or different colour scales.
grid.arrange()
makes no attempt at aligning the plot panels; instead, it merely places the objects into a rectangular grid, where they fit each cell according to the varying size of plot elements. The following figure illustrates the typical structure of ggplots.
Colour-coded structure of examplar ggplot layouts. Note how the panels (red) vary in size from plot to plot, as they accommodate the other graphical components.
As we can readily appreciate, each plot panel stretches or shrinks according to the other plot elements, e.g. guides, axes, titles, etc. This often results in misaligned plot panels.
In this situation, instead of using grid.arrange()
, we recommend to switch to the more powerful gtable
package. In particular, the rbind()
, cbind()
, and join
functions can provide a better alignment. The plots must first be converted to grobs (more specifically, gtables), using the ggplotGrob()
function. The second step is to bind
the two gtables, using the sizes from the first object, then assigning them to the maximum. Finally, the resulting object, a gtable, can be displayed using grid.draw()
(it is no longer a ggplot
, so print()
no longer renders it on a device).
library(gtable)
g2 <- ggplotGrob(p2)
g3 <- ggplotGrob(p3)
g <- rbind(g2, g3, size = "first")
g$widths <- unit.pmax(g2$widths, g3$widths)
grid.newpage()
grid.draw(g)
Aligning plot panels. Note that the two y axes have different widths.
One possible strategy, implemented in egg
with the low-level gtable_frame
and high-level ggarrange
functions, is to take the following steps:
Aligning plots is achieved simply as follows,
p1 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
geom_point()+ theme_article() + theme(legend.position = 'top')
p2 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
geom_point() + facet_wrap(~ cyl, ncol = 2, scales = "free") +
guides(colour = "none") +
theme_article()
ggarrange(p1, p2, widths = c(1.5,2))
where many parameters are common to the grid.arrange()
function from gridExtra
.
Plots produced by ggplot2
, including those with facets, and those combined with grid.arrange()
, are always displayed on a single page. Sometimes, however, there isn’t enough room to display all the information, and it becomes necessary to split the output on multiple pages. A convenient approach consists in storing all the plots in a list, and plotting subsets of them on subsequent pages. The gridExtra
package can simplify this process with the helper function marrangeGrob()
, sharing a common syntax with grid.arrange()
, but outputting as many pages as required by the total number of plots and per-page layout.
Adding a global title and/or subtitle to a page with multiple plots is easy with grid.arrange()
: use the top
, bottom
, left
, or right
parameters to pass either a text string, or a grob for finer control.
grid.arrange(
p3,
p3,
p3,
nrow = 1,
top = "Title of the page",
bottom = textGrob(
"this footnote is right-justified",
gp = gpar(fontface = 3, fontsize = 9),
hjust = 1,
x = 1
)
)
Recent versions of ggplot2 have added built-in options to add a subtitle and a caption; the two stategies are somewhat complementary (grid.arrange
aligns elements with respect to the entire plot, whereas ggplot2 places them with respect to the plot panel area).
When arranging multiple plots, one may wish to share a legend between some of them (although in general this is a clear hint that facetting might be a better option). The procedure involves extracting the legend from one graph, creating a custom layout, and inserting the plots and legend in their corresponding cell.
grid_arrange_shared_legend <-
function(...,
ncol = length(list(...)),
nrow = 1,
position = c("bottom", "right")) {
plots <- list(...)
position <- match.arg(position)
g <-
ggplotGrob(plots[[1]] + theme(legend.position = position))$grobs
legend <- g[[which(sapply(g, function(x)
x$name) == "guide-box")]]
lheight <- sum(legend$height)
lwidth <- sum(legend$width)
gl <- lapply(plots, function(x)
x + theme(legend.position = "none"))
gl <- c(gl, ncol = ncol, nrow = nrow)
combined <- switch(
position,
"bottom" = arrangeGrob(
do.call(arrangeGrob, gl),
legend,
ncol = 1,
heights = unit.c(unit(1, "npc") - lheight, lheight)
),
"right" = arrangeGrob(
do.call(arrangeGrob, gl),
legend,
ncol = 2,
widths = unit.c(unit(1, "npc") - lwidth, lwidth)
)
)
grid.newpage()
grid.draw(combined)
# return gtable invisibly
invisible(combined)
}
grid_arrange_shared_legend(p1, p2)
As we’ve seen in the previous examples, ggplots
are grobs, which can be placed and manipulated. Likewise, other grobs can be added to the mix. For instance, one may wish to add a small table next to the plot, as produced by the tableGrob
function in gridExtra
.
We’ve focused on grid.arrange()
and ggarrange
for simplicity, but there are numerous alternatives to achieve similar arrangements of plots (all of which ultimately based on grid
). We list below a few alternatives, in chronological order.
Package | Function(s) | ggsave compat. | alignment |
---|---|---|---|
grid | viewport , grid.layout |
no | no |
gridExtra | grid.arrange |
yes | no |
(r cookbook) | multiplot |
no | no |
gtable | rbind , cbind |
yes | yes |
cowplot | plot_grid |
yes* | yes* |
multipanelfigure | multi_panel_figure |
yes | yes |
egg | ggarrange |
yes | yes |
patchwork | plot_layout |
yes | yes |
Underlying all these other packages is the grid
package, included in the core R distribution. This package provides the low-level functions used for drawing and placing objects on a device.
pushViewport(viewport(layout = grid.layout(2, 2)))
vplayout <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(p1, vp = vplayout(1, 1:2))
print(p2, vp = vplayout(2, 1))
print(p3, vp = vplayout(2, 2))
Parts of this document were originally written by Hadley Wickham as a draft vignette for the ggplot2
package; the contents have since been shuffled around quite a bit and I have lost track of the exact history of edits.
This document provides a brief overview of the main utiilty functions included in the egg
package.
The function expose_layout
can be useful to illustrate the structure of ggplot2 plots, e.g. when ying to customise and/or post-process the gtable layout.
p1 <- qplot(mpg, wt, data = mtcars, colour = cyl)
p2 <- qplot(mpg, data = mtcars) + ggtitle("title")
p3 <- qplot(mpg, data = mtcars, geom = "dotplot")
p4 <-
p1 + facet_wrap( ~ carb, nrow = 1) + theme(legend.position = "none") +
ggtitle("facetted plot")
pl <- lapply(list(p1, p2, p3, p4), expose_layout, FALSE, FALSE)
grid.arrange(
grobs = pl,
widths = c(1.2, 1, 1),
layout_matrix = rbind(c(1, 2, 3),
c(4, 4, 4))
)
In some cases, having ggplot2 expand the plot panel to best fit the available space isn’t ideal: for instance, we may want to produce multiple plots to appear on different slides of a presentation, and the successive pages should have the exact same layout for smooth visual transition. Another use-case is to embed multiple separate graphics in a drawing/page layout software. In this situation the plot alignement will be made manually, but the plots should not be rescaled (otherwise the fonts would be distorted). For such situations, the easiest solution is to set fixed dimensions to the gtable produced by ggplot2.
The function set_panel_size
helps set the panel size (width, height) to absolute measurements in the form of grid units. In the case of a facetted plot, all panels are set to the same value.
p1 <- qplot(mpg, wt, data = mtcars, colour = cyl)
p2 <- p1 + facet_wrap( ~ carb, nrow = 1)
grid.arrange(grobs = lapply(
list(p1, p2),
set_panel_size,
width = unit(2, "cm"),
height = unit(1, "in")
))
Note that the total size is now fixed, therefore when exporting the plot on a device it can be useful to query the size and set the width and height accordingly, to avoid clipping or white margins. This extra step is enabled by default when saving the results to a file.
gridExtra::grid.arrange
provides no way to align the panels of individual plots. While this is achievable with low-level gtable
functions, it often requires substantial effort on a case-by-case basis. The egg
package introduces a general strategy for such layout manipulations, with the following steps:
rbind
/cbind
p1 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
geom_point()
p2 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
geom_point() + facet_wrap(~ cyl, ncol = 2, scales = "free") +
guides(colour = "none") +
theme()
p3 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
geom_point() + facet_grid(. ~ cyl, scales = "free")
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
g3 <- ggplotGrob(p3)
fg1 <- gtable_frame(g1, debug = TRUE)
fg2 <- gtable_frame(g2, debug = TRUE)
fg12 <-
gtable_frame(gtable_rbind(fg1, fg2),
width = unit(2, "null"),
height = unit(1, "null"))
fg3 <-
gtable_frame(
g3,
width = unit(1, "null"),
height = unit(1, "null"),
debug = TRUE
)
grid.newpage()
combined <- gtable_cbind(fg12, fg3)
grid.draw(combined)
Using this generic strategy, we can easily align arbitrary plots (facetted or single-panel), with the convenience function ggarrange
,
p1 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
geom_point()+ theme_article() + theme(legend.position = 'top')
p2 <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
geom_point() + facet_wrap(~ cyl, ncol = 2, scales = "free") +
guides(colour = "none") +
theme_article()
ggarrange(p1, p2, widths = c(1.5,2))
Note that custom widths and heights may be provided for the layout.
For convenience, labels can be added to refer to the subfigures. All parameters of textGrob
can be used for the formatting of the labels, including the positioning (x
, hjust
, etc.).
ggarrange(p1, p2, p3, ncol=2,
labels = c("A", "b)", "iii."),
label.args = list(gp=gpar(font=4), x=unit(1,"line"), hjust=0))
The package provides two functions for labelling facetted plots in a more compact manner, removing panel strips and using in-panel tags instead:
d = data.frame(
x = 1:90,
y = rnorm(90),
red = rep(letters[1:3], 30),
blue = c(rep(1, 30), rep(2, 30), rep(3, 30)))
p <- ggplot(d) +
geom_point(aes(x = x, y = y)) +
facet_grid(red ~ blue)
tag_facet(p)
egg
provides theme_article
and theme_presentation
,
d = data.frame(
x = 1:90,
y = rnorm(90),
red = rep(letters[1:3], 30),
blue = c(rep(1, 30), rep(2, 30), rep(3, 30)))
p <- ggplot(d) +
geom_point(aes(x = x, y = y)) +
facet_grid(red ~ blue)
p + theme_article()
The function symmetric_range
helps align the 0 value of adjacent panels in facetted plots with asymmetric range of data in each group.
df = data.frame(x = c(1, 2),
y = c(5, 0.2),
group = c(1, 2))
p <- ggplot(df, aes(x = x, y = y)) +
geom_point() +
facet_wrap( ~ group, scale =
"free")
p + scale_y_continuous(limits = symmetric_range)
The function geom_custom
extends the ggplot2 function annotation_custom
to cases where multiple grobs are to be placed, e.g. on different panels, or at different positions in a plot. This geom is a bit special in that it does not truly respect a grammar of graphics – arbitrary grobs can be plotted, with no explicit mapping to variables. Its typical use would be to place annotations (images, tables, …). The data used to create the annotation is passed as a list-column.
codes <- data.frame(country = c("nz","ca","ar","fr","gb","es"))
codes$y <- runif(nrow(codes))
gl <- lapply(codes$country,
function(.x) png::readPNG(system.file("flags",
paste0(.x,".png"),
package="egg")))
codes$raster <- I(gl)
ggplot(codes, aes(x = country, y = y)) +
geom_point() +
geom_custom(data = codes, aes(data=raster),
grob_fun = rasterGrob,
fun_params = list(height=unit(1,"cm"))) +
scale_y_continuous(breaks=NULL, "") +
theme(panel.grid = element_blank())
The list-column format allows passing grobs directly, in which case the grob_fun
function should be identity,
codes$raster <- I(lapply(codes$raster, function(x) rasterGrob(x, height=unit(1,"cm"))))
ggplot(codes, aes(x = country, y = y)) +
geom_point() +
geom_custom(data = codes, aes(data=raster),
grob_fun = identity)
Note that such grobs need to have x
and y
slots, which will be mapped to the appropriate location. It is therefore often necessary to create a wrapper with such fields, as illustrated below.
Because the grobs are manually “mapped”, independently of the main ggplot, this geom also allows the placing of arbitrary annotations without interference from transformed coordinate systems, etc.
custom_grob <- function(data, x=0.5,y=0.5){
grob(data=data,x=x,y=y, cl="custom")
}
preDrawDetails.custom <- function(x){
pushViewport(viewport(x=x$x,y=x$y))
}
postDrawDetails.custom <- function(x){
upViewport()
}
drawDetails.custom <- function(x, recording=FALSE, ...){
grid.rect(mean(x$data$x), mean(x$data$y),
width=diff(range(x$data$x)),
height=diff(range(x$data$y)))
grid.lines(x$data$x, x$data$y, gp=gpar(col=x$data$col,lwd=2), default.units = "native")
}
d <- data.frame(x=rep(1:3, 4), f=rep(letters[1:4], each=3))
gl <- lapply(1:4, function(ii){
data.frame(x=seq(0.4,0.6,length=10),
y = runif(10,0.45,0.55),
col = hcl(h = seq(0,300,length=nrow(d)))[ii],
stringsAsFactors = FALSE)
})
subplots <- data.frame(f=letters[1:4], data = I(gl))
str(subplots)
## 'data.frame': 4 obs. of 2 variables:
## $ f : Factor w/ 4 levels "a","b","c","d": 1 2 3 4
## $ data:List of 4
## ..$ :'data.frame': 10 obs. of 3 variables:
## .. ..$ x : num 0.4 0.422 0.444 0.467 0.489 ...
## .. ..$ y : num 0.469 0.452 0.491 0.498 0.492 ...
## .. ..$ col: chr "#FFC5D0" "#FFC5D0" "#FFC5D0" "#FFC5D0" ...
## ..$ :'data.frame': 10 obs. of 3 variables:
## .. ..$ x : num 0.4 0.422 0.444 0.467 0.489 ...
## .. ..$ y : num 0.527 0.471 0.481 0.547 0.508 ...
## .. ..$ col: chr "#FACABC" "#FACABC" "#FACABC" "#FACABC" ...
## ..$ :'data.frame': 10 obs. of 3 variables:
## .. ..$ x : num 0.4 0.422 0.444 0.467 0.489 ...
## .. ..$ y : num 0.469 0.478 0.459 0.471 0.548 ...
## .. ..$ col: chr "#EDD0AD" "#EDD0AD" "#EDD0AD" "#EDD0AD" ...
## ..$ :'data.frame': 10 obs. of 3 variables:
## .. ..$ x : num 0.4 0.422 0.444 0.467 0.489 ...
## .. ..$ y : num 0.477 0.46 0.462 0.549 0.549 ...
## .. ..$ col: chr "#DAD6A7" "#DAD6A7" "#DAD6A7" "#DAD6A7" ...
## ..- attr(*, "class")= chr "AsIs"
ggplot(d, aes(f,x)) +
facet_wrap(~f, nrow=1)+
coord_polar() +
geom_point()+
geom_custom(data = subplots, aes(data = data, x = f, y = 2),
grob_fun = custom_grob)