How to create base R plot 'type = b' equivalent in ggplot2?

前端 未结 4 2079
日久生厌
日久生厌 2020-12-15 19:40

Base plot() functionality allows one to set type=\'b\' and get a combined line and point plot in which the points are offset from the line segments

4条回答
  •  悲哀的现实
    2020-12-15 20:28

    Ok I have an implementation of a geom, that does not rely on hardcoding and should not have wierd offsets. It's essentialy a geom_point() implementation, that draws a path* between points, draws a larger background point with colours set to the panel background and then the normal points.

    *note that path's behaviour is not to connect points along the x-axis, but along row-order in the data.frame that is given to ggplot. You can sort your data beforehand if you want geom_line() behaviour.

    The main problem for me was to get the inner workings of the geom drawing code to retrieve the theme of the current plot to extract the background colour of the panel. Due to this, I'm very unsure how stable this would be (and would welcome any tips), but at least it works.

    EDIT: should be more stable now

    Let's get to the, admittedly lengthy, ggproto object code:

    GeomPointPath <- ggproto(
      "GeomPointPath", GeomPoint,
      draw_panel = function(self, data, panel_params, coord, na.rm = FALSE)
      {
    
        # bgcol <- sys.frame(4)$theme$panel.background$fill
        # if (is.null(bgcol)) {
        #   bgcol <- theme_get()$panel.background$fill
        # }
    
        # EDIT: More robust bgcol finding -----------
        # Find theme, approach as in https://github.com/tidyverse/ggplot2/issues/3116
        theme <- NULL
        for(i in 1:20) {
          env <- parent.frame(i)
          if("theme" %in% names(env)) {
            theme <- env$theme
            break
          }
        }
        if (is.null(theme)) {
          theme <- theme_get()
        }
    
        # Lookup likely background fills
        bgcol <- theme$panel.background$fill
        if (is.null(bgcol)) {
          bgcol <- theme$plot.background$fill
        }
        if (is.null(bgcol)) {
          bgcol <- theme$rect$fill
        }
        if (is.null(bgcol)) {
          # Default to white if no fill can be found
          bgcol <- "white"
        }
        # END EDIT ------------------
    
        if (is.character(data$shape)) {
          data$shape <- ggplot2:::translate_shape_string(data$shape)
        }
    
        coords <- coord$transform(data, panel_params)
    
        # Draw background points
        bgpoints <- grid::pointsGrob(
          coords$x, coords$y, pch = coords$shape,
          gp = grid::gpar(
            col = alpha(bgcol, NA), 
            fill = alpha(bgcol, NA),
            fontsize = (coords$size * .pt + coords$stroke * .stroke/2) * coords$mult,
            lwd = coords$stroke * .stroke/2
          )
        )
    
        # Draw actual points
        mypoints <- grid::pointsGrob(
          coords$x, coords$y, pch = coords$shape, 
          gp = grid::gpar(
            col = alpha(coords$colour, coords$alpha), 
            fill = alpha(coords$fill, coords$alpha), 
            fontsize = coords$size * .pt + coords$stroke * .stroke/2, 
            lwd = coords$stroke * .stroke/2
          )
        )
    
        # Draw line
        myline <- grid::polylineGrob(
          coords$x, coords$y, 
          id = match(coords$group, unique(coords$group)),
          default.units = "native",
          gp = grid::gpar(
            col = alpha(coords$colour, coords$alpha),
            fill = alpha(coords$colour, coords$alpha),
            lwd = (coords$linesize * .pt),
            lty = coords$linetype,
            lineend = "butt",
            linejoin = "round", linemitre = 10
          )
        )
    
        # Place graphical objects in a tree
        ggplot2:::ggname(
          "geom_pointpath",
          grid::grobTree(myline, bgpoints, mypoints) 
        )
      },
      # Set some defaults, assures that aesthetic mappings can be made
      default_aes = aes(
        shape = 19, colour = "black", size = 1.5, fill = NA, alpha = NA, stroke = 0.5,
        linesize = 0.5, linetype = 1, mult = 3,
      )
    )
    

    Observant people may have noticed the line bgcol <- sys.frame(4)$theme$panel.background$fill. I could not find another way to access the current plot's theme, without having to adjust at least several other functions to pass the theme as an argument. In my version of ggplot (3.1.0), the 4th sys.frame() is the environment of the ggplot2:::ggplot_gtable.ggplot_built call wherein the geom drawing code is evaluated. It's quite easy to imagine that this function can be updated in the future -which can change the scoping- hence the stability warning. As a backup, it defaults to the global theme settings when it can't find the current theme.

    EDIT: should now be more stable

    Onwards to the layer wrapper which is pretty much self-explanatory:

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

    Adding it to a ggplot should be a familiar thing. Just setting the theme to the default theme_gray() to test that it indeed takes the current plot's theme.

    theme_set(theme_gray())
    g <- ggplot(pressure, aes(temperature, pressure)) +
      geom_pointpath() +
      theme(panel.background = element_rect(fill = "dodgerblue"))
    

    Of course, this method will obscure grid lines with the background points, but that is the tradeoff I was willing to make to prevent wonkyness due to line path shortening. Line sizes, line types, and the relative size of the background points can be set with aes(linesize = ..., linetype = ..., mult = ...) or per the ... argument in geom_pointpath(). It inherits the other aesthetics from GeomPoint.

提交回复
热议问题