jsimpsno
jsimpsno

Reputation: 460

compute distance and add lines ggplot

I'm trying to figure out a better way to make the graph below without manually typing the distances and line segments in ggplot. The code below will create the graph, but the lines and text call outs are manual tasks and won't work for large datasets which i'm working with.

df<- data.frame(x=c(1,2,3,4,5,6,7,8,9),y=c(2,5,2,5,2,5,2,5,2),z=c('a','b','a','b','a','b','a','b','a'))

ggplot(df,aes(x,y,color=z))+geom_segment(aes(x = 2, y = 2, xend = 2, yend = 5),color='blue',size=1)+
  geom_segment(aes(x = 1, y = 2, xend = 3, yend = 2),color='blue',size=1)+
  geom_point(size=5)+scale_x_continuous(breaks = c(1,2,3,4,5,6,7,8,9,10,11))+
  annotate("text", x = 2, y = 2, label = "2 feet",size=6)+
  annotate("text", x = 2, y = 3.5, label = "3 feet",size=6)


graph

Yes, i would want to label more than just the two segements. Most of the Dots will be on the same/similar y axis positioning by grouping, for example all red dots will lie on 2 and green dots on 3.5 and blue dots on 6. Sorry for not clarifying, I wanted to provide a simpler problem. The pattern could be different, for instance there might be a middle row between the two.

I've added another chart to better explain my problem.

df<- data.frame(x=c(1,2,1,3,3,3.5,5,6,5),y=c(2,3.5,6,2,3.5,6,2,3.7,6),z=c('a','b','c','a','b','c','a','b','c'))

ggplot(df,aes(x,y,color=z))+geom_segment(aes(x = 2, y = 2, xend = 2, yend = 3.5),color='blue',size=1)+
  geom_segment(aes(x = 1, y = 2, xend = 3, yend = 2),color='blue',size=1)+
  geom_segment(aes(x = 2, y = 3.5, xend = 2, yend = 6),color='blue',size=1)+
  geom_segment(aes(x = 1, y = 6, xend = 3.5, yend = 6),color='blue',size=1)+
  geom_segment(aes(x = 2, y = 3.5, xend = 6, yend = 3.5),color='blue',size=1)+
  geom_point(size=5)+scale_x_continuous(breaks = c(1,2,3,4,5,6,7,8,9,10,11))+
  annotate("text", x = 2, y = 2, label = "2 feet",size=5)+
  annotate("text", x = 2, y = 2.75, label = "1.5 feet",size=5)+
  annotate("text", x = 2, y = 4.75, label = "2.5 feet",size=5)+
  annotate("text", x = 2.2, y = 6.2, label = "2.5 feet",size=5)+
  annotate("text", x = 2.5, y = 3.6, label = "1 foot",size=5)+
  annotate("text", x = 4.5, y = 3.6, label = "3 feet",size=5)+
  expand_limits(y = c(1, 7))+scale_y_continuous(breaks = c(1,2,3,4,5,6,7))+
  theme_bw()+  theme(legend.position = 'bottom')

extended

Upvotes: 0

Views: 1499

Answers (1)

Jon Spring
Jon Spring

Reputation: 66415

I would approach this using a function that takes index values as an input and adds the segments and text accordingly. To add put multiple layers to ggplot, you put the layers into a list.

I have not yet figured out a good way to approach the part of your question about creating secondary lines between lines (like the vertical lines in your example), but it should help start you on your way.

Here's a function that takes index values and uses those to specify the locations for a segment and text that reflects the distance.

add_annotation <- function(index1, index2) {
  x1 = df[index1, 1]   # x1/x2/y1/y2 defined here for shorthand later
  x2 = df[index2, 1]
  y1 = df[index1, 2]
  y2 = df[index2, 2]

  # the function will return the last object it creates, ie this list with two objects
  list(            
    annotate("segment", color = "blue",
      x = x1, xend = x2,
      y = y1, yend = y2),

    annotate("text", color = "black", size = 5,
      x = (x1 + x2) / 2, y = (y1 + y2) / 2,
      label = paste(
        round(sqrt((x1 - x2) ^ 2 + (y1 - y2) ^ 2), digits = 1),
        "feet")
    )
  )
}

We could use this to specify one segment & label at a time...

ggplot(df,aes(x,y,color=z)) + 
  geom_point(size = 5) +
  add_annotation(2, 1)

enter image description here

or use vectors to specify a bunch at one go:

ggplot(df,aes(x,y,color=z)) + 
  geom_point(size = 5) +
  add_annotation(2, c(1,3:9))

enter image description here

And we can feed in vectors of starting and ending indices to get all the dot-to-dot lines in your example:

ggplot(df,aes(x,y,color=z)) + 
  geom_point(size = 5) +
  add_annotation(index1 = c(1, 2, 3, 5), 
                 index2 = c(4, 5, 6, 8))

enter image description here

Adding the vertical lines will take more thought. Curious if others have good ideas for how to approach that.

Upvotes: 1

Related Questions