library(knitr) knitr::opts_chunk$set(fig.height=4, fig.width=6, cache=TRUE, autodep = TRUE, cache.path = 'legend-cache/') knitr::opts_template$set(smallfigure = list(fig.height=2, fig.width=3))
g_legend
reposition_legend
grid_arrange_shared_legend
ggplot2
by default places the legend in the margin of the entire plot.
This is in many instances a nice solution.
If this is not desired, theme(legend.position)
can be used to place the legend
in relative measures on the entire plot:
library(ggplot2) library(grid) library(gridExtra) dsamp <- diamonds[sample(nrow(diamonds), 1000), ] (d <- ggplot(dsamp, aes(carat, price)) + geom_point(aes(colour = clarity)) + theme(legend.position = c(0.06, 0.75)) )
This is however prone to badly positioning, if e.g. the plot is resized or font size changed:
vp1 <- viewport(x=0, y=0, width=unit(0.5, 'npc'), just=c(0,0)) vp2 <- viewport(x=0.5, y=1, width=unit(1.5, 'npc'), height=unit(1.5, 'npc'), just=c(0,1)) grid.newpage() vp0 <- current.viewport() pushViewport(vp1) g <- ggplotGrob(d + theme_gray(base_size=26) + theme(legend.position = c(0.06, 0.75))) grid.draw(g) popViewport() pushViewport(vp2) grid.draw(ggplotGrob(d))
With our function, we can specify exactly how we want it in the plotting area:
library(lemon) reposition_legend(d, 'top left')
And it stays there.
vp1 <- viewport(x=0, y=0, width=unit(0.5, 'npc'), just=c(0,0)) vp2 <- viewport(x=0.5, y=1, width=unit(1.5, 'npc'), height=unit(1.5, 'npc'), just=c(0,1)) grid.newpage() vp0 <- current.viewport() pushViewport(vp1) g <- reposition_legend(d + theme_gray(base_size=26) + theme(legend.position = 'left'), 'top left', plot=FALSE) grid.draw(g) popViewport() pushViewport(vp2) grid.draw(reposition_legend(d, 'top left', plot=FALSE))
The left plot is printed in full size at the end of this document.
For our final trick in this act, we reposition a legend with multiple guides.
For this, use theme(legend.box.background)
to put a background around the
entire legend, not just the individual guides.
d2 <- d + aes(shape=cut) + theme(legend.box.background = element_rect(fill='#fffafa'), legend.background = element_blank()) reposition_legend(d2, 'left')
The guidebox uses a solid background (subject to the chosen theme), and prior to lemon version 0.3.1, the entire legend was placed as the top most element. In the examples above, this was not an issue. With axis lines drawn, this effectively overpainted some of the axis (same applies to the panel border).
The guidebox is therefore placed under the lowest axis line,
if and only if z = Inf
.
To place as top most, specify a large z-index.
reposition_legend(d + theme_classic(), 'top left')
To adjust the guidebox so it does not overpaint the panel border, use arguments
x
and y
,
reposition_legend(d + theme_bw(), 'top left', x=0.002, y=1-0.002)
... or use the argument offset
:
reposition_legend(d + theme_bw(), 'top left', offset=0.002)
To our knowledge, there exists two methods for extracting the legend:
g1 <- function(a.gplot){ if (!gtable::is.gtable(a.gplot)) a.gplot <- ggplotGrob(a.gplot) leg <- which(sapply(a.gplot$grobs, function(x) x$name) == "guide-box") a.gplot$grobs[[leg]] } g2 <- function(a.gplot){ if (!gtable::is.gtable(a.gplot)) a.gplot <- ggplotGrob(a.gplot) gtable::gtable_filter(a.gplot, 'guide-box', fixed=TRUE) }
There is very little difference between them, as the latter essentially does the same as the former. The latter however encapsulated the former in a gtable. This is even more evident with multiple guides:
(da <- ggplot(dsamp, aes(carat, price)) + geom_point(aes(colour = clarity, shape=cut)) + theme(legend.box = 'horizontal') ) print(g1(da)) print(g2(da))
The function reposition_legend
assumes the method given in g1
,
which is also given in g_legend
.
The above demonstration finds the panel named panel
. This is default.
If using facetting, the panels are typically named panel-{column}-{row}
.
We use gtable_show_names
to display the names of the facetted panels.
d2 <- d + facet_grid(.~cut, ) gtable_show_names(d2)
So to place the legend in a specific panel, give its name:
reposition_legend(d2, 'top left', panel = 'panel-1-5')
Likewise for facet_wrap
. Incidentally, empty panels are also named here:
reposition_legend(d + facet_wrap(~cut, ncol=3), 'top left', panel='panel-3-2')
Modifying the legend is done via usual routines of ggplot2:
d3 <- d + facet_wrap(~cut, ncol=3) + scale_color_discrete(guide=guide_legend(ncol=3)) reposition_legend(d3, 'center', panel='panel-3-2')
Also supports spanning multiple panels:
d4 <- d + facet_wrap(~cut, ncol=4) + scale_color_discrete(guide=guide_legend(nrow=2)) reposition_legend(d4, 'center', panel=c('panel-2-2','panel-4-2'))
The panel names are not easy to figure, especially those from facet_wrap
.
We refer to gtable_show_names
to get a look at where they are:
gtable_show_names(d4)
The function grid_arrange_shared_legend
extracts the legend from its first
argument, combines the plots with the legend hidden using arrangeGrob
, and
finally appends the legend to one of the sides.
It even updates the plot's theme to orientate the legend correctly.
dsamp <- diamonds[sample(nrow(diamonds), 1000), ] p1 <- qplot(carat, price, data = dsamp, colour = clarity) p2 <- qplot(cut, price, data = dsamp, colour = clarity) p3 <- qplot(color, price, data = dsamp, colour = clarity) p4 <- qplot(depth, price, data = dsamp, colour = clarity) grid_arrange_shared_legend(p1, p2, p3, p4, ncol = 2, nrow = 2, position='top') grid_arrange_shared_legend(p1, p2, p3, p4, ncol = 2, nrow = 2, position='bottom') grid_arrange_shared_legend(p1, p2, p3, p4, ncol = 2, nrow = 2, position='left') grid_arrange_shared_legend(p1, p2, p3, p4, ncol = 2, nrow = 2, position='right')
grid.arrange
A more flexible approach to combining plots and legends can be found in
Baptiste Auguie's gridExtra
::grid.arrange
and arrangeGrob
.
The latter is the power house that produces a grob object, which the former
then draws to the device.
But being more flexible, it is somewhat less automated.
We demonstrate here how to combine 3 of the 4 plots above, with different options for layout and placing the legend.
g_legend <- function(a.gplot){ if (!gtable::is.gtable(a.gplot)) a.gplot <- ggplotGrob(a.gplot) #gtable_filter(a.gplot, 'guide-box', fixed=TRUE) leg <- which(sapply(a.gplot$grobs, function(x) x$name) == "guide-box") a.gplot$grobs[[leg]] }
library(gridExtra) legend <- g_legend(p1 + theme(legend.position='bottom')) grid.arrange(p1+theme(legend.position='hidden'), p2+theme(legend.position='hidden'), p3+theme(legend.position='hidden'), legend)
grid.arrange(p1+theme(legend.position='hidden'), p2+theme(legend.position='hidden'), p3+theme(legend.position='hidden'), legend, layout_matrix=matrix(c(1,3,4,2,3,4), ncol=2))
In the figure above, a layout matrix was defined with three rows. Subsequently, the three rows were of equal heights.
It is difficult to calculate the row heights to provide grid.arrange
if we
wanted to fix the height of the legend row's height. We could attempt
(unit(1, 'npc') - sum(legend$heights)) / 2
but division is not permitted on units.
When grid.arrange
is given a margin argument (e.g. bottom
), it creates a
gtable object with a row or column of appropiate dimension.
The remainder of the gtable is re-sized to fit.
We can therefore do:
grid.arrange(p1+theme(legend.position='hidden'), p2+theme(legend.position='hidden'), p3+theme(legend.position='hidden'), bottom=legend$grobs[[1]], layout_matrix=matrix(c(1,3,2,3), ncol=2))
There is however currently a shortcoming in grid.arrange
and arrangeGrob
that prevents it from using the full gtable object returned from g_legend
in
the margin arguments. We therefore subset to a specific grob.
What worse is, if the legend contains multiple guides, the above approach will
not work directly. However, defining several arrangeGrob
within each other
is possible.
Asked on: https://stackoverflow.com/questions/46238676/common-legend-for-a-grid-plot
In this reproducible example grid plot, 3 plots have 3 fill colours, and z displays with the "col" blue, but in the fourth plot there is only 1 "col", so z displays as red.
I want to show only one common legend (which I can do), but I want z to be blue in all four plots. Is there a simple way to do that?
Before:
library(ggplot2) library(grid) library(gridExtra) d0 <- read.csv(text="x, y, col\na,2,x\nb,2,y\nc,1,z") d1 <- read.csv(text="x, y, col\na,2,x\nb,2,y\nc,1,z") d2 <- read.csv(text="x, y, col\na,2,x\nb,2,y\nc,1,z") d3 <- read.csv(text="x, y, col\na,2,z\nb,2,z\nc,1,z") p0 <- ggplot(d0) + geom_col(mapping = aes(x, y, fill = col)) p1 <- ggplot(d1) + geom_col(mapping = aes(x, y, fill = col)) p2 <- ggplot(d2) + geom_col(mapping = aes(x, y, fill = col)) p3 <- ggplot(d3) + geom_col(mapping = aes(x, y, fill = col)) grid.arrange(p0, arrangeGrob(p1,p2,p3, ncol=3), ncol=1)
nt <- theme(legend.position='hidden') grid_arrange_shared_legend(p0, arrangeGrob(p1+nt,p2+nt,p3+nt, ncol=3), ncol=1, nrow=2)
p <- ggplot(dsamp, aes(x=cut, y=price, colour=clarity)) + geom_point(position=position_jitter(width=0.2)) + coord_flex_cart(bottom=brackets_horizontal(), left=capped_vertical('none')) + theme_bw() + theme(panel.border=element_blank(), axis.line = element_line(), legend.background = element_rect(colour='grey')) g <- reposition_legend(p, 'top left', plot=TRUE)
g_legend
was proposed as early as June 2012 by Baptiste AuguiƩ
(http://baptiste.github.io/) on
ggplot2's wiki.
It has since propogated throughout
Stack Overflow answers.
Originally brought to you by (Baptiste AuguiƩ)[http://baptiste.github.io/] (https://github.com/tidyverse/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs) and (Shaun Jackman)[http://rpubs.com/sjackman] (http://rpubs.com/sjackman/grid_arrange_shared_legend). It has been further modified here.
reposition_legend
was coded by Stefan McKinnon Edwards/
Example with reposition_legend
that didn't quite work:
dsamp <- diamonds[sample(nrow(diamonds), 1000), ] d <- ggplot(dsamp, aes(carat, price)) + geom_point(aes(colour = clarity)) reposition_legend(d + theme_gray(base_size=26), 'top left')
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.