extending ggplot to take lists and make the internal elements of that list available to custom geoms

I'm sorry to cross post, but I'll try my luck here with this:

I repeat myself a bit here:

Say I have a new ggplot function that accepts lists:

ggplot.list <- function(data = NULL,
                           mapping = ggplot2::aes(),
                           ...,
                           environment = parent.frame()) {

    p <- ggplot2::ggplot(data=data[[1]],mapping=mapping,..., environment=environment)

    p$data_ext <- data[[2]]
    p
}

I create my list, and I plot the first data.frame:

l <- list(tibble(x=1:10, y=1:10), tibble(x=1:10+100, y =1:10+200))

ggplot(l) + geom_point(aes(x=x,y=y))

Ideally I would like to create another geom that by default takes the data_ext from the ggplot object. I could only solve it by piping the plot to a new function:

pipe_point2 <-function (plot, mapping = NULL, data = NULL, stat = "identity", position = "identity", 
                        ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
{

  plot +  layer(data = plot$data_ext, mapping = mapping, stat = stat, geom = GeomPoint,
          position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
          params = list(na.rm = na.rm, ...))
}

{ggplot(l) + geom_point(aes(x=x,y=y))} %>%  pipe_point2(aes(x=x,y=y))

But I would like to create a geom_point2() which I can then add with + to not confuse the users of my functions.

Thanks!

I think it would help if you provided a little more information about why you need information from the original plot when adding a geometry to the plot. I realize you are doing this for users and your case is slightly more complex than your example, but from what you have above, it isn't unreasonable to make a user type:

ggplot() +
  geom_point(mapping = aes(x, y), data = l[[1]]) +
  geom_point(mapping = aes(x, y), data = l[[2]])

This approach gives a user full control over their plot, and is unlikely ever to break.

If you still think that you need to access the plot object, you still want to use the + operator, and you are prepared to maintain this code, you can assign an S3 class to an object you'd like to add, and implement the ggplot_add() generic.

library(ggplot2)

custom_geom_point <- function(...) {
  layer <- geom_point(...)
  structure(list(layer = layer), class = "custom_layer")
}

ggplot_add.custom_layer <- function(object, plot, object_name) {
  x_mapping <- plot$mapping$x
  message("Ha! I know that the global aesthetic mapping for the `x` variable is ", x_mapping)
  plot + object$layer
}

ggplot(mpg, aes(x = cty, y = hwy)) +
  custom_geom_point()
#> Ha! I know that the global aesthetic mapping for the `x` variable is ~cty

Created on 2019-05-02 by the reprex package (v0.2.1)

1 Like

The problem is that the user doesn't need to know about the elements of the list. I'm dealing with my own class which is a list, with very different dataframes containing different aspects of EEG (electroencephalogram) data.
So I want that the default ggplot2 will take the " main" data.frame (this is easy to do), and other custom geoms will take the other dataframes of the list.

In any case, thanks!!!
I was able to do what I wanted in a toy example, it will look like this:

library(ggplot2)

custom_geom_point <- function(...) {
  layer <- geom_point(...)
  class(layer) <- c("custom_layer", class(layer))
layer
  }

ggplot_add.custom_layer <- function(object, plot, object_name) {
  object$mapping <- c(plot$data_ext, object$mapping)
  NextMethod()
}

ggplot(l, aes(x = x, y = y)) + geom_point() +
  custom_geom_point(color= "red")

It works, but does it make sense? Is there an advantage in putting the layer inside a list instead of adding another class to it?

I would avoid modifying the class of a Layer because it uses a different object system (ggproto vs. S3) and the internal Layer object might change in the future. I also think the other way is more clear, since your object is more like a specification of a layer rather than a layer itself.

Also, the code you wrote modifies the mapping, which probably isn't what you meant to do. If the user doesn't need to know about the data frames, they shouldn't be able to map columns from them (and if you were going to modify a mapping, you need to make sure it has the right class and doen't have any repeated names, neither of which is accomplished by c() in this case).

Other than that, it looks like you're on the right track!

yes, I ended up doing something like this:

ggplot_add.custom_layer <- function(object, plot, object_name) {
  object$data <- plot$data_ext
  NextMethod()
}

Thanks a lot!!! It works perfect now (also in my non-toy example)!

This topic was automatically closed 21 days after the last reply. New replies are no longer allowed.