R: how do I draw a line with multiple arrows in it?

前端 未结 5 2016
梦如初夏
梦如初夏 2020-12-04 02:25

I was wondering if anyone could help me plot lines in R with multiple arrows in them, like this:

--->--->--->--->

Thanks in advance!

相关标签:
5条回答
  • 2020-12-04 02:51

    I thought I would add a base R solution here that calculates the arrow heads as segments. There are versions to create a new plot or add arrows to an existing plot. Longer segments are split into shorter ones:

    add_arrows <- function(x, y, ...)
    {
      # Ensure equal lengthed vectors
      min_len <- min(length(x), length(y))
      x  <- x[seq(min_len)]
      y  <- y[seq(min_len)]
    
      # Calculate y:x ratio
      yx_ratio <- 3*(max(y) - min(y)) / (max(x) - min(x))
    
      # Create start and end points
      x1 <- x[-length(x)]
      y1 <- y[-length(y)]
      x2 <- x[-1]
      y2 <- y[-1]
    
      # Length and angle of line segments
      theta <- atan((y2 - y1)/(x2 - x1))
      seg_lengths <- sqrt((x2 - x1)^2 + (y2 - y1)^2)
    
      # Break long segments into shorter segments
      long <- which(seg_lengths > (2 * min(seg_lengths)))
      if(length(long) > 0)
      {
        n_segs <- floor(seg_lengths[long] / (1.5 * min(seg_lengths)))
    
        for(i in seq_along(long))
        {
          xs <- x1[long[i]] + (0:n_segs[i]) * (x2[long[i]] - x1[long[i]]) / n_segs[i]
          ys <- y1[long[i]] + (0:n_segs[i]) * (y2[long[i]] - y1[long[i]]) / n_segs[i]
          x1 <- c(x1, xs[-length(xs)])
          x2 <- c(x2, xs[-1])
          y1 <- c(y1, ys[-length(ys)])
          y2 <- c(y2, ys[-1])
          theta <- c(theta, rep(theta[long[i]], length(xs) - 1))
        }
    
        x1 <- x1[-long]; x2 <- x2[-long]; y1 <- y1[-long]; y2 <- y2[-long];
        theta <- theta[-long]
      }
      # Arrow head length and width
      len <- sqrt((max(y) - min(y))^2 + (max(x) - min(x))^2)/ 20
      wid <- len * 0.1
    
    
      # Calculate arrow heads
      zx  <- x2 - len * cos(theta) * sign(x2 - x1)
      zy  <- y2 - len * sin(theta) * sign(x2 - x1)
      ax1 <- zx + wid * sin(theta) * sign(x2 - x1)
      ax2 <- zx - wid * sin(theta) * sign(x2 - x1)
      ay1 <- zy - wid * cos(theta) * sign(x2 - x1) * yx_ratio
      ay2 <- zy + wid * cos(theta) * sign(x2 - x1) * yx_ratio
    
      df <- data.frame(x1  = x1 + 0.1 * (x2 - x1),
                       y1  = y1 + 0.1 * (y2 - y1),
                       x2  = x2 - 0.5 * len * cos(theta) * sign(x2 - x1),
                       y2  = y2 - 0.5 * len * sin(theta) * sign(x2 - x1),
                       ax1 = ax1, ay1 = ay1, ax2 = ax2, ay2 = ay2)
    
      # Draw segments
      segments(df$x1, df$y1, df$x2,  df$y2,  col = "red", lwd = 2)
      segments(df$x2, df$y2, df$ax1, df$ay1, col = "red", lwd = 2)
      segments(df$x2, df$y2, df$ax2, df$ay2, col = "red", lwd = 2)
    }
    

    This allows you to plot arrows as the basis of your plot:

    
    plot_arrows <- function(x, y, ...)
    {
      plot(x, y, col = "white", ...)
      add_arrows(x, y, ...)
    }
    

    So now you can do:

    set.seed(69)
    
    x <- 1:10
    y <- sample(10)
    plot(x, y)
    add_arrows(x, y)
    

    Created on 2020-02-29 by the reprex package (v0.3.0)

    0 讨论(0)
  • 2020-12-04 02:59

    Simulair to DWin this is what I came up with:

    arrowLine <- function(x0,y0,x1,y1,nArrow=1,...)
    {
      lines(c(x0,x1),c(y0,y1),...)
      Ax=seq(x0,x1,length=nArrow+1)
        Ay=seq(y0,y1,length=nArrow+1)
      for (i in 1:nArrow)
      {
        arrows(Ax[i],Ay[i],Ax[i+1],Ay[i+1],...)
      }
    }
    

    Basically it overlaps several arrows in a line, but does get the desired result (I assume straigth lines):

    plot(0:1,0:1)
    arrowLine(0,0,1,1,4)
    

    Multiple arrows on a line

    The real tricky part comes to getting the arrow to got o the edge of points instead of the center. Is that needed?

    0 讨论(0)
  • 2020-12-04 03:01

    As joran says, arrows

     x=cos( seq(0, pi, by=pi/8)  )
     y=sin( seq(0, pi, by=pi/8))
     plot(1,1, ylim=range(y), xlim=range(x))
     arrows(x[-length(x)],y[-length(y)], x[-1],y[-1])
    

    If you want to draw a straight multiple arrow curve with specification of equal lengths, then use this function:

    multarrows <- function(x0,y0, x1,y1,n_arr, ...) {x<- seq(x0,x1, length=n_arr+1)
                 y<-seq(y0,y1, length=n_arr+1)
                 arrows(x[-length(x)],y[-length(y)], x[-1],y[-1], ...) }
    plot(0,0, xlim=c(0,2), ylim=c(0,11)); multarrows(0,0, 1,10, 10)
    

    enter image description here

    0 讨论(0)
  • 2020-12-04 03:11

    There's a sneaky way to do this without having to plot a bunch of line segments or arrows.
    Start with x and y data as above:

    plot(x, y, t='l')
    

    Then calculate the local slopes from x[1], y[1] to x[2], y[2] and so on, using some canned routine. Convert the slopes to angles in degrees, and then (calling the vector of slopes angslopes )

    text(x, y, labels=">", srt=angslopes)  # throws error
    

    Sorry, that won't work because, so far as I can tell, srt must be a single value. So:

    diflen <- length(x)-1
    sapply(1:diflen, function(xind) text(x[xind+1], y[xind+1], labels='>', cex=2.5, srt=angslopes[xind]))
    

    There may well be a much easier way to do this in the lattice package.

    This approach has the cosmetic advantage of allowing you to specify the arrowhead size via par(cex) as well as the color - which could be different from the line color, or even varying along the curve.

    0 讨论(0)
  • 2020-12-04 03:13

    Based on the clarifications to the original question, I think a general answer should consider

    • calculating the total length of the broken curve specified by (x,y) points

    • splitting apart intermediate segments so as to ensure equal curvilinear length between arrow heads

    • take care of minor details such as initial "phase", end-point, whether the arrows heads should be followed by a thin space, etc.

    arrows

    Below is a rough stab at it.

    arrowLine <- function(x, y, N=10, ...){
      lengths <- c(0, sqrt(diff(x)^2 + diff(y)^2))
      l <- cumsum(lengths)
      tl <- l[length(l)]
      el <- seq(0, to=tl, length=N+1)[-1]
    
      plot(x, y, t="l", ...)
    
      for(ii in el){
    
        int <- findInterval(ii, l)
        xx <- x[int:(int+1)]
        yy <- y[int:(int+1)]
    
        ## points(xx,yy, col="grey", cex=0.5)
    
        dx <- diff(xx)
        dy <- diff(yy)
        new.length <- ii - l[int]
        segment.length <- lengths[int+1]
    
        ratio <- new.length / segment.length
    
        xend <- x[int] + ratio * dx
        yend <- y[int] + ratio * dy
        points(xend,yend, col="white", pch=19)
        arrows(x[int], y[int], xend, yend, length=0.1)
    
    }
    
    }
    
    set.seed(123)
    x = sort(c(0, runif(200, 0,2* pi), 2*pi))
    y=sin(x)
    
    arrowLine(x, y, N=20)
    
    0 讨论(0)
提交回复
热议问题