Problems with positioning of ggplot labels with ggarrange - r

I'm trying to create a panel of plots using ggarrange. I'm using the "labels" argument to create a title for each one, but the positioning is coming out differently for each plot depending on how long the title is. It seems that the longer the title, the further to the right the label gets printed.
How can I make all labels left-justified? I've tried using hjust or label.x, but this doesn't change things.
This is my code and the plot:
ggarrange(plot1,plot2,plot3,
common.legend=TRUE,
labels = c("asdf", "asdfasdf", "asdfasdfasdfsadfasdf"),
hjust=-0.8,
ncol = 2, nrow = 2)

I am not sure whether this is the short-cut to your question but trying this may help :
plot1 <- plot1 + theme(legend.position="left")+
labs(title="asdf")
plot2<- plot2 + theme(legend.position="left")+
labs(title="asdfasf")
plot3 <- plot3 + theme(legend.position="left")+
labs(title="asdfasdfasfdsas")
ggarrange(plot1,plot2,plot3,
common.legend=TRUE,
hjust=-0.8,
ncol = 2, nrow = 2)
You can check: https://www.royfrancis.com/customising-ggplot2/

In my opinion, you may use geom_text() to set the label first for every plot, then use ggarrange() to put all subfigures together.

hjust works for horizontal shift and vjust for vertical. In my case, I kept hjust=-7 to keep the labels in the middle, and it worked. You have to try it based on your data.

Related

Insert rectangle outside of ggplot to visualize plot segments

I hope you can help me. I have the idea of visualizing segments within a plot with a rectangle that can be placed next to the y or x-axis which means that it would be outside of the plot area. It should look similar as in the image below:
I tried to reach the mentioned output by trying two different approaches:
I created two viewports with the grid package and put the plot in one viewport that I placed at the bottom and one viewport on top of that. The big problem here is that I need the coordinates from where the grey background panel of the ggplot starts so I can place the top viewport exactly there, so that the segments conincide with the x-axis length. My code looked like following:
container_viewport <- viewport(x=0,y=0,height=1,width=1,just = c("left","bottom"))
pushViewport(container_viewport)
grid.draw(rectGrob())
popViewport()
section_viewport <- viewport(x=0.055,y=0.99,height=0.085,width=0.935,just=c("left","top"))
pushViewport(section_viewport)
plot_obj <- ggplot_build(testplot)
plot_data <- plot_obj$data[[1]]
grid.draw(rectGrob(gp = gpar(col = "red")))
popViewport()
plot_viewport <- viewport(x=0,y=0,height=0.9,width=1,just=c("left","bottom"))
pushViewport(plot_viewport)
grid.draw(ggplotGrob(testplot))
popViewport()
This looks fine but I had to hardcode the coordinates of the viewport at the top.
I used grid.arrange() to arrange to stack the plots vertically (instead of a grob for the rectangle like in the other approach I create a ggplot instead for that). Here, basically the same problem exists, since I somehow need to put the plot representing the rectangle at the top in the right position on the x-axis. My code looked like following:
p1 <- plot_data %>%
ggplot()+
geom_rect(aes(xmin=-Inf,xmax=Inf,ymin=-Inf,ymax=Inf))
p2 <- testplot
test_plot <- grid.arrange(p1,p2,heights=c(1,10))
This approach does not work that good.
Since I would like to create a solution that can be applied generally, trial and error with the coordinates of the viewport is no option since the length of the y-axis label or tick labels can vary and therefore the length and coordinates of the background panel. When this step is done the segmentation of the rectangle should be no problem anymore.
Maybe this is just not possible but if then I would appreciate any help.
Thank you!
I would probably use patchwork here. Let's start by replicating your plot:
library(ggplot2)
library(patchwork)
p <- ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
geom_point(color = "red") +
labs(x = "test", y = "test")
p
That looks very similar. Now we define (in our own co-ordinates) where we want the section split to occur on the x axis.
section_split <- 5.25
Using just this number, we add rectangles and text annotations that cover a copy of our original plot, and remove its axis annotations using theme_void:
p2 <- p +
annotate("rect", xmin = c(-Inf, section_split), ymin = c(-Inf, -Inf),
xmax = c(section_split, Inf), ymax = c(Inf, Inf),
fill = c("#00a2e8", "#ff7f27")) +
annotate("text", label = c("Section A", "Section B"), size = 6,
y = rep(mean(layer_scales(p)$y$range$range), 2),
x = c((min(layer_scales(p)$x$range$range) + section_split)/2,
(max(layer_scales(p)$x$range$range) + section_split)/2)) +
theme_void()
Now we just draw this second plot above our first, adjusting the relative heights to about 1:10
p2/p + plot_layout(heights = c(1, 10))
The benefit of doing it this way is that, since we copied the original plot, the positional mapping of the x axis is identical between the two plots, and patchwork will automatically line up the panels.
Created on 2023-02-04 with reprex v2.0.2

R ggarrange how to include upper scripts in the plot titles?

I am trying to add plot labels as titles in plots merged by ggarrange. My label contains parantheses and substricpts. I have found that using expression('My volume [m'^3*'/ha]') I can handle both, which works perfectly if placed as x or y label (ylab(expression('My volume [m'^3*'/ha]'))).
However, when using the same approach using ggarrange to combine different plots, and wishing to name them a) and b), the naming prints quotes as well:
How can I correctly write the expression, or using paste('..', '..') approach that quotes are not visible?
Dummy example to create several plots and plot them using ggarrange:
p1 <- ggplot(cars, aes(x=speed, y=dist)) + geom_point() + geom_smooth()
p2 <- p1
ggarrange(p1, p2,
nrow = 2, ncol = 1,
common.legend = TRUE,
legend="bottom",
labels=list(paste("a) ", 'My rate [%]'),
paste('b)', expression('My volume [m'^3*'/ha]'))), # How to change this???
align = c("hv"),
font.label = list(size = 10,
face = "plain",
color ="black"))
Wrong labeled output b:
Finally, I have found my answer based on this post. This works when replacing titles, but I bet it would work for ggarrange as well. Important it that it works with paranteses and a superscript.
p1 + ggtitle(expression(paste("a) ", "volume [m"^3, "/ha", "]")))

ggplot2 - Add extra space between two legend items

I've created a ggplot2 graph using the basic code below:
my_df %>%
ggplot(aes(conv_norm, vot_norm, color = language:poa)) +
geom_smooth(method = "glm", se=FALSE) +
theme(
...
)
[I've left out the formatting commands from the theme() layer]
And I got a graph that looks like this:
Now, my question is: how can I add extra space only in between two legend items? I've looked online and have found ways to increase the spacing between all items in the legend, but I only want extra spacing between the English items and the Spanish items. Is there a way to add a 1-in distance between these language groups?
Well, I don't know of an elegant, simple solution to do what you are asking to do... but by working with how legends are drawn and adjusting some of the elements, we can come up with a really "hacky" solution. ;)
Here's a sample dataset that kind of simulates what you shared, along with the plot:
set.seed(12345)
my_df <- data.frame(
lang = rep(c(paste('English',1:3), paste('Spanish',1:3)),2),
x = c(rep(0,6), rep(1,6)),
y = rnorm(12, 10,2))
library(ggplot2)
p <- ggplot(my_df, aes(x,y, color=lang)) + geom_line()
p
The approach here is going to be to combine all the following individual steps:
Add a "blank" legend entry. We do this by refactoring and specifying the levels of the column mydf$lang to include a blank entry in the correct position. This will be the final order of the items in the legend.
Use scale_color_manual() to set the colors of the legend items manually. I make sure to use "NA" for the blank entry.
Within scale_color_manual() I use the drop=FALSE setting. This includes all levels for a factor, even if there is no data on the plot to show. This makes our blank entry show on the legend.
Use the legend.background theme element to draw transparent boxes for the legend key items. This is so that you don't have a white or gray box for that blank entry.
Putting it all together you get this:
my_df$lang <- factor(my_df$lang, levels=c(paste('English',1:3), '', paste('Spanish',1:3)))
ggplot(my_df, aes(x,y, color=lang)) +
geom_line() +
scale_color_manual(
values=c(rainbow(6)[1:3], 'NA', rainbow(6)[4:6]),
drop=FALSE) +
theme( legend.key = element_rect(fill='NA') )
Alternatively, you could use guides(color=guide_legend(override.aes... to set the colors, but you need the drop=FALSE part within scale_color_manual() get the blank level to draw in the legend anyway.
Another option would be to create two separate legends. Either by using two different aesthetics, or you can use color twice, e.g with ggnewscale - thanks to user chemdork123 for the fake data +1.
library(tidyverse)
library(ggnewscale)
set.seed(12345)
my_df <- data.frame(
lang = rep(c(paste('English',1:3), paste('Spanish',1:3)),2),
x = c(rep(0,6), rep(1,6)),
y = rnorm(12, 10,2))
ggplot(mapping = aes(x,y)) +
geom_line(data = filter(my_df, grepl("English", lang)), aes(color=lang)) +
scale_color_brewer(NULL, palette = "Dark2") +
new_scale_colour() +
geom_line(data = filter(my_df, grepl("Spanish", lang)), aes(color=lang)) +
scale_color_brewer(palette = "Set1") +
guides(color = guide_legend(order = 1))
Created on 2021-04-11 by the reprex package (v1.0.0)

annotate edge of plot without changing plot limits or setting "expand" to 0

I have a ggplot object. I want to use annotate() to add a label to the top of the plot, so that the upper edge of the label is also the upper edge of the plot. When using default settings, this doesn't seem possible: adding an annotation at the upper edge of the plot causes the upper y-limit to increase.
One can get around this problem by specifying scale_y_continuous(expand = c(0, 0)) when creating the plot. But I don't want to do that, partly because I like the y limits created by the default expand setting. Given this constraint, is it possible to use annotate() to position a label at the top of the plot?
Here is a minimal example that demonstrates the problem:
library(ggplot2)
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
yMax <- layer_scales(p)$y$range$range[2] # upper y-limit
p + annotate("label", x = 30, y = yMax, vjust = "top", label = "X")
And here is the result:
You see that the annotation is not at the top of the plot. Instead, consistent with the default "expand" settings, the y-limit of the plot has changed.
Possible solutions:
Figure out the y-limits implied by the default expand setting. Then use scale_y_continuous() to both set the y limits and set expand = c(0, 0). This solution will give me the y limits that I want, and it will place the label appropriately. I know how to implement it, but it seems a bit cumbersome. It would also prevent other annotations at the top of the figure from changing the y-limit of the plot -- and I don't want the solution to affect annotations other than the one that I describe here.
Use annotation_custom(), which doesn't change plot limits in the same way. #baptiste suggests a solution like that in this answer to a different question. But annotation_custom() requires a grob. In practice, the annotations that I use may be more complicated than the label in this example, and I won't always know how to create them as a grob that can be passed to annotation_custom(). In addition, I've had some trouble positioning grobs with annotation_custom() while also specifying their exact sizes.
That said, I am quite open to annotation_custom()-based solutions. And perhaps there are solutions other than the two that I've sketched above.
I've read many SO posts on changing plot limits, but I haven't found any that speak to this problem.
A simple solution for that is setting y = Inf instead of using the maximum value found of the y-axis (yMax). The code would be like that then:
# load library
library(ggplot2)
# load data
data(mtcars)
# define plot
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p + annotate("label", x = 30, y = Inf, vjust = "top", label = "X")
Here is the output:
Let me know if this is what you're looking for.
Does this help?
library(ggplot2)
data(mtcars)
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
geom_text(label = "X", x = 30, y = max(mtcars$wt))

Preserving text size with `grid.arrange`

Problem
I have 4 graphs that I want to display using grid.arrange(). When I display them individually, they look like this:
But when I use grid.arrange(), they become distorted
with them individually looking like
Specific Issues:
The x-axis labels do not scale and overlap, making them unreadable.
The subtitles get cutoff.
Goal
I want to reproduce each plot exactly like the first ideal case in a grid with grid.arrange(). One possible way might be to convert each plot to an image and then use grid.arrange() but I don't know how to do this.
Reproducible Example
Below is an example reproducible code that shows the problem I am having.
p1 <- ggplot(subset(mtcars, cyl = 4), aes(wt, mpg, colour = cyl)) + geom_point() + labs(title = "TITLE-TITLE-TITLE-TITLE-TITLE-TITLE", subtitle = "-subtitle-subtitle-subtitle-subtitle-subtitle-subtitle-subtitle-") +theme(plot.title = element_text(hjust = 0.5),plot.subtitle = element_text(hjust = 0.5))
p2 <- ggplot(subset(mtcars, cyl = 4), aes(wt, mpg, colour = cyl)) + geom_point() + labs(title = "TITLE-TITLE-TITLE-TITLE-TITLE-TITLE", subtitle = "-subtitle-subtitle-subtitle-subtitle-subtitle-subtitle-subtitle-") +theme(plot.title = element_text(hjust = 0.5),plot.subtitle = element_text(hjust = 0.5))
grid.arrange(p1, p2, ncol = 2)
When you display those graphs individually they simply have more space. So, those are natural distortions and there are perhaps only three ways to solve that.
When exporting the combined graph, make it big enough. If the individual one looks good in 6x5 inches, then surely the combined one will look good in 12x10 inches.
Give correspondingly less space for the problematic parts: x-axis labels and the subtitle. For instance, use something like element_text(size = 6) for plot.subtitle and axis.title.x, add \n to the subtitles and even x-axis labels, try something like element_text(angle = 30) for the latter as well.
Get rid of something unnecessary. As #Richard Telford suggests in the comments, using facet_wrap should work better. That would be due to, e.g., not repeating the y-axis labels and, hence, giving more horizontal space.

Resources