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

is it possible to let ggsave utilize ggplot_build method? #6002

Closed
Yunuuuu opened this issue Jul 16, 2024 · 8 comments
Closed

is it possible to let ggsave utilize ggplot_build method? #6002

Yunuuuu opened this issue Jul 16, 2024 · 8 comments

Comments

@Yunuuuu
Copy link

Yunuuuu commented Jul 16, 2024

Hi, is it possible to let ggsave utilize ggplot_build method? in this way, all objects support ggplot_build can use ggsave function

I have developed a ggplot2 extension ggheat for creating heatmaps and I would like to utilize all the functions in ggplot, including ggsave. However, creating a new function with the same name would result in duplication. Additionally, since the package automatically loads ggplot, there is no need to create a duplicate function. Tt would be beneficial to incorporate the ggplot_build method, and it would make ggplot extension more powerful and allow for seamless integration with the existing ggplot function.

library(ggheat)
#> Loading required package: ggplot2
mat <- matrix(rnorm(81), nrow = 9)
rownames(mat) <- paste0("row", seq_len(nrow(mat)))
colnames(mat) <- paste0("column", seq_len(ncol(mat)))
x <- ggheat(mat) +
  scale_fill_viridis_c() +
  htanno_dendro(aes(color = branch), position = "top", k = 3L) +
  gganno_top(aes(y = value), data = rowSums) +
  geom_bar(stat = "identity", aes(fill = factor(.panel))) +
  scale_fill_brewer(name = NULL, palette = "Dark2") +
  gganno_left(aes(x = value), data = rowSums, size = 0.5) +
  geom_bar(
    aes(y = .y, fill = factor(.y)),
    stat = "identity",
    orientation = "y"
  ) +
  scale_x_reverse() +
  htanno_dendro(aes(color = branch),
    position = "left",
    size = unit(1, "null"),
    k = 4L
  ) +
  scale_x_reverse()
ggplot2::ggsave("ggheat.png",
  plot = x,
  width = 350, height = 350, units = "px"
)
#> Error in slot(x, name): no slot of name "theme" for this object of class "ggheatmap"
x

Created on 2024-07-17 with reprex v2.1.0
~

image

@teunbrand
Copy link
Collaborator

I'm not sure what you mean with 'incorporate the ggplot_build() method, would you mind clarifying a bit?

@Yunuuuu
Copy link
Author

Yunuuuu commented Jul 17, 2024

Something like this, other objects can utilize ggplot_build methods to use ggsave seamlessly.

ggsave <- function(filename, plot) {
  # before drawing, call `ggplot_build` or maybe `ggplotGrob`
  plot <- ggplot_build(plot)
  # ... here we do some other common work
  grid.draw(plot)
  invisible(filename)
}

@teunbrand
Copy link
Collaborator

Implicitly, it is used in the grid.draw.ggplot() method for typical ggplots. This should work fine for any subclass of ggplot.
If you have a plot with a different class, you should make a grid.draw.your_class() method that renders the plot. I don't think ggplot2 should change anything here.

Also, based on the error message, it seems like your class object doesn't have a theme element that is required for rendering a typical ggplot.

@yutannihilation
Copy link
Member

@Yunuuuu
In my understanding, it's your responsibility to make your ggheatmap object compatible with ggplot2. While you might be able to bypass the error by asking for a tweak in this specific case of ggsave(), but I'm afraid you'll encounter various errors on the functions that assume the input is an ordinary ggplot object. It's not realistic to ask the authors to modify their functions, especially it's an extension package.

I think it's a good attempt to utilize S4 system to overcome the limitation of S3, although I'm not sure whether it will succeed or not in the end. I'm not familiar with S4, but maybe you can override $ better?

library(ggheat)
#> Loading required package: ggplot2
p1 <- ggplot()
p1$theme
#> list()
p2 <- ggheat(matrix())
p2$theme
#> Error in slot(x, name): no slot of name "theme" for this object of class "ggheatmap"

Created on 2024-07-17 with reprex v2.1.0

@yutannihilation
Copy link
Member

@teunbrand

Implicitly, it is used in the grid.draw.ggplot() method for typical ggplots.

To be clear, the error happens before grid.draw().

bg <- calc_element("plot.background", plot_theme(plot))$fill %||% "transparent"

ggplot2/R/theme.R

Lines 600 to 601 in 25ad0b1

plot_theme <- function(x, default = get_theme()) {
theme <- x$theme

@Yunuuuu
Copy link
Author

Yunuuuu commented Jul 17, 2024

Thanks for your suggestion, @yutannihilation. It is not possible for ggheat to know the value of plot.background before it is built into a patchwork object, as the elements are simply a list of ggplot objects. However, I have found a solution by creating a custom method for the $ operator with a specific consideration for ggplot2. Here is an example of how it can be done in R:

#' Subset a `ggheatmap` object
#'
#' Used by [ggplot_build][ggplot2::ggplot_build]
#'
#' @param x A ggheatmap object
#' @param name A string of slot name in `ggheatmap` object.
#' @keywords internal
methods::setMethod("$", "ggheatmap", function(x, name) {
    if (name == "theme") {
        slot(x, "heatmap")$theme
    } else if (name == "plot_env") {
        slot(x, "plot_env")
    } else {
        cli::cli_abort(c(
            "`$` is just for internal usage for ggplot2 methods",
            i = "try to use `@` method instead"
        ))
    }
})

@Yunuuuu
Copy link
Author

Yunuuuu commented Jul 17, 2024

I have implemented another S4 class to override the +.gg method, as suggested by @teunbrand you. This approach was recommended in your previous interaction in stackoverflow, which can be found here: stackoverflow.com/questions/65817557/s3-methods-extending-ggplot2-gg-function. Initially, I tried using an S3 class and adding a gg element to the ggheat object. However, there was a conflict between the +.ggheat method and the +.gg method. In order to resolve this conflict, I decided to utilize an S4 class instead. I am pleased to report that the implementation is now functioning smoothly and is capable of performing all the tasks of Complexheatmap.

@Yunuuuu
Copy link
Author

Yunuuuu commented Aug 2, 2024

By using the tricks in #6002 (comment), I'm able to resolve the problem when using ggsave in ggheat package. It's well for me to close this issue. Thanks.

@Yunuuuu Yunuuuu closed this as completed Aug 2, 2024
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

3 participants