I would like to leave my subtitle centered without having to manually change the position by legend.position in theme(). If I select "bottom", the caption will be centered relative to the graphic frame rather than the image, making it necessary to make changes to the margins. Is there any way to center by some argument as in the image?
You will have to use a workaround such as extracting the legend then combine it with the original plot. Here is an example using get_legend and plot_grid functions from the cowplot package.
library(ggplot2)
library(cowplot)
#>
#> ********************************************************
#> Note: As of version 1.0.0, cowplot does not change the
#> default ggplot2 theme anymore. To recover the previous
#> behavior, execute:
#> theme_set(theme_cowplot())
#> ********************************************************
p1 <- ggplot(iris, aes(x = Species, y = Petal.Length)) +
geom_col(aes(fill = Species)) +
coord_flip() +
scale_fill_brewer(palette = 'Set2') +
theme_minimal(base_size = 14) +
theme(legend.position = 'bottom')
# extract the legend
p1_legend <- get_legend(p1)
# plot p1 and legend together
p2 <- plot_grid(p1 + theme(legend.position = 'none'), p1_legend,
nrow = 2, rel_heights = c(1, 0.1))
# comparison
plot_grid(p1, p2,
nrow = 2)
Created on 2019-12-25 by the reprex package (v0.3.0)
Related
How do I set the legend height or width to be 100% of the plot height/width regardless of the actual dimensions?
library(ggplot2)
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point()+
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.key.width=unit(0.1,"npc"))
Created on 2022-02-11 by the reprex package (v2.0.1)
Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#> setting value
#> version R version 4.1.0 (2021-05-18)
#> os Ubuntu 20.04.3 LTS
#> ─ Packages ───────────────────────────────────────────────────────────────────
#> ggplot2 * 3.3.5 2021-06-25 [1] CRAN (R 4.1.0)
#>
#> ──────────────────────────────────────────────────────────────────────────────
The only way I know of is to manually adjust the grid objects in the gtable of the plot. AFAIK, the guides are mostly defined in cm (rather than relative units), so getting them adapted to the panels is a bit of a pain. I'd also love to know a better way to do this.
library(ggplot2)
g <- ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point()+
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.key.width=unit(0.1,"npc"),
legend.margin = margin(), # pre-emptively set zero margins
legend.spacing.x = unit(0, "cm"))
gt <- ggplotGrob(g)
# Extract legend
is_legend <- which(gt$layout$name == "guide-box")
legend <- gt$grobs[is_legend][[1]]
legend <- legend$grobs[legend$layout$name == "guides"][[1]]
# Set widths in guide gtable
width <- as.numeric(legend$widths[4]) # save bar width (assumes 'cm' unit)
legend$widths[4] <- unit(1, "null") # replace bar width
# Set width/x of bar/labels/ticks. Assumes everything is 'cm' unit.
legend$grobs[[2]]$width <- unit(1, "npc")
legend$grobs[[3]]$children[[1]]$x <- unit(
as.numeric(legend$grobs[[3]]$children[[1]]$x) / width, "npc"
)
legend$grobs[[5]]$x0 <- unit(as.numeric(legend$grobs[[5]]$x0) / width, "npc")
legend$grobs[[5]]$x1 <- unit(as.numeric(legend$grobs[[5]]$x1) / width, "npc")
# Replace legend
gt$grobs[[is_legend]] <- legend
# Draw new plot
grid::grid.newpage()
grid::grid.draw(gt)
Created on 2022-02-11 by the reprex package (v2.0.1)
Please forgive me for double answers, but I believe this to be a totally different approach, and the credits for the idea go to #benson23.
We can use ggh4x::force_panelsizes() to set an absolute size for the panel and match the width of the bar. Upside is that it is reasonably easy to do, downside is that your plot's width doesn't automagically adapts to the window size anymore.
library(ggplot2)
library(ggh4x)
width <- unit(10, "cm")
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point() +
guides(colour = guide_colorbar(barwidth = width)) +
force_panelsizes(cols = width) +
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.spacing.x = unit(0, "cm"))
The process becomes slightly more complicated if a plot has multiple panels, but it is not undoable.
ncol <- 3
total_width <- unit(10, "cm")
# Optionally: replace `theme_get()` with actual theme you're using
spacing <- calc_element("panel.spacing.x", theme_get())
panel_widths <- (total_width - spacing * (ncol - 1)) / ncol
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point() +
guides(colour = guide_colorbar(barwidth = total_width)) +
facet_wrap(~ Species, ncol = ncol) +
force_panelsizes(cols = panel_widths) +
theme(
legend.title=element_blank(),
legend.position="bottom",
legend.spacing.x = unit(0, "cm"))
Created on 2022-02-11 by the reprex package (v2.0.1)
Disclaimer: I'm the author of {ggh4x}
Here I'll use a very manual way to set the legend width, which is guides(colour = guide_colorbar(barwidth = 25)). Just try out the barwidth parameter, you'll get one that fits the plot.
I'd also love to know a way to easily get the width of the plot, so that we don't need to manually set the barwidth.
UPDATE: As you can see in the comment under this answer, both #teunbrand and I agree that we can use a barwidth value close to the value of fig.width in Rmarkdown or width in ggsave if you are using these utilities to display your graph
ggplot(iris, aes(Petal.Width, Sepal.Width, color=Petal.Length))+
geom_point()+
guides(colour = guide_colorbar(barwidth = 25)) +
theme(
legend.title=element_blank(),
legend.position="bottom")
Adapted #teunbrand's ggplotGrob answer to work as a function. Added ggplotify to convert gtable back to ggplot. Note that it only works specifically for horizontal colorbars.
Original plot:
library(ggplot2)
library(ggplotify)
g <- ggplot(iris, aes(Petal.Width, Sepal.Width, color=Sepal.Width))+
geom_point()+
theme(legend.position="bottom")
g
Plot with full width colorbar legend:
#' #param x A ggplot object with colorbar legend along the width.
#'
full_width_legend <- function(x){
gt <- ggplotGrob(x)
# Extract legend
is_legend <- which(gt$layout$name == "guide-box")
legend <- gt$grobs[is_legend][[1]]
legend <- legend$grobs[legend$layout$name == "guides"][[1]]
# Set widths in guide gtable
width <- as.numeric(legend$widths[4]) # save bar width (assumes 'cm' unit)
legend$widths[4] <- unit(1, "null") # replace bar width
# Set width/x of bar/labels/ticks. Assumes everything is 'cm' unit.
legend$grobs[[2]]$width <- unit(1, "npc")
legend$grobs[[3]]$children[[1]]$x <- unit(
as.numeric(legend$grobs[[3]]$children[[1]]$x) / width, "npc"
)
legend$grobs[[5]]$x0 <- unit(as.numeric(legend$grobs[[5]]$x0) / width, "npc")
legend$grobs[[5]]$x1 <- unit(as.numeric(legend$grobs[[5]]$x1) / width, "npc")
# Replace legend
gt$grobs[[is_legend]] <- legend
return(ggplotify::as.ggplot(gt))
}
g1 <- full_width_legend(g)
g1
Created on 2022-02-11 by the reprex package (v2.0.1)
How can I display two plots in one row with R function ggarrange() so that they have the same dimensions, in particular the same height?
In this example, the second plot is a bit higher than the first plot. I would like to increase the size of a1_plot, so that it matches the size of a2_plot.
# required packages
library(ggplot2)
library(ggbreak)
library(directlabels)
library(ggpubr)
# make dataframe
df1 <- data.frame(first_column=c("value_1","value_2","value_3","value_4","value_4","value_5"),
second_column=c("123","123","325","325","656","656"),
third_column=c(12,13,1,19,200,360),
fourth_column=c(1,124,155,3533,5533,6666))
# plot 1
a1_plot <-
ggplot(df1, aes(x=third_column, y=fourth_column, colour=second_column)) +
scale_x_continuous(breaks = c(0,50,100,150,200,250,300)) +
ylab("Fourth column")+ xlab("Third column") +
scale_x_break(breaks = c(210,400)) +
geom_dl(mapping=aes(x=third_column, y=fourth_column, label=second_column),
method = list(dl.trans(x = x + 0.1), dl.combine("last.points"))) +
theme(legend.position = "none")
# plot 2
a2_plot <-
ggplot(data=df1)+
geom_point(aes(x=second_column, y=fourth_column) +
xlab("X axis")+ ylab("Y axis") +
theme(legend.position = "none")
# merge plot1 and plot2
ggarrange(print(a1_plot), print(a2_plot), labels = c('a1', 'a2'))
I was unable to change the height of plot 1. By adjusting the margins of plot 2, the problem has been solved.
theme(legend.position = "none", plot.margin = unit(x=c(3.6,5,3.9,0), units = "mm"))
Following is the sample code for the random chart:
library(ggplot2)
ggplot(mtcars,aes(x=mpg,y=wt,color=factor(vs)))+geom_line()+theme(legend.position="top",legend.direction = "horizontal",legend.title=element_blank(),legend.key = element_blank())
and following is the output from above code:
My requirement: Instead of having two lines denoted by 0 and 1 as legend elements, I only want legend texts "0" and "1" colored as "red" and "skyblue" (same as color of lines) and remove the lines. Is this possible?
I've written a string legend guide in the ggh4x package, that you might find useful.
Example:
library(ggplot2)
library(ggh4x)
ggplot(mtcars,aes(x=mpg,y=wt,color=factor(vs))) +
guides(colour = "stringlegend") +
geom_line() +
theme(legend.position="top",
legend.direction = "horizontal",
legend.title=element_blank(),
legend.key = element_blank())
Created on 2021-03-19 by the reprex package (v0.3.0)
You can use the ggtext package which has element_markdown() function that allows text formatting
The ggtext package provides simple Markdown and HTML rendering for ggplot2. Under the hood, the package uses the gridtext package for the actual rendering, and consequently it is limited to the feature set provided by gridtext.
Support is provided for Markdown both in theme elements (plot titles, subtitles, captions, axis labels, legends, etc.) and in geoms (similar to geom_text()). In both cases, there are two alternatives, one for creating simple text labels and one for creating text boxes with word wrapping.
library(RColorBrewer)
library(ggplot2)
library(ggtext)
my_col <- RColorBrewer::brewer.pal(unique(mtcars$vs), 'Dark2')
p <- ggplot(mtcars, aes(x = mpg, y = wt, color = factor(vs))) +
geom_line() +
theme_bw(base_size = 16) +
theme(
legend.position = "top", legend.direction = "horizontal",
legend.title = element_blank(), legend.key = element_blank()
)
p +
scale_color_manual(
labels = paste(
"<span style='color:",
my_col,
"'>",
unique(mtcars$vs),
"</span>"
),
values = my_col
) +
theme(legend.text = element_markdown(size = 16)) +
guides(color = guide_legend(override.aes = list(linetype = c(NA, NA))))
Created on 2021-03-18 by the reprex package (v1.0.0)
teunbrands custom guide is awesome. Just for completeness sake, here the fake legend option.
Technically, a legend is a very specific type of plot annotation. One of the most frequently asked questions is "how do I annotate outside the plot area" - which you can almost always answer with "create a second plot and stitch it to the main plot". Same here.
It requires a couple of tricks, in particular use of geom_blank for scaling, and then correct use of theme elements, which you can either pass to each plot or globally (with &)
The disadvantage of this approach is that you need to fiddle around with the coordinates to get the position right. but this should not be too much of an issue, except if you need to automate this.
P.S. the colors you are showing are neither "steelblue" nor "red" ... ;)
library(ggplot2)
library(patchwork)
p_main <-
ggplot(mtcars, aes(x = mpg, y = wt, color = factor(vs))) +
geom_line()
# semi - manual - chose x and y depending on the position relative to the other plot
# for labels: I used as.character just to be very explicit
df_legend <- data.frame(x = c(22,24), y = 3, labels = as.character(0:1))
p_legend <-
ggplot(mtcars, aes(x = mpg, y = wt)) +
geom_blank() +
geom_text(data = df_legend, aes(x, y, label = labels, color = labels)) +
theme_void()
p_legend / p_main &
plot_layout(heights = c(1, 10)) &
theme(legend.position = "none")
Created on 2021-03-20 by the reprex package (v1.0.0)
This is a follow up problem to this question. The OP asked for a way to arrange parts of a plot in specific distances. I think teunbrand gave a very good answer.
My own suggestion (extract the legend with cowplot, and stitch them to a plot in desired proportions) is not fully satisfactory, because it worked only "by chance" in the given example - the legend labels were long enough to center the legend grob into the viewport for the third plot.
Having shorter labels reveals the problem - when adding a grob, patchwork centres this grob, basically padding equally to all sides.
My question is, do you know of a way to control this padding behaviour?
Cowplot (or any other ggplot combining package for that sake) also very welcome.
library(tidyverse)
library(patchwork)
data <- midwest %>%
head(5) %>%
select(2,23:25) %>%
pivot_longer(cols=2:4,names_to="Variable", values_to="Percent") %>%
mutate(Variable=factor(Variable,
levels=c("percbelowpoverty","percchildbelowpovert","percadultpoverty"),
labels = paste0("perc", 1:3)))
p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col() +
scale_fill_manual(values = c("#CF232B","#942192","#000000")) +
theme(legend.background = element_rect(fill = "grey50"))
p_legend <- cowplot::get_legend(p1)
p_main <- p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col(show.legend = FALSE) +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))
p_main + plot_spacer() + p_legend +
plot_layout(widths = c(12.5, 1.5, 4)) &
theme(plot.margin = margin(),
plot.background = element_rect(colour = "black"))
Not so desired result - the legend grob (with grey background) should be aligned to the left plot border (black line)
Created on 2021-04-09 by the reprex package (v1.0.0)
As far as I get it the issue is not on patchworks side. Having a look at the layout of the legend's gtable we see that it is made up of 5 rows and 5 columns and that the legend is to be placed in the cell in the center:
p_legend <- cowplot::get_legend(p1)
p_legend
#> TableGrob (5 x 5) "guide-box": 2 grobs
#> z cells name
#> 99_a788e923bf245af3853cee162f5f8bc9 1 (3-3,3-3) guides
#> 0 (2-4,2-4) legend.box.background
#> grob
#> 99_a788e923bf245af3853cee162f5f8bc9 gtable[layout]
#> zeroGrob[NULL]
gtable::gtable_show_layout(p_legend)
Hence, when adding the legend patchwork centers is as demanded by the gtable layout.
One option to control the positioning or the padding of the legend would be to squash the first column via cowplot::gtable_squash_cols and if desired add some padding by adding a new column with the desired amount of padding via gtable::gtable_add_cols:
# Squash first column
p_legend <- cowplot::gtable_squash_cols(p_legend, 1)
# Add some padding by adding a new col
p_legend <- gtable::gtable_add_cols(p_legend, unit(.1, "cm"), pos = 1)
p_main <- p1 <-
ggplot(data=data, mapping=aes(x=county, y=Percent, fill=Variable)) +
geom_col(show.legend = FALSE) +
scale_fill_manual(values = c("#CF232B","#942192","#000000"))
p_main + plot_spacer() + p_legend +
plot_layout(widths = c(12.5, 1.5, 4)) &
theme(plot.margin = margin(),
plot.background = element_rect(colour = "black"))
I want to combine these two graphs :
p1 <- ggplot(iris, aes(Sepal.Length)) +
geom_density() +
facet_wrap(~ Species)
p2 <- ggplot(iris, aes(Sepal.Length)) +
geom_density()
To combine, I do :
multiplot(p1, p2, cols = 2)
But it is not the desired shape.
I would like the graph p2 has the same dimensions than others and is situated just next to the last faceted graph.
Thanks for help
Not sure if this is applicable in you generic case, but with facet_grid instead of facet_wrap, you can use the margins argument:
library(ggplot2)
ggplot(iris, aes(Sepal.Length)) +
geom_density() +
facet_grid(. ~ Species, margins = T)
If you question is more generic the answer probably lies in grid.arrange.
Something like this could be a start:
library(gridExtra)
grid.arrange(arrangeGrob(p1, p2,
widths = c(3,1),
heights = c(1,20),
layout_matrix = matrix(c(1,1,NA,2),2)))
As you can see there are several problems (different axes, top strip), but working with grid could gets complicated quickly.
This code should work:
p1 <- ggplot(iris, aes(Sepal.Length)) +
geom_density() +
ylim(limits = c(0, 1.25))+
facet_wrap(~ Species)
p2 <- ggplot(iris, aes(Sepal.Length)) +
geom_density() +
ggtitle("") + # ad empty title as place holder
labs(y = "", x = "") + # hide axis labels
ylim(limits = c(0, 1.25)) + # y axis values should be fixed in both plots
coord_fixed(ratio=20/1) + # ratio of x- and y-axis to reduce width of plot
theme(axis.ticks.y = element_blank(), axis.text.y = element_blank(), axis.line.y = element_blank(),
plot.margin=unit(c(0,0,0.65,-10), "lines")) # margin between plots = "0.65"
I fiddled a bit and used just different styling options to produce this result. If you have more plots than this one I would recommend to use one theme for all.
You can use either the multiplot function that you are already using
multiplot(p1, p2, cols = 2)
or you install the packages gridExtra and grid and use that one:
grid.arrange(p1, p2, ncol=2)
Hope this helps!