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.
Related
I am not sure exactly how to override aesthetic properties of a custom plot made with ggplot. The only way I could think of right now was using the functionality of the grid package, though is really hackish. Maybe there is a easier way, like using guides or so from ggplot2, though I could't manage to make it work?
Below is an example where I just want to adjust the line width in the graph. Of course, I would like that to trickle down in the legend as well. So, below are my steps with grid, but any simpler solution is greatly appreciated (ideally something that doesn't need grid but just ggplot2, if possible).
library(iNEXT)
library(ggplot2)
library(grid)
# Some custom plot from the iNEXT package
data(spider)
out <- iNEXT(spider, q=0, datatype="abundance")
custom_plot <- ggiNEXT(out)
custom_plot
# Get the grobs
g <- grid.force(ggplotGrob(custom_plot))
# Check the list of names of grobs:
# grid.ls(g)
# View(g$grobs)
# Get an idea about the grob paths
gpaths <- paste(gsub(pattern = "layout::",
replacement = "",
x = grid.ls(g, print = FALSE)$gPath),
grid.ls(g, print = FALSE)$name,
sep = "::")
gpaths[grepl("polyline", gpaths)]
#> [1] "panel.7-5-7-5::grill.gTree.114::panel.grid.minor.y..polyline.107"
#> [2] "panel.7-5-7-5::grill.gTree.114::panel.grid.minor.x..polyline.109"
#> [3] "panel.7-5-7-5::grill.gTree.114::panel.grid.major.y..polyline.111"
#> [4] "panel.7-5-7-5::grill.gTree.114::panel.grid.major.x..polyline.113"
#> [5] "panel.7-5-7-5::GRID.polyline.91"
#> [6] "panel.7-5-7-5::geom_ribbon.gTree.101::geom_ribbon.gTree.95::GRID.polyline.93"
#> [7] "panel.7-5-7-5::geom_ribbon.gTree.101::geom_ribbon.gTree.99::GRID.polyline.97"
# Edit the width of the lines
g <- editGrob(grob = g,
gPath = gpaths[grepl("panel.7-5-7-5::GRID.polyline", gpaths)],
gp = gpar(lwd = c(1,1,1,1)))
plot(g)
Created on 2020-07-22 by the reprex package (v0.3.0)
The answer you are looking for is under "Draw R/E curves by yourself" at
https://cran.r-project.org/web/packages/iNEXT/vignettes/Introduction.html.
Fortunately, the authors of the package have provided the function fortify() along with some code to copy verbatim, to achieve what you desire.
You should copy the following from that section and change the lwd (line width) parameter in the geom_line() function call to your liking.
df <- fortify(out, type=1) # Note the type parameter!
df.point <- df[which(df$method=="observed"),]
df.line <- df[which(df$method!="observed"),]
df.line$method <- factor(df.line$method,
c("interpolated", "extrapolated"),
c("interpolation", "extrapolation"))
ggplot(df, aes(x=x, y=y, colour=site)) +
geom_point(aes(shape=site), size=5, data=df.point) +
geom_line(aes(linetype=method), lwd=1.5, data=df.line) +
geom_ribbon(aes(ymin=y.lwr, ymax=y.upr,
fill=site, colour=NULL), alpha=0.2) +
labs(x="Number of individuals", y="Species diversity") +
theme(legend.position = "bottom",
legend.title=element_blank(),
text=element_text(size=18),
legend.box = "vertical")
I think you're making life overly complicated. Does this approach gives you what you need?
Generate a plot
plot <- mtcars %>% ggplot() + geom_line(aes(x=mpg, y=cyl, colour=as.factor(gear)))
plot
Modify the plot
plot + aes(size=5) + guides(size=FALSE)
The guides call suppresses the legend for size. Obviously, you can delete it if you do want the legend to appear.
Update
Responding to OP's question in the comments. I agree: my suggestion does not modify the ggiNEXT plot as I predicted.
I've done some digging. The diversity curves in the plot are produced by the following statement in the ggiNEXT.iNEXT function
g <- g + geom_line(aes_string(linetype = "lty"), lwd = 1.5) + ...
I find this strange. As far as I know, lwd is not an aesthetic in ggplot2. (And "lty" is not a valid value for the linetype aesthetic. However, lty and lwd are the base R equivalents of ggplot2's linetype and size respectively.)
In case lwd was an undocumented feature, I tried
custom_plot + aes(lwd=3)
But this had no effect.
I then copied the body of the ggiNEXT.iNEXT function into my own function and changed the call to geom_line to read
g <- g + geom_line(aes_string(linetype = "lty"), size = 1.5)
Calling my new function produced a plot identical (to my eye at least) to that produced by the original ggiNEXT.iNEXT call. Then
custom_plot <- myPlot(out)
custom_plot
custom_plot + aes(size=3) + guides(size=FALSE)
Produced the predicted changes. So my best suggestion is either (1) to create a local version of ggiNEXT.iNEXT and load it whenever you need to make this modification [Of course, you then need to make sure you update your local copy in line with any changes to the "official" version] or (2) to create the graph from scratch. Looking at the source code for ggiNEXT.iNEXT, it's not that complicated.
This might be worth raising as an issue with the authors of iNEXT.
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)
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.
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)
I'm new to plotly and not able to find the relevant documentation on how to name the traces so a meaningful label appears in plot rendered by ggplotly. Here is the ggplotly site that shows a number of examples. What is needed to show a meaningful label on hover instead of the value followed by trace0, trace1, etc.
For example, in the first plot, how can the labels appear so it shows:
Proportion: value
Total bill: value
Ideally, I would like to do this directly in R rather than through the web interface. Thanks in advance.
Using ggplot2 and Plotly you can set the text. You'll want to install Plotly and get a key. Here are two examples. Example one:
data(canada.cities, package="maps")
viz <- ggplot(canada.cities, aes(long, lat)) +
borders(regions="canada", name="borders") +
coord_equal() +
geom_point(aes(text=name, size=pop), colour="red", alpha=1/2, name="cities")
ggplotly()
ggplotly(filename="r-docs/canada-bubble")
This yields this plot with the name of Canadian cities available on the hover.
Example two:
install.packages("gapminder")
library(gapminder)
ggplot(gapminder, aes(x = gdpPercap, y = lifeExp, color = continent, text = paste("country:", country))) +
geom_point(alpha = (1/3)) + scale_x_log10()
ggplotly(filename="ggplot2-docs/alpha-example")
Which yields this plot.
For more information, see our R docs or this question on how to overwrite the hover_text element. Plotly's native R API lets you add more controls to your plots. Thanks for asking Brian. We'll add a new section to our docs on this as well. Disclaimer: I work for Plotly.
You can also edit any of the plotly figure properties after the ggplot2 conversion but before you send it to plotly. Here is an example that changes the legend entry names manually. I'll repeat it here:
df <- data.frame(x=c(1, 2, 3, 4), y=c(1, 5, 3, 5), group=c('A', 'A', 'B', 'B'))
g <- ggplot(data=df, aes(x=x, y=y, colour=group)) + geom_point()
# an intermediate step that `ggplotly` calls
p <- plotly_build(g)
# manually change the legend entry names, which are "trace0", "trace1" in your case
p$data[[1]]$name <- 'Group A'
p$data[[2]]$name <- 'Group B'
# send this up to your plotly account
p$filename <- 'ggplot2-user-guide/custom-ggplot2'
plotly_POST(p)
The extended example here explains in more detail how and why this works.
Note that in general the legend item names, e.g. "trace0", are going to be the labels that you grouped by in the dataframe (as in ggplot2).