How to keep linetype spacing constant despite line size

前端 未结 2 1795
南方客
南方客 2021-01-02 07:59

I\'ve been attempting to plot lines in either ggplot2 or grid with equal spacing between line segments when the sizes differ. However I\'ve not been succesfull so I ask you

2条回答
  •  予麋鹿
    予麋鹿 (楼主)
    2021-01-02 08:49

    This is probably not what you're looking for Teunbrand, but I guess you could convert your lines to a series of thin polygonGrobs equally spaced along the lines.

    This function takes a series of x and y co-ordinates and returns a dashed line (as a single treeGrob). As per your example it returns it in normalised npc co-ordinates. You have full control over the line width, dash length and break length (though not the pattern), as well as the colour. I'm afraid the units are a bit arbitrary, and this is far from production standard, but it's fairly effective:

    segmentify <- function(x, y, linewidth = 1, dash_len = 1, 
                           break_len = 1, col = "black")
    {
      
      linewidth <- 0.002 * linewidth
      dash_len  <- 0.01  * dash_len
      break_len <- 0.04  * break_len
    
      if(length(y) != length(x)) 
        stop("x and y must be the same length")
      if(!is.numeric(x) | !is.numeric(y))
        stop("x and y must be numeric vectors")
      if(length(x) < 2)
        stop("Insufficient x, y pairs to make line.")
      
      x <- scales::rescale(x)
      y <- scales::rescale(y)
      
      n_dashes <- 0
      skip_len <- break_len + dash_len
      
       df <- list()
      for(i in seq_along(x)[-1])
      {
        x_diff          <- x[i] - x[i - 1]
        y_diff          <- y[i] - y[i - 1]
        seg_len         <- sqrt(x_diff^2 + y_diff^2)
        seg_prop        <- skip_len / seg_len
        dist_from_start <- n_dashes * skip_len
        prop_start      <- dist_from_start/seg_len
        x_start         <- x[i-1] + prop_start * x_diff
        y_len           <- y_diff * seg_prop
        x_len           <- x_diff * seg_prop
        y_start         <- y[i-1] + prop_start * y_diff
        n_breaks        <- (seg_len - dist_from_start)/skip_len
        n_dashes        <- (n_dashes + n_breaks) %% 1
        n_breaks        <- floor(n_breaks)
        
        if(n_breaks)
        {
           df[[length( df) + 1]] <- data.frame(
            x = seq(x_start, x[i], by = x_len),
            y = seq(y_start, y[i], by = y_len)
            )
           df[[length( df)]]$theta <-
            atan(rep(y_diff/x_diff, length( df[[length( df)]]$x)))
        }
      }
      
       df <- do.call(rbind,  df)
       df$x1 <-  df$x + sin( df$theta) * linewidth + cos(df$theta) * dash_len
       df$x2 <-  df$x + sin( df$theta) * linewidth - cos(df$theta) * dash_len
       df$x3 <-  df$x - sin( df$theta) * linewidth - cos(df$theta) * dash_len
       df$x4 <-  df$x - sin( df$theta) * linewidth + cos(df$theta) * dash_len
       
       df$y1 <-  df$y - cos( df$theta) * linewidth + sin(df$theta) * dash_len
       df$y2 <-  df$y - cos( df$theta) * linewidth - sin(df$theta) * dash_len
       df$y3 <-  df$y + cos( df$theta) * linewidth - sin(df$theta) * dash_len
       df$y4 <-  df$y + cos( df$theta) * linewidth + sin(df$theta) * dash_len
      
       do.call(grid::grobTree, lapply(seq(nrow(df)), function(i) {
        grid::polygonGrob(c(df$x1[i], df$x2[i], df$x3[i], df$x4[i]), 
                          c(df$y1[i], df$y2[i], df$y3[i], df$y4[i]),
                  gp = gpar(col = "#00000000", lwd = 0, fill = col))
       }))
    
    }
    

    It's fairly straightforward to use:

    set.seed(2)
    
    x <- 1:10
    y <- rnorm(10)
    
    grid::grid.newpage()
    grid::grid.draw(segmentify(x, y))
    

    And changing the line width without affecting the spacing is just like this:

    grid::grid.newpage()
    grid::grid.draw(segmentify(x, y, linewidth = 3))
    

    And you can control spacing and color like this:

    grid::grid.newpage()
    grid::grid.draw(segmentify(x, y, linewidth = 2, break_len = 0.5, col = "forestgreen"))
    

提交回复
热议问题