Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] labs for created plots #43

Closed
dtelad11 opened this issue Mar 15, 2018 · 31 comments
Closed

[Feature Request] labs for created plots #43

dtelad11 opened this issue Mar 15, 2018 · 31 comments

Comments

@dtelad11
Copy link

dtelad11 commented Mar 15, 2018

Would it be possible to add a global labs function that will affect the created plot?

Title is straightforward, sometimes I have 3-4 figures and I want all of them to share one major title.
For X- and Y-axis labels, I often patchwork 4, 6, or 9 figures which have the same X- and Y-axis labels, I want to show one "big" label for each axis rather than many small labels.

Edit: As @thomasp85 points out below, plot_annotation takes care of this for the title, subtitle, and caption.

@thomasp85
Copy link
Owner

plot_annotation() already solves this for title, subtitle and caption. I might consider adding axis titles to this as well...

@dtelad11
Copy link
Author

Thanks for the clarification. I updated the issue title and description accordingly.

@slowkow
Copy link

slowkow commented Mar 16, 2018

Thanks for opening this issue, and thanks for mentioning plot_annotation()!

It'd be great to see plot_annotation() in the README.md

Example

Here's why I prefer patchwork instead of gridExtra:

set.seed(42)
dat <- data.frame(
  x = rnorm(100),
  y = rnorm(100),
  z = rnorm(100),
  group = sample(c("A", "B"), size = 100, replace = TRUE)
)

library(ggplot2)

p1 <- ggplot(dat, aes(x, y, color = z)) +
  geom_point(size = 5) +
  labs(title = "Hello")

p2 <- ggplot(dat, aes(x, y, color = group)) +
  geom_point(size = 5) +
  theme(legend.position = "bottom")

Patchwork

library(patchwork)

p1 + p2 + plot_annotation(
  title = "patchwork",
  caption = "This is the caption.",
  theme = theme(
    plot.title = element_text(size = 20),
    plot.caption = element_text(size = 20)
  )
)

Notice the plot panels are perfectly aligned, and it is easy to change the theme of the title and caption with the theme() function.

gridExtra

library(gridExtra)

grid.arrange(
  p1, p2, ncol = 2,
  top = "gridExtra",
  bottom = "This is a caption."
)

Notice that the plot panels are not aligned, and you have to use gpar() to change the theme of the text.

Session info
devtools::session_info()
#> Session info -------------------------------------------------------------
#>  setting  value                       
#>  version  R version 3.4.4 (2018-03-15)
#>  system   x86_64, darwin17.4.0        
#>  ui       unknown                     
#>  language (EN)                        
#>  collate  en_US.UTF-8                 
#>  tz       America/New_York            
#>  date     2018-03-16
#> Packages -----------------------------------------------------------------
#>  package    * version    date       source                              
#>  backports    1.1.2      2017-12-13 CRAN (R 3.4.4)                      
#>  base       * 3.4.4      2018-03-16 local                               
#>  colorspace   1.3-2      2016-12-14 CRAN (R 3.4.3)                      
#>  compiler     3.4.4      2018-03-16 local                               
#>  curl         3.1        2017-12-12 CRAN (R 3.4.3)                      
#>  datasets   * 3.4.4      2018-03-16 local                               
#>  devtools     1.13.5     2018-02-18 CRAN (R 3.4.3)                      
#>  digest       0.6.15     2018-01-28 CRAN (R 3.4.3)                      
#>  evaluate     0.10.1     2017-06-24 CRAN (R 3.4.4)                      
#>  ggplot2    * 2.2.1.9000 2018-03-15 Github (thomasp85/ggplot2@f1ba983)  
#>  graphics   * 3.4.4      2018-03-16 local                               
#>  grDevices  * 3.4.4      2018-03-16 local                               
#>  grid         3.4.4      2018-03-16 local                               
#>  gridExtra  * 2.3        2017-09-09 CRAN (R 3.4.4)                      
#>  gtable       0.2.0      2016-02-26 CRAN (R 3.4.3)                      
#>  htmltools    0.3.6      2017-04-28 CRAN (R 3.4.3)                      
#>  httr         1.3.1      2017-08-20 CRAN (R 3.4.3)                      
#>  knitr        1.20       2018-02-20 CRAN (R 3.4.4)                      
#>  labeling     0.3        2014-08-23 CRAN (R 3.4.3)                      
#>  lazyeval     0.2.1      2017-10-29 CRAN (R 3.4.3)                      
#>  magrittr     1.5        2014-11-22 CRAN (R 3.4.3)                      
#>  memoise      1.1.0      2017-04-21 CRAN (R 3.4.3)                      
#>  methods    * 3.4.4      2018-03-16 local                               
#>  mime         0.5        2016-07-07 CRAN (R 3.4.3)                      
#>  munsell      0.4.3      2016-02-13 CRAN (R 3.4.3)                      
#>  patchwork  * 0.0.1      2018-03-15 Github (thomasp85/patchwork@49e6ba4)
#>  pillar       1.2.1      2018-02-27 CRAN (R 3.4.3)                      
#>  plyr         1.8.4      2016-06-08 CRAN (R 3.4.3)                      
#>  R6           2.2.2      2017-06-17 CRAN (R 3.4.3)                      
#>  Rcpp         0.12.16    2018-03-13 CRAN (R 3.4.3)                      
#>  rlang        0.2.0.9000 2018-03-15 Github (tidyverse/rlang@1b81816)    
#>  rmarkdown    1.9        2018-03-01 CRAN (R 3.4.4)                      
#>  rprojroot    1.3-2      2018-01-03 CRAN (R 3.4.4)                      
#>  scales       0.5.0.9000 2018-03-15 Github (hadley/scales@d767915)      
#>  stats      * 3.4.4      2018-03-16 local                               
#>  stringi      1.1.7      2018-03-12 CRAN (R 3.4.3)                      
#>  stringr      1.3.0      2018-02-19 CRAN (R 3.4.3)                      
#>  tibble       1.4.2      2018-01-22 CRAN (R 3.4.3)                      
#>  tools        3.4.4      2018-03-16 local                               
#>  utils      * 3.4.4      2018-03-16 local                               
#>  withr        2.1.2      2018-03-15 Github (jimhester/withr@79d7b0d)    
#>  xml2         1.2.0      2018-01-24 CRAN (R 3.4.4)                      
#>  yaml         2.1.18     2018-03-08 CRAN (R 3.4.3)

@thiagosfsilva
Copy link

Came here to suggest this feature for axis titles, so count me in as +1

@jackhump
Copy link

+1 for adding this to the README. The plot_annotation() function is really handy so should be made clearer. Thanks for creating such a great package!

@prosoitos
Copy link

prosoitos commented Sep 1, 2018

Huge fan of patchwork here and also hoping to see the option to have joint y axis for several plots.

Also agree that updating the README would be great.

Thanks @thomasp85 for the really cool work!

@steveharoz
Copy link
Collaborator

Documentation for plot_annotation() was added to the readme in c4545d5

@JenspederM
Copy link

I see that this issue has been closed. Does this mean that the feature (global labs) has been added or that it is no longer considered?

@jaseeverett
Copy link

Thanks @thomasp85 for this wonderful package.

I would love to see x and y labels added to the plot_annotation() function. Like @dtelad11, I regularly make sets of subplots with the same axes. A single label for each would be amazing.

Will this be forthcoming or have you closed this feature request because it is not feasible?

Thanks again

@eipi10
Copy link

eipi10 commented Feb 12, 2020

Just wanted to add another request for global x- and y-axis titles with plot_annotation. Several plots in a single column with a common x-axis is a frequent use case for me. At the moment, I typically do something like this:

library(tidyverse)
library(patchwork)

# Create list of plots with common x-axis column
# Remove all x-axis titles
pl = syms(c("disp", "hp", "wt")) %>% 
  map(~ggplot(mtcars, aes(mpg, !!.x, colour=cyl)) + 
        geom_point() +
        labs(x="Miles per Gallon") +
        theme(axis.title.x=element_blank()))

# Add back x-axis title to bottom plot
pl[[length(pl)]] = pl[[length(pl)]] + theme(axis.title.x=element_text())

# Lay out plots
pl %>% 
  wrap_plots(ncol=1) + 
  plot_layout(guides="collect") + 
  plot_annotation(title="Compare mpg against other variables")

Another option would be to use the caption as a kludge to get a global x-axis title, but this precludes using the caption feature for an actual caption and is hackish:

pl %>% 
  wrap_plots(ncol=1) + 
  plot_layout(guides="collect") + 
  plot_annotation(title="Compare mpg against other variables",
                  caption="Miles per Gallon", 
                  theme=theme(plot.caption=element_text(hjust=0.5, size=12, margin=margin(t=0))))

Would it be possible to add an option to create a global x-axis title (and/or global y-axis title) using plot_annotation()?

As another potential option, what about creating a "collect" option for x-axis and/or y-axis titles for cases where the same axis title is used in multiple plots. Something like this, where axis.title.x="collect" would strip the individual x-axis titles and add a single x-axis title at the bottom:

pl = syms(c("disp", "hp", "wt")) %>% 
  map(~ggplot(mtcars, aes(mpg, !!.x, colour=cyl)) + 
        geom_point() + 
        labs(x="Miles per Gallon"))

pl %>% 
  wrap_plots(ncol=1) + 
  plot_layout(guides="collect",
              axis.title.x="collect") + 
  plot_annotation(title="Compare mpg against other variables")

Maybe that's more trouble than it's worth, since such a feature would have to deal with cases where a user chooses "collect" even when each plot has a different x-axis title, or in cases where the plot layout has more than one column. Even then, in the first case you could return a warning. In the second, assuming all plots in a given column have the same x-axis title, "collect" could operate by column.

@melissagwolf
Copy link

I would find a common axis useful as well!

@benscarlson
Copy link

Hello, I'd just like to add my support for global axes labels!

Right now I individually hide axes labels for certain panels so that it appears as if there is global axes labels, but this is a cumbersome, manual process. For example, if I have p1 + p2 + p3, I hide all y labels except for p1, and hide all x labels except for p2. It would be much cleaner to have global axes labels.

@teaguescott
Copy link

Agreed, global axes would be great.

I really like the simplicity of patchwork. It's a bummer to have to revert to using another package to draw multiple plots together...

@schnebeni
Copy link

I would also love if a global axis option was added :c

@stemangiola
Copy link

+1!

@GeorgeAthana
Copy link

Yes please to a global axis option

@msto
Copy link

msto commented Aug 18, 2020

Based on this stackoverflow post, I've found you can add the same axis label to every subplot with the following:

patchwork_object & 
  xlab("xlabel") &
  ylab("ylabel")

@thomasp85 it's not super clear to me why the ampersand works here instead of the usual plus sign, maybe you could elaborate?

I agree with the posters above that it would still be helpful to update plot_annotation() to permit adding a single label to each axis, like you'd see with ggplot's facet_wrap() + xlab(). One, the figures would be less cluttered without redundant axis labels on each subplot, and two, it would be more discoverable than the ampersand syntax.

@mingsu
Copy link

mingsu commented Sep 15, 2020

I hacked a function to create global x label and y label, in an ugly way. I post here for anyone's interest.

function

add_global_label <- function(pwobj, Xlab = NULL, Ylab = NULL, Xgap = 0.03, Ygap = 0.03, ...) {
    ylabgrob <- patchwork::plot_spacer()
    if (!is.null(Ylab)) {
        ylabgrob <- ggplot() +
            geom_text(aes(x = .5, y = .5), label = Ylab, angle = 90, ...) +
            theme_void()
    }
    if (!is.null(Xlab)) {
        xlabgrob <- ggplot() +
            geom_text(aes(x = .5, y = .5), label = Xlab, ...) +
            theme_void()
    }
    if (!is.null(Ylab) & is.null(Xlab)) {
        return((ylabgrob + patchworkGrob(pwobj)) + 
            patchwork::plot_layout(widths = 100 * c(Ygap, 1 - Ygap)))
    }
    if (is.null(Ylab) & !is.null(Xlab)) {
        return((ylabgrob + pwobj) + 
            (xlabgrob) +
            patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
                                   widths = c(0, 100),
                                   design = "
                                   AB
                                   CC
                                   "
            ))
    }
    if (!is.null(Ylab) & !is.null(Xlab)) {
        return((ylabgrob + pwobj) + 
            (xlabgrob) +
            patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
                                   widths = 100 * c(Ygap, 1 - Ygap),
                                   design = "
                                   AB
                                   CC
                                   "
            ))
    }
    return(pwobj)
}

How to use:

# example 
require(ggplot2)
require(patchwork)
     p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
     p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))
     p3 <- ggplot(mtcars) + geom_bar(aes(gear)) + facet_wrap(~cyl)
     p4 <- ggplot(mtcars) + geom_bar(aes(carb))
     p5 <- ggplot(mtcars) + geom_violin(aes(cyl, mpg, group = cyl))
    
     add_global_label((p1 + p2 + p3 + p4 + p5),
                                Ylab = "Global Y title",
                         #       size = 8,
                         #      Ygap = 0.04
)

# or use pipe
require(magrittr)
(p1 + p2 + p3 + p4 + p5) %>%
add_global_label(Ylab = "Global Y title",
                          #       size = 8,
                         #      Ygap = 0.04
)

@basilkhuder
Copy link

I hacked a function to create global x label and y label, in an ugly way. I post here for anyone's interest.

function

add_global_label <- function(pwobj, Xlab = NULL, Ylab = NULL, Xgap = 0.03, Ygap = 0.03, ...) {
    ylabgrob <- patchwork::plot_spacer()
    if (!is.null(Ylab)) {
        ylabgrob <- ggplot() +
            geom_text(aes(x = .5, y = .5), label = Ylab, angle = 90, ...) +
            theme_void()
    }
    if (!is.null(Xlab)) {
        xlabgrob <- ggplot() +
            geom_text(aes(x = .5, y = .5), label = Xlab, ...) +
            theme_void()
    }
    if (!is.null(Ylab) & is.null(Xlab)) {
        return((ylabgrob + patchworkGrob(pwobj)) + 
            patchwork::plot_layout(widths = 100 * c(Ygap, 1 - Ygap)))
    }
    if (is.null(Ylab) & !is.null(Xlab)) {
        return((ylabgrob + pwobj) + 
            (xlabgrob) +
            patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
                                   widths = c(0, 100),
                                   design = "
                                   AB
                                   CC
                                   "
            ))
    }
    if (!is.null(Ylab) & !is.null(Xlab)) {
        return((ylabgrob + pwobj) + 
            (xlabgrob) +
            patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
                                   widths = 100 * c(Ygap, 1 - Ygap),
                                   design = "
                                   AB
                                   CC
                                   "
            ))
    }
    return(pwobj)
}

How to use:

# example 
require(ggplot2)
require(patchwork)
     p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
     p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))
     p3 <- ggplot(mtcars) + geom_bar(aes(gear)) + facet_wrap(~cyl)
     p4 <- ggplot(mtcars) + geom_bar(aes(carb))
     p5 <- ggplot(mtcars) + geom_violin(aes(cyl, mpg, group = cyl))
    
     add_global_label((p1 + p2 + p3 + p4 + p5),
                                Ylab = "Global Y title",
                         #       size = 8,
                         #      Ygap = 0.04
)

# or use pipe
require(magrittr)
(p1 + p2 + p3 + p4 + p5) %>%
add_global_label(Ylab = "Global Y title",
                          #       size = 8,
                         #      Ygap = 0.04
)

This is excellent! Thank you!

@Cameron-Fairfield
Copy link

Cameron-Fairfield commented Oct 15, 2020

I hacked a function to create global x label and y label, in an ugly way. I post here for anyone's interest.

function

add_global_label <- function(pwobj, Xlab = NULL, Ylab = NULL, Xgap = 0.03, Ygap = 0.03, ...) {
    ylabgrob <- patchwork::plot_spacer()
    if (!is.null(Ylab)) {
        ylabgrob <- ggplot() +
            geom_text(aes(x = .5, y = .5), label = Ylab, angle = 90, ...) +
            theme_void()
    }
    if (!is.null(Xlab)) {
        xlabgrob <- ggplot() +
            geom_text(aes(x = .5, y = .5), label = Xlab, ...) +
            theme_void()
    }
    if (!is.null(Ylab) & is.null(Xlab)) {
        return((ylabgrob + patchworkGrob(pwobj)) + 
            patchwork::plot_layout(widths = 100 * c(Ygap, 1 - Ygap)))
    }
    if (is.null(Ylab) & !is.null(Xlab)) {
        return((ylabgrob + pwobj) + 
            (xlabgrob) +
            patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
                                   widths = c(0, 100),
                                   design = "
                                   AB
                                   CC
                                   "
            ))
    }
    if (!is.null(Ylab) & !is.null(Xlab)) {
        return((ylabgrob + pwobj) + 
            (xlabgrob) +
            patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
                                   widths = 100 * c(Ygap, 1 - Ygap),
                                   design = "
                                   AB
                                   CC
                                   "
            ))
    }
    return(pwobj)
}

How to use:

# example 
require(ggplot2)
require(patchwork)
     p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
     p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))
     p3 <- ggplot(mtcars) + geom_bar(aes(gear)) + facet_wrap(~cyl)
     p4 <- ggplot(mtcars) + geom_bar(aes(carb))
     p5 <- ggplot(mtcars) + geom_violin(aes(cyl, mpg, group = cyl))
    
     add_global_label((p1 + p2 + p3 + p4 + p5),
                                Ylab = "Global Y title",
                         #       size = 8,
                         #      Ygap = 0.04
)

# or use pipe
require(magrittr)
(p1 + p2 + p3 + p4 + p5) %>%
add_global_label(Ylab = "Global Y title",
                          #       size = 8,
                         #      Ygap = 0.04
)

Thanks for this, really useful code. Couple of issues that are easy to overcome with it. First is that if using plot_layout() from patchwork for other purposes must be done after e.g.

require(magrittr)
(p1 + p2 + p3 + p4 + p5) %>%
add_global_label(Ylab = "Global Y title",
                          #       size = 8,
                         #      Ygap = 0.04
 ) + plot_layout(guides = "collect")

Second more a comment for other to be aware. If plots have been passed through coord_flip then this function treats original Y axis as X-axis so to change the original Y-axis need to use Xlab argument

@steveb-123
Copy link

steveb-123 commented Nov 16, 2020

+1 for a complete implementation of this functionality

The hack fom @mingsu, while super useful (thank you!) does not align with the plot area only, but the full plot objects (including axis labels).

I would expect the "count" to be aligned vertically at the gap between the two plots:

offset_axis

library(tidyverse)
library(patchwork)

source("./add_global_label.R")

zoo <- tibble(
  skin = c("a", "b", "b", "a", "c"),
  axis_title = c("a", "a", "a", "big word", "a"))


p1 <- ggplot(zoo, aes(x=axis_title)) + geom_bar() +
  theme(axis.title.y=element_blank(), axis.text.x = element_text(angle=90))
p2 <- ggplot(zoo, aes(x=skin)) + geom_bar() +
  theme(axis.title.y=element_blank(), axis.text.x = element_blank(), axis.title.x = element_blank())

wrap_plots(p2,p1,ncol=1) %>% add_global_label(Ylab="count", Ygap=0.05)
ggsave("offset_axis.png", width=7, height=7, units="cm")

@MattCowgill
Copy link

I agree this would be a very useful feature. It's unclear to me why the issue was closed.

@tjebo
Copy link

tjebo commented Mar 24, 2021

another workaround is offered here: https://stackoverflow.com/a/66778622/7941188

@lpmorenoc
Copy link

lpmorenoc commented May 12, 2021

One option apparently is wrap_elements https://patchwork.data-imaginist.com/articles/guides/assembly.html
So for the y label it could be used wrap_elements(grid::textGrob('Text on left side')) + p1
However, it did not work for me because it creates a lot of space between my wrap_plots and the text:
image

Even when we define different widths with plot_playout like the code below:

name1 <- grid::textGrob('Text on left side', rot=90)
name2 <-grid::textGrob('Text on the bottom')
a <-(wrap_elements(name1, clip=F) | 
    wrap_plots(Fig8a,fig8b,fig8c,fig8d,fig8e,fig8f, ncol=3)) 
a+ 
  plot_layout(guides='collect', widths=c(.001, 1) ) &
  theme(legend.position='bottom',legend.title=element_text(face="bold", size=16),
        legend.text = element_text(size=14),
        legend.background = element_rect(linetype = "solid", colour="black"),
        plot.margin=unit(c(0.5,0.5,0,0),"cm")) &
  xlab("") & ylab("") 

@MarinkavP
Copy link

Despite the different options to get a centered x-axis it would still be nice to have a separate function for this. The mentioned solutions don't work well with the auto-tag feature.

@kmichelson
Copy link

I have to agree - having global axis labels would be extremely useful, and makes sense in the plot_annotation parameters.

@nipnipj
Copy link

nipnipj commented Oct 28, 2021

Is Global axis ready?

@brunomioto
Copy link

+1

@MattCowgill
Copy link

@thomasp85 would you consider a PR that implemented this?

@kinnaryshah
Copy link

I agree, this would be a very helpful feature.

@julianbarg
Copy link

This has been implemented in #337

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests