-1

I wonder how can I find the control points of geom_curve in ggplot2? e.g.

p <- ggplot(mtcars, aes(wt, mpg)) + 
geom_curve(aes(x = 2.62, y = 21.0, xend = 3.57, yend = 15.0),curvature = -0.2, data = df) + 
geom_point()

b <- ggplot_build(p)
b$data[[1]]
p$layers[[1]]$geom_params

b$data[[1]] gives the starting and ending points and
p$layers[[1]]$geom_params gives the curve information (angle, curvature, ...).
But how can I find the control points, so I can reproduce the graphics?

Marco Sandri
  • 23,289
  • 7
  • 54
  • 58
Zane
  • 69
  • 8

1 Answers1

0
library(ggplot)
library(grid)
library(stringr)

df <- data.frame(x = 1:3, y = 1:3)
df2 <- data.frame(x = c(1,3), y = c(1,3),
              xend = c(2,2), yend = c(2,2))
g <- ggplot(df, aes(x, y)) +
  geom_point() +
  geom_curve(aes(x = x ,y = y,
                 xend = xend, yend = yend),
             data = df2,
             color = c("red", "blue"))
g

getCurve_controlPoints <- function(ggplotObject) {
  len_layers <- length(ggplotObject$layers)
  layerNames <- lapply(seq_len(len_layers),
                       function(j) {
                         className <- class(ggplotObject$layers[[j]]$geom)
                         className[-which(className %in% c("ggproto"  ,"gg", "Geom"))] 
                       })
  curveLayerId <- which(sapply(layerNames,
                               function(layerName){
                                 "GeomCurve" %in% layerName
                               }) == TRUE
  )
  gg_build <- ggplot_build(ggplotObject)
  # you can also add yes or no in your code
  # answer <- utils::menu(c("y", "n"), title="Do you want to draw the ggplot?")
  grid.draw(ggplotObject)
  grid.force()
  gridList <- grid.ls(print = FALSE)
  gridList.name <- gridList$name
  xspline.name <- gridList.name[which(str_detect(gridList.name, "curve") == TRUE)]
  xspline.len <- length(xspline.name)

  controlPoints <- lapply(seq_len(length(curveLayerId)),
                          function (j) {
                            # curve data
                            curve_data <- gg_build$data[[curveLayerId[j]]]
                            # avoid duplicated rows
                            curve_data <- curve_data[!duplicated(curve_data), ]
                            n <- dim(curve_data)[1]
                            # here we go! But wait a second, it seems like the starting and ending position do not match
                            xsplinePoints <- xsplinePoints(grid.get("xspline", grep=TRUE))  
                            # mapping data to our coordinates
                            control_data <- lapply(seq_len(n),
                                                   function(i){
                                                     if (n == 1) {
                                                       xy <- lapply(xsplinePoints,
                                                                    function(coord){
                                                                      as.numeric(coord) 
                                                                    })
                                                     } else {
                                                       xy <- lapply(xsplinePoints[[i]],
                                                                    function(coord){
                                                                      as.numeric(coord)
                                                                    })
                                                     }
                                                     x.start <- curve_data[i, ]$x
                                                     x.end <- curve_data[i, ]$xend
                                                     y.start <- curve_data[i, ]$y
                                                     y.end <- curve_data[i, ]$yend
                                                     # mapping to ggplot coordinates
                                                     xy_x.diff <- xy$x[length(xy$x)] - xy$x[1] 
                                                     xy_y.diff <- xy$y[length(xy$y)] - xy$y[1]
                                                     # maybe there is a better way?
                                                    if(xy_x.diff == 0){
                                                       xy_x.diff <- 1e-16
                                                     }
                                                     if(xy_y.diff == 0){
                                                        xy_y.diff <- 1e-16
                                                     }
                                                     x <- (x.end - x.start) / (xy_x.diff) * (xy$x - xy$x[1]) + x.start
                                                     y <- (y.end - y.start) / (xy_y.diff) * (xy$y - xy$y[1]) + y.start
                                                     list(x = x, y = y)
                                                   })
                            # grid remove
                            grid.remove(xspline.name[j], redraw = FALSE)
                            control_data
                          })

  controlPoints
}
controlPoints <- getCurve_controlPoints(g)
# check the points
plot(controlPoints[[1]][[1]]$x, controlPoints[[1]][[1]]$y, 
     xlim = c(1,3), 
     ylim = c(1,3),
     xlab = "x", 
     ylab = "y", 
     pch = 19)
points(controlPoints[[1]][[2]]$x, controlPoints[[1]][[2]]$y, pch = 19)

I think it works well so far and the ggplot version I used is 3.0.0. If you use any version less than 2.2.1, error may occur.

This idea is suggested by Prof Paul Murrell. Perhaps the easiest way to capture the control points of geom_curve? The cons are that grobs(ggplot object) must be drawn at first, since these points are only generated at the drawing time.

Zane
  • 69
  • 8