rggplot2

Setting y limit for geom_curve


I am trying to set the height of a geom_curve() plot, so that the plot area will end just above the "highest arch". I tried by setting ylim() but it does not work. Any advice on how to achieve this?

df <- data.frame(x1 = c(9,10,11,12,13), 
                 x2 = c(25,24,23,22,210),
                 y1 = c(0,0,0,0,0),
                 y2 = c(0,0,0,0,0), 
                 fill = c(rep('black',5)))

ggplot(df) + 
  geom_curve(aes(x=x1, xend=x2, y=y1, yend=y2), curvature=1, linewidth=0.5, ncp=1000, colour='black') + 
  expand_limits(y=1) + 
  scale_y_reverse() +
  theme(axis.text.x = element_text(size=9, angle=0, vjust=0.5), 
        axis.text.y = element_text(size=9), 
        legend.position = 'none', 
        axis.title.x = element_blank(), 
        axis.title.y = element_blank(), 
        plot.margin = margin(10,0,0,0))

See example


Solution

  • Unlike other geoms, geom_curve is a fixed shape within the plotting window. Although its endpoints are defined in a fixed position in user space, the curve will stay the same shape as you resize the plotting window. Its lowest point therefore does not have a well-defined x, y co-ordinate.

    To see this, let's see what happens when we call your code with a relatively tall and thin plotting window:

    enter image description here

    Now, without touching the console or running any other code, let's see what the plot looks like when we simply drag the plotting window to make it less tall:

    enter image description here

    Note that the curve is not only nearer the bottom of the plot, the y co-ordinate of the bottom of the curve has "moved" from about 0.35 to 0.75.

    If we make it shorter again (without running any code), we can even bring the curve down to the very bottom of the plot:

    enter image description here

    So if we want the curve to touch the bottom of the plot, we need to fix the aspect ratio. If your curve always has a curvature of 1 and you are always drawing the lower half of semicircles, then an aspect ratio of 0.5 will bring the curve near the bottom (and we can make it very slightly lower to account for the default axis expansion)

    ggplot(df) + 
      geom_curve(aes(x=x1, xend=x2, y=y1, yend=y2), curvature=1, linewidth=0.5, 
                 ncp=1000, colour='black') + 
      expand_limits(y=1) + 
      scale_y_reverse() +
      theme(axis.text.x = element_text(size=9, angle=0, vjust=0.5), 
            axis.text.y = element_text(size=9), 
            legend.position = 'none', 
            axis.title.x = element_blank(), 
            axis.title.y = element_blank(), 
            plot.margin = margin(10,0,0,0),
            aspect.ratio = 0.48)
    

    enter image description here

    Personally, I don't like this behaviour of geom_curve, and for precision I would rather have the data that I am plotting be specified in user co-ordinates. A simple geometric function allows the data conversion to allow you to draw using geom_path, and means you need only set the y limits in coord_cartesian:

    curve_fun <- function(xvals1, xvals2, n = 100, ylim = c(-0.5, 0)) {
      xdiff <- max(abs(xvals1 - xvals2), na.rm = TRUE)/2
      ydiff <- abs(diff(ylim))
      do.call("rbind", Map(function(x1, x2, g) {
        r <- (x1 - x2)/2
        mid <- (x1 + x2)/2
        theta <- seq(0, pi, length.out = n)
        x <- mid + r * cos(theta)
        y <- r * ydiff/xdiff * sin(theta)
        data.frame(x, y, g = g)
      }, xvals1, xvals2, seq_along(xvals1)))
    }
    
    ggplot(curve_fun(df$x1, df$x2)) + 
      geom_path(aes(x, y, group = g)) +
      coord_cartesian(ylim = c(-0.5, 0)) +
      theme(axis.text.x = element_text(size = 9), 
            axis.text.y = element_text(size = 9), 
            axis.title.x = element_blank(), 
            axis.title.y = element_blank(), 
            plot.margin = margin(10, 0, 0, 0))
    

    enter image description here

    And now this curve will have fixed points, bottoming out at precisely 0.5 and changing shape when you resize the window with fixed co-ordinates in data space