0

I am trying to create a global/spanning Y-axis title and a global/spanning X-axis title for a plot containing several ggplot objects arranged in a grid with clockwork. A global axis title is one that replaces the multiple axis titles along some margin of the plot with a single title, a shown in Step 2 to Step 4 below. Although this post shows how to create a global Y axis title, it does not show how to create do so for both the X and Y axes.

My issue is that my attempt at creating a global X-axis title places it far away from the other plots and shifts the Y-axis title so it is not centered in the plot. In the following reproducible examples, I show my starting point, successful creation of one global axis, and then (starting at Step 3) my failed attempts to add more alongside what I expected to produce. Does anyone know how I can add the global x-axes close to the rest of the plot without shifting the y-axis substantially toward the top of the figure?

1. X and Y axis titles for each cell

This is my "starting" plot: it has a 2x2 layout with axis titles for each cell. I'll want to replace these with fewer axis titles.

library(ggplot2)
library(patchwork)

# Create a list of 4 plots, then render
plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) +geom_point()), 4)
wrap_plots(plot_list, nrow = 2)

2. Add a global Y-axis title

The cell-specific Y-axis titles are successfully replaced with a single, global Y-axis title. However, each cell still has its own X-axis title.

# Same list of 4 plots
plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) +geom_point()), 4)

# Loop through list to remove each plot's Y-axis
for(i in 1:length(plot_list)){
  plot_list[[i]] <- plot_list[[i]] + theme(axis.title.y = element_blank())
}

# Create geom_text figure with Y-axis title that will fill the first column
y_lab_big <- ggplot() + 
  annotate(geom = "text", x = 1, y = 1, 
           label = "Here is some example long Y-axis text.", angle = 90) +
  coord_cartesian(clip = "off")+
  theme_void()

# Specify the text plot is NOT stacked and is much less wide
(y_lab_big | wrap_plots(plot_list, nrow = 2)) +
  plot_layout(widths = c(.1, 1))

3. Try to add a global X-axis title

I successfully replace each cell's x-axis title with a single, global x-axis title. However, it is far from the 2x2 grid and the y-axis title is now not centered on the 2x2 grid.

plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) + geom_point()), 4)

# Loop through list to remove BOTH X-axis and Y-axis from every plot
for(i in 1:length(plot_list)){
  plot_list[[i]] <- plot_list[[i]] + theme(axis.title.y = element_blank(),
                                 axis.title.x = element_blank())
}

# Create geom_text figures for *Y-axis* title (just like Example 2)
y_lab_big <- 
  ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "Here is some example long Y-axis text.", angle = 90) +
  coord_cartesian(clip = "off")+
  theme_void()

# Create geom_text figures for *X-axis* title (different angle)
x_lab_big <- 
  ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "Big X Text", angle = 0) +
  coord_cartesian(clip = "off")+
  theme_void()

# Same as Step 2 but add x-axis title using / to indicate that it should
# be stacked below the plot_list plots and specify heights to the thirst row
# (which should be the x-axis title) is relatively small.
(y_lab_big | (wrap_plots(plot_list, nrow = 2)) / x_lab_big) +
  plot_layout(widths = c(.1, 1),
              heights = c(1,1,.1))

Ideally looks like below, with y_title centered between [1.1] and [2,1] and x_title centered between [3,2] [3,3].

matrix(c("y_title", "y_title", NA,
         "scatter1","scatter2", "x_title",
         "scatter3","scatter4","x_title"), nrow = 3, ncol = 3)
#>      [,1]      [,2]       [,3]      
#> [1,] "y_title" "scatter1" "scatter3"
#> [2,] "y_title" "scatter2" "scatter4"
#> [3,] NA        "x_title"  "x_title"

4. Adding more than one global X-axis title

Here I attempt to add a second global X-axis title so that each column receives its own x-axis title but all rows share the same axis title. The issue remains the same.

plot_list <- rep(list(ggplot(mtcars, aes(mpg, disp)) + geom_point()), 4)

# Loop through list to remove BOTH X-axis and Y-axis from every plot
for(i in 1:length(plot_list)){
  plot_list[[i]] <- plot_list[[i]] + theme(axis.title.y = element_blank(),
                                           axis.title.x = element_blank())
}

# Create geom_text figures for *Y-axis* title (just like Example 2)
y_lab_big <-  ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "Here is some example long Y-axis text.", angle = 90) +
  coord_cartesian(clip = "off")+
  theme_void()

# Create geom_text figures for *X-axis* title (different angle)
x_lab_big <- ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "X Text 1", angle = 0) +
  coord_cartesian(clip = "off")+
  theme_void()

x_lab_big2 <- ggplot() + 
  annotate(geom = "text", x = 1, y = 1, label = "X Text 2", angle = 0) +
  coord_cartesian(clip = "off")+
  theme_void()

# Same as previous but add x-axis title using / to indicate that it should
# be stacked below the plot_list plots and specify heights to the thirst row
# (which should be the x-axis title) is relatively small.
(y_lab_big | (wrap_plots(plot_list, nrow = 2)) / (x_lab_big | x_lab_big2)) +
  plot_layout(widths = c(.1, 1),
              heights = c(1, .1))

Ideally looks like below, with y_title centered between [1,1] and [2,1], x_title1 centered in [3,2], and x_title3 centered in [3,3].

matrix(c("y_title", "y_title", NA,
         "scatter1","scatter2", "x_title1",
         "scatter3","scatter4","x_title2"), nrow = 3, ncol = 3)
#>      [,1]      [,2]       [,3]      
#> [1,] "y_title" "scatter1" "scatter3"
#> [2,] "y_title" "scatter2" "scatter4"
#> [3,] NA        "x_title1" "x_title2"

Although there are alternative solutions (ggplot: how to add common x and y labels to a grid of plots) I am trying to stay with using patchwork.

socialscientist
  • 3,759
  • 5
  • 23
  • 58

1 Answers1

1

Hopefully patchwork will gain this feature in one of the next releases. In the meanwhile one option would be to stick with patchwork and get some support from cowplot. Adapting my answer on R ggplot2 patchwork common axis labels to your case(s):

Note: That works nice if there is no legend. With a legend it gets probably more fiddling.

library(ggplot2)
library(patchwork)
library(cowplot)

p <- ggplot(mtcars, aes(mpg, disp)) +
  geom_point()
plot_list <- rep(list(p + labs(x = NULL, y = NULL)), 4)

p_axis <- p + labs(x = "Big X Text", y = "Here is some example long Y-axis text.")
x_axis <- cowplot::get_plot_component(p_axis, "xlab-b")
y_axis <- cowplot::get_plot_component(p_axis, "ylab-l")

design = "
FAB
FCD
#EE
"

c(plot_list, list(x_axis, y_axis)) |> 
  wrap_plots() + 
  plot_layout(heights = c(20, 20, 1), widths = c(1, 25, 25), design = design)

And for the e.g. two separate x titles you could do:

Note: For that case another option would be to remove the axis titles for only the top two plots.


p_axis1 <- p + labs(x = "X Text 1", y = "Here is some example long Y-axis text.")
p_axis2 <- p + labs(x = "X Text 2", y = "Here is some example long Y-axis text.")
x_axis1 <- cowplot::get_plot_component(p_axis1, "xlab-b")
x_axis2 <- cowplot::get_plot_component(p_axis2, "xlab-b")

design = "
GAB
GCD
#EF
"


c(plot_list, list(x_axis1, x_axis2, y_axis)) |> 
  wrap_plots() + 
  plot_layout(heights = c(20, 20, 1), widths = c(1, 25, 25), design = design)

socialscientist
  • 3,759
  • 5
  • 23
  • 58
stefan
  • 90,330
  • 6
  • 25
  • 51
  • (1) When you use `cowplot::get_plot_component(p_axis1, "xlab-b")` what is the "-b" in `xlab-b`? I see elsewhere you use `xlab-l`. (2) A general `patchwork` question relevant here: are legends counted as their own columns when `guide = "collect"` is used? As in, would `widths = c(1, 25, 25, 50)` give the legends collected on the right-hand side of the plot more space? Not actually sure if there's a way to get the dimensionality of the `patchwork` object. – socialscientist Jul 30 '22 at 07:54
  • I also experimented with this with various legend positioning. If the legend positions are bottom or top, everything adjusts accordingly with no issue. With legends on default placement (right), the x-axis title positions itself seemingly in the center of the entire figure - which will be a little to the right of center. One idea is to create MANY columns and spread all objects over many cells. I think the larger the size of the grid, the smaller the bias off center will be. – socialscientist Jul 30 '22 at 07:57
  • 1
    `xlab-b` is the "b"ottom x-axis title, `ylab-l" the "l"eft y-axis title. – stefan Jul 30 '22 at 07:57
  • 1
    Yeah. The legends are an issue. One option would be to take a similar approach to extract the legend via `cowplot::get_legend` and add it to the patch. – stefan Jul 30 '22 at 07:58