Duplicate Same Legend Twice in Ggplot2 - r

I am preparing a chart where I have client's requirement to put same legend on top and bottom. Using ggplot I can put it either at top or at bottom. But I am not aware about option of duplicating at both the places.
I have tried putting legend.position as c('top','bottom') but that is giving me error and I know if should give error.
Can it be done with other libraries? I want to same legend twice at top and at bottom?
Take this code for an instance
library(ggplot2)
bp <- ggplot(data=PlantGrowth, aes(x=group, y=weight, fill=group)) + geom_boxplot()
bp <- bp + theme(legend.position="bottom")
bp

You have to work with the intermediate graphic objects (grobs) that ggplot2 uses when being plotted.
I grabbed a function that was flowing around here on StackOverflow to extract the legend, and put it into a package that is now on CRAN.
Here's a solution:
library(lemon)
bp <- bp + theme(legend.position='bottom')
g <- ggplotGrob(bp)
l <- g_legend(g)
grid.arrange(g, top=l)
g_legend accepts both the grob-version (that cannot be manipulated with ggplot2 objects) and the ordinary ggplot2 objects. Using ggplotGrob is a one-way street; once converted you cannot convert it back to ggplot2. But, as in the example, we keep the original ggplot2 object. ;)

Depending on the use case, a center-aligned top legend may not be appropriate as in the contributed answer by #MrGrumble here: https://stackoverflow.com/a/46725487/5982900
Alternatively, you can copy the "guide-box" element of the ggplotGrob, append it to your grob object, and reset the coordinates to the top of the ggplot.
createTopLegend <- function(ggplot, heightFromTop = 1) {
g <- ggplotGrob(ggplot)
nGrobs <- (length(g$grobs))
legendGrob <- which(g$layout$name == "guide-box")
g$grobs[[nGrobs+ 1]] <- g$grobs[[legendGrob]]
g$layout[nGrobs+ 1,] <- g$layout[legendGrob,]
rightLeft <- unname(unlist(g$layout[legendGrob, c(2,4)]))
g$layout[nGrobs+ 1, 1:4] <- c(heightFromTop, rightLeft[1], heightFromTop, rightLeft[2])
g
}
Load the gridExtra package. From your ggplot object bp, use createTopLegend to duplicate another legend, then use grid.draw to produce your final figure. Note you may need to alter your plot margins depending on your figure.
library(ggplot2)
library(grid)
library(gridExtra)
bp <- ggplot(data=PlantGrowth, aes(x=group, y=weight, fill=group)) + geom_boxplot()
bp <- bp + theme(legend.position="bottom", plot.margin = unit(c(2,0,0,0), "lines"))
g <- createTopLegend(bp)
grid.draw(g)
# dev.off()
This will ensure the legend is aligned in the same way horizontally as it appears in your original ggplot.

Related

Annotate the axis ggplo2

I am trying to create an annotation (particularly a rectangle) over a ggplot. Here's what I want to get:
I have tried geom_rect but that can only draw inside the plot axis.
I have also attempted to use annotate_custom which this post mentions, but when I try to work with xmin = -3 (for instance), it doesn't work.
Thank you!
I'm gonna start by asking what you are trying to achieve with this? It seems odd, at least in your example.
But, it can be done. Because you did not provide a reproducible example, I've got something else. The goal here is to turn of the panel's clipping, such that elements that lie outside it's boundaries will be plotted.
library(ggplot2)
library(grid)
# Create a plot
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
Here, I'm adding a rectangle with rect. But this also modifies the x- and y-axis, so we fix these with coord_cartesian. You cannot use xlim as this will remove data points that fall outside the range.
g <- p + annotate('rect', xmin=-1, xmax=3, ymin=10, ymax=30, fill='blue', alpha=1/3) +
coord_cartesian(xlim=c(1, 4))
# Convert into a graphical object -- a grob
g <- ggplotGrob(g)
# Try printing g
g is an object that puts all the elements into a table-like structure. So now, we find the panel in the layout dataframe of g, and turn of clipping.
i <- which(g$layout$name == 'panel')
g$layout[i,'clip'] <- 'off'
Finally draw the grob:
# grid.newpage()
grid.draw(g)

change font size of labels without knowing labels in ggplot2

I would like to change the font size of the labels in this plot:
library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y=mpg)) +
geom_text(label=rownames(mtcars))
p
My problem: I do not know what the labels are. (I stored a plot in which I used different data.frame()s to add geom_text(). I now only loaded the plot (p in this example), but do not want to also load the data.frame()s with which I created the labels).
As I do not know what the labels are, I cannot use this solution:
p + geom_text(label=rownames(mtcars), size=2)
(Another problem with this solution would be that I still needed to delete the original geom_text() with the larger font-size).
I can change the size of all text in the plot with this solution:
library(grid)
grid.force()
grid.gedit("GRID.text", grep=TRUE, gp=gpar(fontsize=4.5))
However, now also my axes changed, which is not what I wanted.
I believe there are several options to achieve what I want, at least two of which should be fairly simply to implement:
Save the object from grid.gedit() to p1 and then p1 + theme(text = element_text(size=2)). My problem here: I do not know how to save the object from grid.gedit(). This would be my preferred option.
Go to the right viewport before applying grid.gedit(). I tried this, but still change both the labels (which I want) and the axes text (which I do not want).
Somehow extract the data.frame for the labels from the stored plot (p in this example) to apply the solution that I provided first.
You can inspect (/modify) the plot after building it,
library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y=mpg)) +
geom_text(label=rownames(mtcars))
g <- ggplot_build(p)
# original data is in str(g$plot$data)
# but it's easier to process the data for rendering
g[["data"]][[1]][["size"]] <- 5
g[["data"]][[1]][["colour"]] <- "red"
gg <- ggplot_gtable(g)
grid.newpage()
grid.draw(gg)
Your grid.gedit command was close. You need to set up a gPath so that the edit command finds just those labels in the plot panel. grid.ls(grid.force()) returns a hierarchy of grobs. Find the 'panel', then the 'text'. (Note: the 'g' in 'gedit' stands for 'grep = TRUE, global = TRUE')
library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y=mpg)) +
geom_text(label=rownames(mtcars))
p
library(grid)
grid.ls(grid.force()) # Locate the path to the labels in the panel
grid.gedit(gPath("panel","GRID.text"), gp=gpar(fontsize=4.5))
If you prefer, with a few more lines of code, the plot object can be edited rather than editing on screen.
g = ggplotGrob(p)
g = editGrob(grid.force(g), gPath("panel", "GRID.text"), grep=TRUE, gp=gpar(fontsize=4.5))
grid.newpage()
grid.draw(g)

Is it possible to desaturate a ggplot easily?

Is it possible to desaturate a ggplot easily?
In principle, there could be two possible strategies.
First, apply some function to a ggplot object (or, possibly, Grob object) to desaturate all colors. Second, some trick to print ggplot desaturated while rendering a .rmd file. Both strategies would be ok for me, but first one is, of course, more promissing.
Creating ggplot in greys from the beginning is not a good option as the idea is to have the same plot as if it was printed in shades of grey.
There were some similar questions and remarkably good answers on how to perform desaturation in R. Here is a convenient way to desaturate color palette. And here is the way of desaturating a raster image. What I'm looking for, is a simple way of desaturating the whole ggplot.
Just came across this question. The experimental package colorblindr (written by Claire McWhite and myself) contains a function that can do this in a generic way. I'm using the example figure from #hrbrmstr:
library(ggplot2)
library(viridis)
gg <- ggplot(mtcars) +
geom_point(aes(x=mpg, y=wt, fill=factor(cyl), size=factor(carb)),
color="black", shape=21) +
scale_fill_viridis(discrete = TRUE) +
scale_size_manual(values = c(3, 6, 9, 12, 15, 18)) +
facet_wrap(~am)
gg
Now let's desaturate this plot, using the edit_colors() function from colorblindr:
library(colorblindr) # devtools::install_github("clauswilke/colorblindr")
library(colorspace) # install.packages("colorspace", repos = "http://R-Forge.R-project.org") --- colorblindr requires the development version
# need also install cowplot; current version on CRAN is fine.
gg_des <- edit_colors(gg, desaturate)
cowplot::ggdraw(gg_des)
The function edit_colors() takes a ggplot2 object or grob and applies a color transformation function (here desaturate) to all colors in the grob.
We can provide additional arguments to the transformation function, e.g. to do partial desaturation:
gg_des <- edit_colors(gg, desaturate, amount = 0.7)
cowplot::ggdraw(gg_des)
We can also do other transformations, e.g. color-blind simulations:
gg_des <- edit_colors(gg, deutan)
cowplot::ggdraw(gg_des)
Finally, we can manipulate line colors and fill colors separately. E.g., we could make all filled areas blue. (Not sure this is useful, but whatever.)
gg_des <- edit_colors(gg, fillfun = function(x) "lightblue")
cowplot::ggdraw(gg_des)
As per my comment above, this might be the quickest/dirtiest way to achieve the desaturation for a ggplot2 object:
library(ggplot2)
set.seed(1)
p <- qplot(rnorm(50), rnorm(50), col="Class")
print(p)
pdf(file="p.pdf", colormodel="grey")
print(p)
dev.off()
I tried this with the new viridis color palette since it desaturates well (i.e. it should be noticeable between the colored & non-colored plots):
library(ggplot2)
library(grid)
library(colorspace)
library(viridis) # devtools::install_github("sjmgarnier/viridis") for scale_fill_viridis
gg <- ggplot(mtcars) +
geom_point(aes(x=mpg, y=wt, fill=factor(cyl), size=factor(carb)),
color="black", shape=21) +
scale_fill_viridis(discrete = TRUE) +
scale_size_manual(values = c(3, 6, 9, 12, 15, 18)) +
facet_wrap(~am)
gb <- ggplot_build(gg)
gb$data[[1]]$colour <- desaturate(gb$data[[1]]$colour)
gb$data[[1]]$fill <- desaturate(gb$data[[1]]$fill)
gt <- ggplot_gtable(gb)
grid.newpage()
grid.draw(gt)
You end up having to manipulate on the grob level.
Here's the plot pre-desaturate:
and here's the plot post-desature:
I'm trying to figure out why the legend got skipped and this may miss other highly customized ggplot aesthetics & components, so even while it's not a complete answer, perhaps it might be useful (and perhaps someone else can tack on to it or expand on it in another answer). It should just be a matter of replacing the right bits in either the gb object or gt object.
UPDATE I managed to find the right grob element for the legend:
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[4]]$gp$fill <-
desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[4]]$gp$fill)
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[6]]$gp$fill <-
desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[6]]$gp$fill)
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[8]]$gp$fill <-
desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[8]]$gp$fill)
grid.newpage()
grid.draw(gt)
The machinations to find the other gp elements that need desaturation aren't too bad either.

Dropping y strip, but not x, from ggplot2 facet

I am using ggplot2 to create a 2D facet plot. I wish to drop the y-axis strip (because it is self-explanatory) but remove the x-axis strip (because it is not).
I found several examples about how to remove both strips, e.g. this. However, in every case, it was showing you how to get rid of all the labels. There is an example of how to remove the labels from one panel and not the other, but this seems more complicated than I imagine (hope) it actually is. (Also, when I copied and pasted the code, R did not recognize the "unit" command that appeared in the code fragment, though I see that this is addressed elsewhere.)
So, let's say I have the facet grid below, and I want to hide the grey strip for cut, but not color.
require(ggplot2)
pdf(file = sprintf("minimal.pdf"))
p <- ggplot(diamonds, aes(carat, price))
p <- p + geom_point()
p <- p + facet_grid(cut ~ color, scales="fixed")
print(p)
dev.off()
Adding the following line turns both strips white, and removes the characters from the y-strip (cut).
p <- p + theme(strip.text.y = element_blank(), strip.background = element_blank())
That's an improvement, but what I really want to do is keep the x-strip as it was, with the original grey background, but remove the y-strip. Manually adjusting the margins every time I resize the figure, as is done in one of the references, does not seem like a nice way to do it. I am wondering if there is a better way.
You can subset the gtable to remove the column you don't want
g <- ggplotGrob(p)
strips <- subset(g$layout, grepl("strip-right", g$layout$name))
library(grid)
grid.newpage()
grid.draw(g[,-unique(strips$r)])

Justification of multiple legends in ggmap/ggplot2

I am trying to make a map with two legends denoting shape and colour ("Type" and "Org" in the example below), and have the legends inset. I can place the legends, but I would like them to be left justified so that their left edges line up. I can't make them anything other than centred with respect to each other:
require(ggplot2)
require(ggmap)
require(grid)
require(mapproj)
data <- data.frame(Org=rep(c("ABCDEFG","HIJKLMNOP","QRSTUVWX"),4)
, Type=rep(c("Y","Z"),6), Lat=runif(12,48,54.5)
, Long=runif(12,-133.5,-122.5))
osmMap <- get_map(location=c(-134,47.5,-122,55), source = 'osm')
points <- geom_jitter(data=data, aes(Long, Lat, shape=Type
, colour=Org))
legend <- theme(legend.justification=c(0,0), legend.position=c(0,0)
, legend.margin=unit(0,"lines"), legend.box="vertical"
, legend.key.size=unit(1,"lines"), legend.text.align=0
, legend.title.align=0)
ggmap(osmMap) + points + legend
This option is now available in ggplot2 0.9.3.1, use
ggmap(osmMap) + points + legend + theme(legend.box.just = "left")
Old, manual solution:
Here is a solution:
require(gtable)
require(ggplot2)
require(ggmap)
require(grid)
require(mapproj)
# Original data
data <- data.frame(Org=rep(c("ABCDEFG","HIJKLMNOP","QRSTUVWX"),4),
Type=rep(c("Y","Z"),6), Lat=runif(12,48,54.5),
Long=runif(12,-133.5,-122.5))
osmMap <- get_map(location=c(-134,47.5,-122,55), source = 'google')
points <- geom_jitter(data=data, aes(Long, Lat, shape=Type, colour=Org))
legend <- theme(legend.justification=c(0,0), legend.position=c(0,0),
legend.margin=unit(0,"lines"), legend.box="vertical",
legend.key.size=unit(1,"lines"), legend.text.align=0,
legend.title.align=0)
# Data transformation
p <- ggmap(osmMap) + points + legend
data <- ggplot_build(p)
gtable <- ggplot_gtable(data)
# Determining index of legends table
lbox <- which(sapply(gtable$grobs, paste) == "gtable[guide-box]")
# Each legend has several parts, wdth contains total widths for each legend
wdth <- with(gtable$grobs[[lbox]], c(sum(as.vector(grobs[[1]]$widths)),
sum(as.vector(grobs[[2]]$widths))))
# Determining narrower legend
id <- which.min(wdth)
# Adding a new empty column of abs(diff(wdth)) mm width on the right of
# the smaller legend box
gtable$grobs[[lbox]]$grobs[[id]] <- gtable_add_cols(
gtable$grobs[[lbox]]$grobs[[id]],
unit(abs(diff(wdth)), "mm"))
# Plotting
grid.draw(gtable)
This does not depend on Type or Org. However, this would not be enough having more than two legends. Also, in case you do some changes so that list of grobs (graphical objects) is altered, you might need to change grobs[[8]] to grobs[[i]] where i is the position of your legends, see gtable$grobs and look for TableGrob (5 x 3) "guide-box": 2 grobs.
Edit: 1. Automatically detecting which grob is legends table, i.e. no need to change anything after modifying other parts of plot. 2. Changed calculation of width differences, now code should work when having any two legends, i.e. in more complex cases as well, for example:

Resources