Related
guides = "collect" does its job but it locates the legend in a way that it overlaps the plot. I would like it to be placed right in the middle of the empty bottom right corner, but it appears to be difficult since theme(legend.position = c(X,Y)) does not work with patchwork
Illustration of the issue:
This is the code I have for assembling 4 graphs I have using patchwork. Pretty sure there is a more elegant way to use theme() but I am quite new to patchwork and it worked for me so far, except for the legend positioning.
A similar issue was resolved here but it does not seem to help in my case.
#first panel
s_wpanels_final <- (dots & theme_bw() & theme(axis.title.x =
element_blank(), panel.grid.minor.y = element_blank())) +
#second panel
(g_box_tmax & theme_bw() & theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank(), panel.grid.minor.y = element_blank(),
axis.text.y=element_blank(), axis.ticks.y=element_blank(),
axis.title.x = element_blank(), legend.position = "none")) +
#third panel
(g_box_t0 & theme_bw() & theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank(), panel.grid.minor.y = element_blank(),
axis.text.y=element_blank(), axis.ticks.y=element_blank(),
axis.title.x = element_blank(), legend.position = "none")) +
#fourth panel
(tmax_box & theme_bw() & theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank(), axis.text.y=element_blank(),
axis.ticks.y=element_blank(), axis.title.y = element_blank(),
legend.position = "none")) +
guide_area()+
plot_layout(ncol=3, guides = "collect", widths=c(6,1,1), heights=c(6,1)) &
theme(legend.direction = "vertical", legend.box = "horizontal")
There are two issues with your code. First using + to glue your plots together and setting ncol=3 will place the guide_area in the second column of the second row. To center the legend I would suggest to use the design argument to specify the layout of the plot. Second, while the plot panels will adjust to the space set via the height and width arguments and the size of your plotting device, the legend will not, i.e. if the legend will not fit into the space given it will overlap with the surrounding panels. To fix that I would suggest to increase the widths of the second and third columns and the height of the second row. But as I said this also depends on the size of the plotting device.
Using some fake example plot based on mtcars(see below) let's first reproduce your issue:
library(ggplot2)
library(patchwork)
list(
dots,
g_box_tmax,
g_box_t0,
tmax_box,
guide_area()
) |>
wrap_plots() +
plot_layout(guides = "collect", widths = c(6, 1, 1), heights = c(6, 1), ncol = 3) &
theme(legend.direction = "vertical", legend.box = "horizontal")
However, specifying the layout via the design argument and increasing the height of the second row as well as the widths of the second and third columns works fine and centers the legend in the guide area:
design <-
"
ABC
DEE
"
list(
dots,
g_box_tmax,
g_box_t0,
tmax_box,
guide_area()
) |>
wrap_plots() +
plot_layout(guides = "collect", widths = c(6, 1.5, 1.5), heights = c(6, 1.5), design = design) &
theme(legend.direction = "vertical", legend.box = "horizontal")
PLOTS
dots <- ggplot(mtcars, aes(mpg, hp, color = factor(cyl), size = qsec)) +
geom_point() +
theme_bw() +
theme(
axis.title.x = element_blank(),
panel.grid.minor.y = element_blank()
)
g_box_tmax <- g_box_t0 <- ggplot(mtcars, aes(factor(cyl), hp, fill = factor(cyl))) +
geom_boxplot() +
theme_bw() +
theme(
axis.text.x = element_blank(),
axis.ticks.x = element_blank(), panel.grid.minor.y = element_blank(),
axis.text.y = element_blank(), axis.ticks.y = element_blank(),
axis.title.x = element_blank(), legend.position = "none"
)
tmax_box <- ggplot(mtcars, aes(mpg, factor(cyl), fill = factor(cyl))) +
geom_boxplot() +
theme_bw() +
theme(
axis.text.x = element_blank(),
axis.ticks.x = element_blank(), axis.text.y = element_blank(),
axis.ticks.y = element_blank(), axis.title.y = element_blank(),
legend.position = "none"
)
What causes the legend box (which is too big for the plot dimension) to be positioned there, is probably some quite clever patchwork code, and is related to guide_area (therefore my question title edit).
The below is a slightly unsatisfactory, but effective hack to modify the position. It's a bit of a trial and error. Simply give a negative margin to the legend box to the right and it will "drag" the box accordingly.
I've removed all the legend.position = "none" from your plots as this is not necessary with guides = "collect"
library(ggplot2)
library(patchwork)
p1 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p2 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p3 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p4 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p1 + p2 + p3 + p4 +
guide_area()+
plot_layout(ncol=3, guides = "collect", widths=c(6,1,1), heights=c(6,1)) &
theme(legend.direction = "vertical", legend.box = "horizontal",
legend.box.margin = margin(r = -1, unit = "in"))
How to change the plot.background in a 'geom_rect()' + 'coord.polar()' in a Donut ggplot graph?
I dont know what I´m missing, but I´w working in html black background style and needing to set panel as well plot background to black, but the graph sides of my ggplot are white and I need to know which attribute or command I need to use to turn sides to black too.
Below my code:
my_df %>%
ggplot(aes(ymax=max, ymin=min, xmax=4, xmin=3,fill=ResultCode)) +
geom_rect() +
geom_label( x=3.5, aes(y=labelPosition, label=label), size=4, color="white") +
coord_polar(theta="y") +
xlim(c(2, 4)) +
theme_void() +
theme(legend.position="none",
plot.background=element_rect(fill = "black"),
panel.background = element_rect(fill = "black"),
panel.border = element_blank(),
legend.key = element_blank(),
axis.ticks = element_blank(),
axis.text.y = element_blank(),
panel.grid = element_blank())
Below the resulted graph (see the "white" sides at right and left I need to fill with black)
The problem here is that ggplot by default calls grid::grid.newpage before drawing. This creates a blank (white) screen. It will then set up a square viewport to fit your plotting window because you are using coord_polar. Once it has done this, it considers the square area to be "the" plotting window. In a sense then, ggplot has no knowledge or control over these white areas. No theme element can touch it.
The solution is to explicitly call grid.newpage yourself, draw a black background manually, and then explicitly print your ggplot using the parameter newpage = FALSE. You could alternatively set the grid gpar parameters so that the background is black by default, but this is likely to have undesired side effects later on.
Here's a reprex with some made-up data:
my_df <- data.frame(max = c(160, 320), min = c(0, 161),
ResultCode = c("A","B"),
labelPosition = c(80, 240), label = c("A", "B"))
p <- my_df %>%
ggplot(aes(ymax=max, ymin=min, xmax=4, xmin=3,fill=ResultCode)) +
geom_rect() +
geom_label( x=3.5, aes(y=labelPosition, label=label), size=4, color="white") +
coord_polar(theta="y") +
xlim(c(2, 4)) +
theme(legend.position="none",
plot.background = element_rect(fill = "black", color = "black"),
panel.background = element_rect(fill = "black", color = "black"),
plot.margin = margin(0,0,0,0),
panel.border = element_blank(),
legend.key = element_blank(),
axis.ticks = element_blank(),
axis.text.y = element_blank(),
panel.grid = element_blank())
grid::grid.newpage()
grid::grid.draw(grid::rectGrob(gp = grid::gpar(fill = "black")))
print(p, newpage = FALSE)
This is a follow up of Question How to fit custom long annotations geom_text inside plot area for a Donuts plot?. See the accepted answer, the resulting plot understandably has extra blank area on the top and on the bottom. How can I get rid of those extra blank areas? I looked at theme aspect.ratio but this is not what I intend though it does the job but distorts the plot. I'm after cropping the plot from a square to a landscape form.
How can I do that?
UPDATE This is a self contained example of my use-case:
library(ggplot2); library(dplyr); library(stringr)
df <- data.frame(group = c("Cars", "Trucks", "Motorbikes"),n = c(25, 25, 50),
label2=c("Cars are blah blah blah", "Trucks some of the best in town", "Motorbikes are great if you ..."))
df$ymax = cumsum(df$n)
df$ymin = cumsum(df$n)-df$n
df$ypos = df$ymin+df$n/2
df$hjust = c(0,0,1)
ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)), #change width to adjust width of annotations
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
expand_limits(x = c(2, 4)) + #change x-axis range limits here
# no change to theme
theme(axis.title=element_blank(),axis.text=element_blank(),
panel.background = element_rect(fill = "white", colour = "grey50"),
panel.grid=element_blank(),
axis.ticks.length=unit(0,"cm"),axis.ticks.margin=unit(0,"cm"),
legend.position="none",panel.spacing=unit(0,"lines"),
plot.margin=unit(c(0,0,0,0),"lines"),complete=TRUE) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar("y", start=0) + scale_x_discrete()
And this is the result I'd like to find an answer to fix those annotated resulting blank spaces:
This is a multi-part solution to answer this and the other related question you've posted.
First, for changing the margins in a single graph, #Keith_H was on the right track; using plot.margin inside theme() is a convenient way. However, as mentioned, this alone won't solve the issue if the goal is to combine multiple plots, as in the case of the other question linked above.
To do that you'll need a combination of plot.margin and a specific plotting order within arrangeGrob(). You'll need a specific order because plots get printed in the order you call them, and because of that, it will be easier to change the margins of plots that are layered behind other plots, instead of in front of plots. We can think of it like covering the plot margins we want to shrink by expanding the plot on top of the one we want to shrink. See the graphs below for illustration:
Before plot.margin setting:
#Main code for the 1st graph can be found in the original question.
After plot.margin setting:
#Main code for 2nd graph:
ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)),
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar(theta='y') +
expand_limits(x = c(2, 4)) +
guides(fill=guide_legend(override.aes=list(colour=NA))) +
theme(axis.line = element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank(),
axis.text.y=element_blank(),
axis.text.x=element_blank(),
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "white"),
plot.margin = unit(c(-2, 0, -2, -.1), "cm"),
legend.position = "none") +
scale_x_discrete(limits=c(0, 1))
After combining plot.margin setting and arrangeGrob() reordering:
#Main code for 3rd graph:
p1 <- ggplot(mtcars,aes(x=1:nrow(mtcars),y=mpg)) + geom_point()
p2 <- ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)), #change width to adjust width of annotations
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar(theta='y') +
expand_limits(x = c(2, 4)) + #change x-axis range limits here
guides(fill=guide_legend(override.aes=list(colour=NA))) +
theme(axis.line = element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank(),
axis.text.y=element_blank(),
axis.text.x=element_blank(),
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "white"),
plot.margin = unit(c(-2, 0, -2, -.1), "cm"),
legend.position = "none") +
scale_x_discrete(limits=c(0, 1))
final <- arrangeGrob(p2,p1,layout_matrix = rbind(c(1),c(2)),
widths=c(4),heights=c(2.5,4),respect=TRUE)
Note that in the final code, I reversed the order you had in the arrangeGrob from p1,p2 to p2,p1. I then adjusted the height of the first object plotted, which is the one we want to shrink. This adjustment allows the earlier plot.margin adjustment to take effect, and as that takes effect, the graph printed last in order, which is P1, will start to take the space of what was the margins of P2. If you make one of these adjustments with out the others, the solution won't work. Each of these steps are important to produce the end result above.
You can set the plot.margins to negative values for the top and bottom of the plot.
plot.margin=unit(c(-4,0,-4,0),"cm"),complete=TRUE)
edit: here is the output:
I have been trying to shift my legend title across to be centered over the legend contents using the guide function. I've been trying to use the following code:
guides(colour=guide_legend(title.hjust = 20))
I thought of trying to make a reproducable example, but I think the reason it's not working has something to do with the above line not matching the rest of my code specifically. So here is the rest of the code I'm using in my plot:
NH4.cum <- ggplot(data=NH4_by_Date, aes(x=date, y=avg.NH4, group = CO2, colour=CO2)) +
geom_line(aes(linetype=CO2), size=1) + #line options
geom_point(size=3) + #point symbol sizes
#scale_shape_manual(values = c(1, 16)) + #manually choose symbols
theme_bw()+
theme(axis.text.x=element_text(colour="white"), #change x axis labels to white.
axis.title=element_text(size=12),
axis.title.x = element_text(color="white"), #Change x axis label colour to white
panel.border = element_blank(), #remove box boarder
axis.line.x = element_line(color="black", size = 0.5), #add x axis line
axis.line.y = element_line(color="black", size = 0.5), #add y axis line
legend.key = element_blank(), #remove grey box from around legend
legend.position = c(0.9, 0.6))+ #change legend position
geom_vline(xintercept=c(1.4,7.5), linetype="dotted", color="black")+ #put in dotted lines for season boundaries
scale_color_manual(values = c("#FF6600", "green4", "#0099FF"),
name=expression(CO[2]~concentration~(ppm))) + #manually define line colour
scale_linetype_manual(guide="none", values=c("solid", "solid", "solid")) + #manually define line types
scale_shape_manual(values = c(16, 16, 16)) + #manually choose symbols
guides(colour=guide_legend(title.hjust = 20))+
scale_y_continuous(expand = c(0, 0), limits = c(0,2200), breaks=seq(0,2200,200))+ #change x axis to intercept y axis at 0
xlab("Date")+
ylab(expression(Membrane~available~NH[4]^{" +"}~-N~(~mu~g~resin^{-1}~14~day^{-1})))+
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())+
geom_errorbar(aes(ymin = avg.NH4 - se.NH4, #set y error bars
ymax = avg.NH4 + se.NH4),
width=0.1)
I have tried doing the following instead with no luck:
guides(fill=guide_legend(title.hjust=20)
I have also adjusted the hjust value from values between -2 to 20 just to see if that made a difference but it didn't.
I'll try to attach a picture of the graph so far so you can see what I'm talking about.
I've looked through all the questions I can on stack overflow and to the best of my knowledge this is not a duplicate as it's specific to a coding error of my own somewhere.
Thank-you in advance!!
The obvious approach e.g.
theme(legend.title = element_text(hjust = .5))
didn't work for me. I wonder if it is related to this open issue in ggplot2. In any case, one manual approach would be to remove the legend title, and position a new one manually:
ggplot(mtcars, aes(x = wt, y = mpg, colour = factor(cyl))) +
geom_point() +
stat_smooth(se = FALSE) +
theme_bw() +
theme(legend.position = c(.85, .6),
legend.title = element_blank(),
legend.background = element_rect(fill = alpha("white", 0)),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank()) +
annotate("text", x = 5, y = 27, size = 3,
label = "CO[2]~concentration~(ppm)", parse = TRUE)
Output:
This might primarily be a result of me misunderstanding how panel.margin = unit(...) works in the theme() function...but I'm unable to customize margins in facet_wrap the way that I'd like. Basically, I want a facet_grid that looks like this, with facet text (i.e. strip.text) inset in each facet and no spcaing between each facet:
(I've left in the pink borders to show the dimensions of each facet)
So here's the code so far.
To set up the data and plot:
library(ggplot2)
library(grid)
p <- ggplot() +
geom_bar(data = mtcars, aes(x = cyl, y = qsec), stat = 'identity') +
facet_wrap( ~ carb, ncol = 3)
mytheme <- theme_minimal() + theme(
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
axis.title = element_blank(),
panel.margin = unit(0, "lines"),
panel.border = element_rect(colour = rgb(1.0, 0, 0, 0.5), fill=NA, size=1)
)
The standard plot
p + mytheme
Removing the strip.text completely
p + mytheme + theme(strip.text = element_blank())
Adding the strip.text and insetting it
p + mytheme +
theme(strip.text = element_text(size = rel(3.0), vjust = -4.0))
The re-inclusion of strip.text (and the increased relative size) increases the vertical margin between the two rows. So at this point, I don't know how to close the vertical gap between the top and bottom rows.
Too much negative margin
p + mytheme +
theme(strip.text = element_text(size = rel(3.0), vjust = -4.0),
panel.margin = unit(c(-2, -2), "lines"))
So how do I target just the panel.margin between the two rows?
Edit: Additional information. The space between the rows appears to be strip.background:
p + mytheme +
theme(strip.text = element_text(size = rel(3.0), vjust = -4.0),
panel.margin = unit(-1, "lines"),
strip.background = element_rect(fill = rgb(0, 1.0, 0, 0.2)))
Among the list of possible arguments to theme(), there is not only panel.margin ("margin around facet panels (unit)", see ?theme), but conveniently, you can also access one of the axes at a time, with panel.margin.x and panel.margin.y respectively ("horizontal/vertical margin around facet panels (unit; inherits from panel.margin)").
Therefore, while decreasing the margin below zero feels a bit like a hack, something like the following will do the job (you might have to adjust the value a little - unit(-2, "lines") worked best for me):
p + theme(strip.text = element_text(size = rel(3.0), vjust = -4.0),
panel.margin.y = unit(-2, "lines"))
If you use strip.text = element_blank(), then you should probably use panel.margin.y = unit(-0.5, "lines").