rggplot2ggproto

Passing an extra parameter to a custom geom in ggplot2


I am creating a custom geom and would like it to take an extra parameter, called showpoints, which does something or other to the actual plot. For example, by setting it to FALSE, the geom actually returns a zeroGrob(). I have found a way of doing that, but (i) it is clunky and somewhat weird and (ii) I don't fully understand what I am doing, which is a bad sign. The problem is that when you are defining a new stat, it is possible to run setup_params, but geoms don't have it:

Compared to Stat and Position, Geom is a little different because the execution of the setup and compute functions is split up. setup_data runs before position adjustments, and draw_layer is not run until render time, much later. This means there is no setup_params because it's hard to communicate the changes.

[Source]

Basically, the code I have works in the sense that you can use the additional parameter to suppress drawing of the points:

# draw the points by default
ggplot(mpg, aes(displ, hwy)) + geom_simple_point()

# suppresses drawing of the points
ggplot(mpg, aes(displ, hwy)) + geom_simple_point(showpoints=FALSE)

Here is my code so far, based on extending ggplot2 tutorial:

## Return the grob to draw. The additional parameter,
## showpoints, determines whether a points grob should be returned,
## or whether zeroGrob() should take care of not showing the points
.draw_panel_func <- function(data, panel_params, coord) {
  coords <- coord$transform(data, panel_params)
  showpoints <- unique(data$showpoints)
  cat("showpoints=", as.character(showpoints), "\n")
  if(!is.null(showpoints) && is.logical(showpoints) && !showpoints) {
    return(zeroGrob())
  } else {
    return(
      grid::pointsGrob(coords$x, coords$y,
        pch = coords$shape,
        gp = grid::gpar(col = coords$colour))
   )
 }
}

## definition of the new geom. setup_data inserts the parameter 
## into data, therefore making it accessible for .draw_panel_func
GeomSimplePoint <- ggproto("GeomSimplePoint", Geom,
  required_aes = c("x", "y"),
  default_aes = aes(shape = 19, colour = "black"),
  draw_key = draw_key_point,
  setup_data = function(data, params) {
    if(!is.null(params$showpoints)) {
      data$showpoints <- params$showpoints
    }
    data
  },
  extra_params = c("na.rm", "showpoints"),
  draw_panel = .draw_panel_func
)

geom_simple_point <- function(mapping = NULL, data = NULL, stat = "identity",
                              position = "identity", na.rm = FALSE, show.legend = NA, 
                              inherit.aes = TRUE, showpoints=TRUE, ...) {
  layer(
    geom = GeomSimplePoint, mapping = mapping,  data = data, stat = stat, 
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, showpoints=showpoints, ...)
  )
}

Is there a simpler way of just passing a chosen extra param to the geom? If I can define extra_params, why can't I access them somehow more easily?


Solution

  • There is a relatively easy way, and that is to pass the extra parameter as argument to the panel drawing function. Here is a simple example that would work with your geom_simple_point():

    GeomSimplePoint <- ggproto(
      "GeomSimplePoint", 
      GeomPoint,
      extra_params = c("na.rm", "showpoints"),
      draw_panel = function(data, panel_params, 
                            coord, na.rm = FALSE, showpoints = TRUE) {
        if (showpoints) {
          return(GeomPoint$draw_panel(data, panel_params, coord, na.rm = na.rm))
        } else {
          return(zeroGrob())
        }
      }
    )
    

    As an aside, if you want to replicate behaviour of an already existing geom, such as geom_point(), it is easier to set your own ggproto object to inherit from that geom's ggproto object. That way, the default aes, required aes and other arguments that you specify in ggproto are automatically copied/inherited from that geom and you don't have to manually specify all of them.