---
title: "Working with legends"
author: "Stefan McKinnon Edwards <sme@iysik.com>"
date: "`r Sys.Date()`"
output:
  rmarkdown::html_vignette:
    fig_caption: yes
    toc: true # table of content true
vignette: >
  %\VignetteIndexEntry{Working with legends}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup,include=FALSE}
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))
```

## Legend functions

* Extract legend: `g_legend`
* Reposition legend onto the actual plot: `reposition_legend`
* Combine multiple plots, but use only _one_ legend: `grid_arrange_shared_legend`

## Reposition legend onto plotting panel

`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:

```{r fig.cap='Imprecise positioning of legend with `theme(legend.position)`.'}
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:

```{r echo=FALSE,fig.cap='**Left:** Base font size set to 22 pt. **Right:** Zoom on plot that is plotted at 150% size.'}


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:
```{r reposition_legend,fig.cap='Exact positioning of legend in the main panel.'}
library(lemon)
reposition_legend(d, 'top left')
```

And it stays there. 

```{r echo=FALSE,fig.cap='**Left:** Base font size set to 22 pt. **Right:** Zoom on plot that is plotted at 150% size.'}
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.

### Multiple legends per guide
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.

```{r fig.cap="Legend with multple guides on a tacky 'snow' background."}
d2 <- d + aes(shape=cut) + 
  theme(legend.box.background = element_rect(fill='#fffafa'),
        legend.background = element_blank())
reposition_legend(d2, 'left')
```

### Legends are placed *under* axis lines

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. 

```{r fig.cap='Legend is drawn *under* axis lines.'}
reposition_legend(d + theme_classic(), 'top left')
```

To adjust the guidebox so it does not overpaint the panel border, use arguments
`x` and `y`,

```{r fig.cap='Legend has to be nudged to not overpaint panel border.'}
reposition_legend(d + theme_bw(), 'top left', x=0.002, y=1-0.002)
```

...  or use the argument `offset`:

```{r fig.cap='Legend has to be nudged to not overpaint panel border, this time by `offset`.'}
reposition_legend(d + theme_bw(), 'top left', offset=0.002)
```

### Warning regarding extracting legend

To our knowledge, there exists two methods for extracting the legend:

```{r}
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:

```{r opts.label='smallfigure',fig.cap='Two guides in a single legend, in a grossly undersized figure.'}
(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`.

### Placing the legend in facets

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.

```{r fig.cap="Facetted panels' names."}
d2 <- d + facet_grid(.~cut, )
gtable_show_names(d2)
```

So to place the legend in a specific panel, give its name. 
Depending on ggplot2 version, the panel's name may differ.

```{r reposition_legend_facet1,fig.cap='Placing the legend in a facet panel.'}
try(reposition_legend(d2, 'top left', panel = 'panel-5-1'))
try(reposition_legend(d2, 'top left', panel = 'panel-1-5'))
```

Likewise for `facet_wrap`. Incidentally, empty panels are also named here:

```{r reposition_legend_facet2,fig.cap='Placing the legend in an empty panel when using `facet_wrap`.'}
reposition_legend(d + facet_wrap(~cut, ncol=3), 'top left', panel='panel-3-2')
```

Modifying the legend is done via usual routines of ggplot2:
```{r reposition_legend_facet3,fig.cap='The looks of the legend is modified with usual ggplot2 options.'}
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:
```{r reposition_multiple_panels,fig.cap='Supplying `reposition_legend` with multple panel-names allows the legend to span them.'}
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:

```{r gtable_show_names,fig.cap="Use of `gtable_show_names` to reveal the panels' names."}
gtable_show_names(d4)
```

## Shared legend across multiple plot

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.

```{r fig.cap='...'}
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')
```

## Shared legend with `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.

```{r g_legend,include=FALSE,fig.cap=''}
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]]
}
```
```{r}
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)
```

```{r fig.cap='Using layout_matrix to have plots span different cells of a grid.'}
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:
```{r fig.cap='Using layout_matrix to have plots span different cells of a grid, but with legend in a separate argument.'}
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.

## More examples

### Complex layout with grid_arrange_shared_legend

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:
```{r example_shared_legend_complex_layout_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)
```

```{r example_shared_legend_complex_layout_after}
nt <- theme(legend.position='hidden')
grid_arrange_shared_legend(p0, arrangeGrob(p1+nt,p2+nt,p3+nt, ncol=3), ncol=1, nrow=2)
```




## Plot for ggplot2-extensions

```{r fig.height=300/72,fig.width=350/72}
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)
```


## Acknowledgements

`g_legend` was proposed as early as June 2012 by Baptiste Auguié 
(http://baptiste.github.io/) on 
[ggplot2's wiki](https://github.com/tidyverse/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs/047381b48b0f0ef51a174286a595817f01a0dfad). 
It has since propogated throughout 
[Stack Overflow](https://stackoverflow.com) 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](http://www.iysik.com/)/

## Footnotes

Example with `reposition_legend` that didn't quite work:
```{r}
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')
```

