GFauxPas
GFauxPas

Reputation: 299

How do I show the orientation of a curve in ggplot2?

I have a parameterized contour that I'm plotting in R. What I'm trying to do is add arrows along the curve to show the viewer which direction the curve is going in.

Here's the code I'm using to generate the curve:

library(ggplot2)
library(grid)
set.seed(9)
T<-sort(runif(2^12,min=2^-5, max=16))

U<-function(t) exp(4*log(t) - 4*t)*(cos(log(t) + 3*t))
#Re(t^(4+1i)*t)*exp(-(4-3i)*t))
V<-function(t) exp(4*log(t) - 4*t)*(sin(log(t) + 3*t)) 
#Im(t^(4+1i)*t)*exp(-(4-3i)*t))


X<-sapply(T,U)
Y<-sapply(T,V)

df<-data.frame(X=X,Y=Y)

p<-ggplot(data=df,aes(x = df$X, y = df$Y))

p+theme_bw()+
geom_path(size=1,color='blue',linetype=1) #+
#geom_segment(aes(xend=c(tail(X, n=-1), NA), yend=c(tail(Y, n=-1), NA)),
#arrow=arrow(length=unit(0.2,"cm")),color='blue')
dev.off()

The last part I commented out:

#+
#geom_segment(aes(xend=c(tail(X, n=-1), NA), yend=c(tail(Y, n=-1), NA)),
#arrow=arrow(length=unit(0.2,"cm")),color='blue') 

does something similar to what I want, but the arrows are very close together and the curve ends up looking "fuzzy" rather than directed.

Here's the fuzzy and non-fuzzy version of the curve:

Fuzzy Curve

enter image description here

Thank you!

Upvotes: 2

Views: 612

Answers (3)

baptiste
baptiste

Reputation: 77116

It might look better if the arrows were more equally spaced along the curved path, e.g.

enter image description here

library(ggplot2)
library(grid)
set.seed(9)
T <- sort(runif(2^12,min=2^-5, max=16))
U <- function(t) exp(4*log(t) - 4*t)*(cos(log(t) + 3*t))
V <- function(t) exp(4*log(t) - 4*t)*(sin(log(t) + 3*t)) 
drough <- data.frame(x=sapply(T,U), y=sapply(T,V))


p <- ggplot(data = drough, aes(x = x, y = y))  + 
       geom_path() 

## because the parametric curve was generated with uneven spacing
## we can try to resample more evenly along the path
parametric_smoothie <- function(x, y, N=1e2, phase=1, offset=0) {

  lengths <- c(0, sqrt(diff(x)^2 + diff(y)^2))
  l <- cumsum(lengths)
  lmax <- max(l)
  newpos <- seq(phase*lmax/N, lmax-phase*lmax/N, length.out = N) + offset*lmax/N
  xx <- approx(l, x, newpos)$y
  yy <- approx(l, y, newpos)$y
  data.frame(x = xx, y = yy)
}

## this is a finer set of points
dfine <- parametric_smoothie(X, Y, 20)

gridExtra::grid.arrange(p + geom_point(data = drough, col="grey"),
                        p + geom_point(data = dfine, col="grey"), ncol=2)

enter image description here

## now we use this function to create N start points for the arrows
## and another N end points slightly apart to give a sense of direction
relay_arrow <- function(x, y, N=10, phase = 0.8, offset = 1e-2, ...){

  start <- parametric_smoothie(x, y, N, phase)
  end <- parametric_smoothie(x, y, N, phase, offset)

  data.frame(xstart = start$x, xend = end$x, 
             ystart = start$y, yend = end$y)

}

breaks <- relay_arrow(drough$x, drough$y, N=20)

p + geom_point(data = breaks, aes(xstart, ystart), col="grey98", size=2) +
  geom_segment(data = breaks, aes(xstart, ystart, xend = xend, yend = yend), 
               arrow = arrow(length = unit(0.5, "line")),
               col="red", lwd=1)

Upvotes: 6

Sandipan Dey
Sandipan Dey

Reputation: 23129

Try this with slight modification of your code (you don't want to compromise the quality of the curve by having smaller number of points and at the same time you want to have smaller number of segments to draw the arrows for better quality of the arrows):

library(ggplot2)
library(grid)
set.seed(9)
T<-sort(runif(2^12,min=2^-5, max=16))

U<-function(t) exp(4*log(t) - 4*t)*(cos(log(t) + 3*t))
#Re(t^(4+1i)*t)*exp(-(4-3i)*t))
V<-function(t) exp(4*log(t) - 4*t)*(sin(log(t) + 3*t)) 
#Im(t^(4+1i)*t)*exp(-(4-3i)*t))


X<-sapply(T,U)
Y<-sapply(T,V)

df<-data.frame(X=X,Y=Y)
df1 <- df[seq(1,length(X), 8),]

p<-ggplot(data=df,aes(x = df$X, y = df$Y))

p+theme_bw()+
  geom_path(size=1,color='blue',linetype=1) +
  geom_segment(data=df1,aes(x=X, y=Y, xend=c(tail(X, n=-1), NA), yend=c(tail(Y, n=-1), NA)),
             arrow=arrow(length=unit(0.3,"cm"),type='closed'),color='blue')
#dev.off()

enter image description here

Upvotes: 1

sebastian-c
sebastian-c

Reputation: 15415

One way to do it is to draw them on after. You can probably get the direction better by using the angle aesthetic (if it's easy enough to work out):

p<-ggplot(data=df,aes(x = X, y = Y)) 
p + 
  geom_path(size=1,color='blue',linetype=1)+ 
  geom_segment(data = df[seq(1, nrow(df), 20), ], aes(x = X, y = Y, xend=c(tail(X, n=-1), NA), yend=c(tail(Y, n=-1), NA)), 
  arrow=arrow(length=unit(0.2,"cm"), type = "closed"), color="blue", linetype = 0, inherit.aes = FALSE) 

enter image description here

Note the closed arrow type. I had to do that so they weren't interpreted as lines and hence disappear when linetype = 0.

Upvotes: 2

Related Questions