Search code examples
rggplot2geom-hlinegeom-vline

How to add a label to the x / y axis whenever a vertical / horizontal line is added to a ggplot?


The x and y axis are labeled based on certain interval set by ggplot.

In the event that a horizontal or vertical line is added to the plot, the goal is to label x or y axis at the exact value of the line.

How can this be done?


Solution

  • This isn't totally straightforward, but it is possible. I would probably use these two little functions to automate the tricky parts:

    add_x_break <- function(plot, xval) {
      
      p2 <- ggplot_build(plot)
      breaks <- p2$layout$panel_params[[1]]$x$breaks
      breaks <- breaks[!is.na(breaks)]
      
      plot +
        geom_vline(xintercept = xval) +
        scale_x_continuous(breaks = sort(c(xval, breaks)))
    }
    
    add_y_break <- function(plot, yval) {
      
      p2 <- ggplot_build(plot)
      breaks <- p2$layout$panel_params[[1]]$y$breaks
      breaks <- breaks[!is.na(breaks)]
      
      plot +
        geom_hline(yintercept = yval) +
        scale_y_continuous(breaks = sort(c(yval, breaks)))
    }
    

    These would work like this. Start with a basic plot:

    library(ggplot2)
    
    set.seed(1)
    
    df <- data.frame(x = 1:10, y = runif(10))
    
    p <- ggplot(df, aes(x, y)) + 
      geom_point() +
      ylim(0, 1)
    
    p
    

    Add a vertical line with add_x_break

    p <- add_x_break(p, 6.34)
    
    p
    

    Add a horizontal line with add_y_break:

    p <- add_y_break(p, 0.333)
    #> Scale for 'y' is already present. Adding another scale for 'y', which will
    #> replace the existing scale.
    
    p
    


    ADDENDUM

    If for some reason you do not have the code that generated the plot, or the vline is already present, you could use the following function to extract the xintercept and add it to the axis breaks:

    add_x_intercepts <- function(p) {
      
      p2 <- ggplot_build(p)
      breaks <- p2$layout$panel_params[[1]]$x$breaks
      breaks <- breaks[!is.na(breaks)]
      
      vals <- unlist(lapply(seq_along(p$layers), function(x) {
        d <- layer_data(p, x)
        if('xintercept' %in% names(d)) d$xintercept else numeric()
      }))
      
      p + scale_x_continuous(breaks = sort(c(vals, breaks)))
    }
    

    So, for example:

    set.seed(1)
    
    df <- data.frame(x = 1:10, y = runif(10))
    
    p <- ggplot(df, aes(x, y)) + 
      geom_point() +
      geom_vline(xintercept = 6.34) +
      ylim(0, 1)
    
    p
    

    enter image description here

    Then we can do:

    add_x_intercepts(p)
    

    enter image description here

    The y intercepts of geom_hline can be obtained in a similar way, which should hopefully be evident from the code of add_x_intercepts