22

I want to make the following graph in R:

enter image description here

How can I plot those horizontal braces?

Leo
  • 1,861
  • 5
  • 19
  • 18
  • Looks like you tried to add an image and failed. – IRTFM May 30 '11 at 17:08
  • 1
    You might look at http://yihui.name/en/2011/04/produce-authentic-math-formulas-in-r-graphics/#more-719 (I won't post this as an answer since it would take a bit more fussing to get the underbraces spaced correctly / lined up with the points as shown ...) – Ben Bolker May 30 '11 at 20:12

6 Answers6

27

How about something like this?

plot(c(0,1), c(0,1))
text(x = 0.5, y = 0.5, '{', srt = 90, cex = 8, family = 'Helvetica Neue UltraLight')

plot

Adapt it to your purposes. You might find a lighter weight font or a shape you like better. There are hairline fonts if you do a search online.

John
  • 23,360
  • 7
  • 57
  • 83
  • Interesting. I couldn't make it to print properly with ``ggplot2`` though. I used variations on ``+ annotate("text", x = 0, y = 0, label = '{', angle = 90, size = 20, family = 'Helvetica Neue UltraLight')``. The braces come out very thick in the PDF output (they look alright in the RStudio console). – PatrickT Apr 16 '18 at 23:59
  • 1
    @PatrickT you might need to download a hairline font. As you can see, even here the brace is pretty thick. – John Dec 24 '21 at 01:31
15

A little Googling turn up some grid code from a thread on the R help mailing list here. At the very least it gives you something to work with. Here's the code from that post:

library(grid)

# function to draw curly braces in red
# x1...y2 are the ends of the brace
# for upside down braces, x1 > x2 and y1 > y2
Brack <- function(x1,y1,x2,y2,h)
{
   x2 <- x2-x1; y2 <- y2-y1
   v1 <- viewport(x=x1,y=y1,width=sqrt(x2^2+y2^2),
           height=h,angle=180*atan2(y2,x2)/pi,
           just=c("left","bottom"),gp=gpar(col="red"))
   pushViewport(v1)
   grid.curve(x2=0,y2=0,x1=.125,y1=.5,curvature=.5)
   grid.move.to(.125,.5)
   grid.line.to(.375,.5)
   grid.curve(x1=.375,y1=.5,x2=.5,y2=1,curvature=.5)
   grid.curve(x2=1,y2=0,x1=.875,y1=.5,curvature=-.5)
   grid.move.to(.875,.5)
   grid.line.to(.625,.5)
   grid.curve(x2=.625,y2=.5,x1=.5,y1=1,curvature=.5)
   popViewport()}
joran
  • 169,992
  • 32
  • 429
  • 468
  • 1
    This would be a beautiful solution, but according to the Grid package documentation, "Grid graphics and standard R graphics do not mix!" This is very unfortunate. I used the text solution from John but it is not as nice with larger brackets. – David M. Perlman Mar 09 '12 at 21:05
9

I think pBrackets package is the most elegant solution.

To try it with the default plotting function plot, review the the vignettes of the package for examples.

They don't show examples with ggplot2. You could try my code here at stackoverflow to use it with ggplot2 graphs.

Best, Pankil

Community
  • 1
  • 1
Pankil Shah
  • 1,090
  • 12
  • 9
  • I had a similar problem, and this was by far the simplest approach I came across. One limitation of the package, however, is that the dimensions of the bracket are set by the dimensions of the x and y axis. For example, if you have a y axis in 100 increments and x axis in 1 increments, your curve will look a bit distorted. – Jacob H Sep 29 '15 at 21:50
9

Or this:

# Function to create curly braces
# x, y position where to put the braces
# range is the widht
# position: 1 vertical, 2 horizontal
# direction: 1 left/down, 2 right/up
CurlyBraces <- function(x, y, range, pos = 1, direction = 1 ) {

    a=c(1,2,3,48,50)    # set flexion point for spline
    b=c(0,.2,.28,.7,.8) # set depth for spline flexion point

    curve = spline(a, b, n = 50, method = "natural")$y / 2 

    curve = c(curve,rev(curve))

    a_sequence = rep(x,100)
    b_sequence = seq(y-range/2,y+range/2,length=100)  

    # direction
    if(direction==1)
    a_sequence = a_sequence+curve
    if(direction==2)
    a_sequence = a_sequence-curve

    # pos
    if(pos==1)
    lines(a_sequence,b_sequence) # vertical
    if(pos==2)
    lines(b_sequence,a_sequence) # horizontal

    }

plot(0,0,ylim=c(-10,10),xlim=c(-10,10))
CurlyBraces(2, 0, 10, pos = 1, direction = 1 )
CurlyBraces(2, 0, 5,  pos = 1, direction = 2 )
CurlyBraces(1, 0, 10, pos = 2, direction = 1 )
CurlyBraces(1, 0, 5,  pos = 2, direction = 2 )
Sharon
  • 91
  • 1
  • 1
3

With rotation option / And every lines() aka par() option you want

I first mixed up Sharons answer and with another answer i found to a new function with more possibilities. But then i added the "shape" package to the game and now u can put curlybraces in every angle you want to. You don't have to use the package, but if u have 2 points that are not on a horizontal or vertical line it will be very ugly, without shape==T.

CurlyBraces <- function(
  # function to draw curly braces
  x=NA, y=NA,     # Option 1 (Midpoint) if you enter only x, y the position points the middle of the braces
  x1=NA, y1=NA,   # Option 2 (Point to Point) if you additionaly enter x1, y1 then x,y become one
                  #     end of the brace and x1,y1 is the other end
  range=NA,       # (Option 1 only) range is the length of the brace 
  ang=0,          # (Option 1 only, only with shape==T) ang will set the angle for rotation
  depth = 1,      # depth controls width of the shape
  shape=T,        # use of package "shape" necessary for angles other than 0 and 90
  pos=1,          # (only if shape==F) position: 1 vertical, 2 horizontal
  direction=1,    # (only if shape==F)  direction: 1 left/down, 2 right/up
  opt.lines="lty=1,lwd=2,col='red'")   # All posible Options for lines from par (exept: xpd)
                  # enter as 1 character string or as character vector
  {

 if(shape==F){
  # only x & y are given so range is for length
  if(is.na(x1) | is.na(y1)){
    a_sequence = rep(x,100)
    b_sequence = seq(y-range/2,y+range/2,length=100)
    if (pos == 2){
      a_sequence = rep(y,100)
      b_sequence = seq(x-range/2,x+range/2,length=100)
    }
  }
  # 2 pairs of coordinates are given range is not needed
  if(!is.na(x1) & !is.na(y1)){
    if (pos == 1){
      a_sequence = seq(x,x1,length=100)
      b_sequence = seq(y,y1,length=100)  
    }
    if (pos == 2){
      b_sequence = seq(x,x1,length=100)
      a_sequence = seq(y,y1,length=100)      
    }
  }  
  # direction
  if(direction==1)
    a_sequence = a_sequence+curve
  if(direction==2)
    a_sequence = a_sequence-curve

  # pos
  if(pos==1)
    lines(a_sequence,b_sequence, lwd=lwd, col=col, lty=lty,  xpd=NA) # vertical
  if(pos==2)
    lines(b_sequence,a_sequence, lwd=lwd, col=col, lty=lty, xpd=NA) # horizontal
}
 if(shape==T) {
  # Enable input of variables of length 2 or 4
  if(!("shape" %in% installed.packages())) install.packages("shape")
  library("shape")

  if(length(x)==2) {
    helpx <- x
    x<-helpx[1]
    y<-helpx[2]}
  if(length(x)==4) {
    helpx <- x
    x =helpx[1]
    y =helpx[2]
    x1=helpx[3]
    y1=helpx[4]
  }

  # Check input
  if((is.na(x) | is.na(y) )) stop("Set x & y")
  if((!is.na(x1) & is.na(y1)) | ((is.na(x1) & !is.na(y1))))stop("Set x1 & y1")
  if((is.na(x1) & is.na(y1)) & is.na(range)) stop("Set range > 0")

  a=c(1,2,3,48,50)    # set flexion point for spline
  b=c(0,.2,.28,.7,.8) # set depth for spline flexion point

  curve = spline(a, b, n = 50, method = "natural")$y * depth
  curve = c(curve,rev(curve))

  if(!is.na(x1) & !is.na(y1)){
    ang=atan2(y1-y,x1-x)*180/pi-90
    range = sqrt(sum((c(x,y) - c(x1,y1)) ^ 2))
    x = (x + x1)/2
    y = (y + y1)/2
  }

  a_sequence = rep(x,100)+curve
  b_sequence = seq(y-range/2,y+range/2,length=100)

  eval(parse(text=paste("lines(rotatexy(cbind(a_sequence,b_sequence),mid=c(x,y), angle =ang ),",
                        paste(opt.lines, collapse = ", ")
                        ,", xpd=NA)")))
 }
}


# # Some Examples with shape==T
# plot(c(),c(),ylim=c(-10,10),xlim=c(-10,10))
# grid()
# 
# CurlyBraces(4,-2,4,2, opt.lines = "lty=1,col='blue' ,lwd=2")
# CurlyBraces(4,2,2,4, opt.lines = "col=2, lty=1 ,lwd=0.5")
# points(3,3)
# segments(4,2,2,4,lty =  2)
# segments(3,3,4,4,lty =  2)
# segments(4,2,5,3,lty =  2)
# segments(2,4,3,5,lty =  2)
# CurlyBraces(2,4,4,2, opt.lines = "col=2, lty=2 ,lwd=0.5") # Reverse entry of datapoints changes direction of brace
# 
# CurlyBraces(2,4,-2,4, opt.lines = "col=3 , lty=1 ,lwd=0.5")
# CurlyBraces(-2,4,-4,2, opt.lines = "col=4 , lty=1 ,lwd=0.5")
# CurlyBraces(-4,2,-4,-2, opt.lines = "col=5 , lty=1 ,lwd=0.5")
# CurlyBraces(-4,-2,-2,-4, opt.lines = "col=6 , lty=1 ,lwd=0.5")
# CurlyBraces(-2,-4,2,-4, opt.lines = "col=7 , lty=1 ,lwd=0.5")
# CurlyBraces(2,-4,4,-2, opt.lines = "col=8 , lty=1 ,lwd=0.5")
# 
# CurlyBraces( 7.5, 0, ang= 0 , range=5, opt.lines = "col=1 , lty=1 ,lwd=2 " )
# CurlyBraces( 5, 5, ang= 45 , range=5, opt.lines = "col=2 , lty=1 ,lwd=2 " )
# CurlyBraces( 0, 7.5, ang= 90 , range=5, opt.lines = "col=3, lty=1 ,lwd=2"  )
# CurlyBraces( -5, 5, ang= 135 , range=5, opt.lines = "col='blue' , lty=1 ,lwd=2 " )
# CurlyBraces( -7.5, 0, ang= 180 , range=5, opt.lines = "col=5 , lty=1 ,lwd=2 " )
# CurlyBraces( -5, -5, ang= 225 , range=5, opt.lines = "col=6 , lty=1 ,lwd=2"  )
# CurlyBraces( 0, -7.5, ang= 270 , range=5, opt.lines = "col=7, lty=1 ,lwd=2"  )
# CurlyBraces( 5, -5, ang= 315 , range=5, opt.lines = "col=8 , lty=1 ,lwd=2"  )
# points(5,5)
# segments(5,5,6,6,lty =  2)
# segments(7,3,3,7,lty =  2)
# 
# # Or anywhere you klick
# locator(1) -> where  # klick 1 positions in the plot with your Mouse
# CurlyBraces( where$x[1], where$y[1], range=3, ang=45 , opt.lines = "col='blue' , depth=1, lty=1 ,lwd=2"  )
# locator(2) -> where  # klick 2 positions in the plot with your Mouse
# CurlyBraces( where$x[1], where$y[1], where$x[2], where$y[2],  opt.lines = "col='blue' , depth=2, lty=1 ,lwd=2"  )
# 
# # Some Examples with shape == F
# plot(c(),c(),ylim=c(-10,10),xlim=c(-10,10))
# grid()
# 
# CurlyBraces( 5, 0, shape=F, range= 10, pos = 1, direction = 1 , depth=5 ,opt.lines = " col='red' , lty=1 ,lwd=2"  )
# CurlyBraces(-5, 0, shape=F,  range= 5,  pos = 1, direction = 2 , opt.lines = "col='red' , lty=2 ,lwd=0.5")
# CurlyBraces( 1, 4, shape=F,  range= 6,  pos = 2, direction = 1 , opt.lines = "col='red' , lty=3 ,lwd=1.5")
# CurlyBraces(-1,-3, shape=F,  range= 5,  pos = 2, direction = 2 , opt.lines = "col='red' , lty=4 ,lwd=2"  )
# 
# 
# CurlyBraces( 4, 4, 4,-4, shape=F, pos=1, direction = 1 , opt.lines = "col='blue' , lty=1 ,lwd=2" )
# CurlyBraces(-4, 4,-4,-4, shape=F, pos=1, direction = 2 , opt.lines = "col='blue' , lty=2 ,lwd=0.5")
# CurlyBraces(-2, 5, 2, 5, shape=F, pos=2, direction = 1 , opt.lines = "col='blue' , lty=3 ,lwd=1.5")
# CurlyBraces(-2,-1, 4,-1, shape=F, pos=2, direction = 2 , opt.lines = "col='blue' , lty=4 ,lwd=2"  )
# 
# # Or anywhere you klick
# locator(1) -> where  # klick 1 positions in the plot with your Mouse
# CurlyBraces( where$x[1], where$y[1], range=3, shape=F, pos=2, direction = 2 , opt.lines = "col='blue' , depth=1, lty=1 ,lwd=2"  )
# locator(2) -> where  # klick 2 positions in the plot with your Mouse
# CurlyBraces( where$x[1], where$y[1], where$x[2], where$y[2], shape=F, pos=2, direction = 2 , opt.lines = "col='blue' , depth=0.2, lty=1 ,lwd=2"  )
# 
# # Some Examples with shape==T
# plot(c(),c(),ylim=c(-100,100),xlim=c(-1,1))
# grid()
# 
# CurlyBraces(.4,-20,.4,20, depth=.1, opt.lines = "col=1 , lty=1 ,lwd=0.5")
# CurlyBraces(.4,20,.2,40, depth=.1, opt.lines = "col=2, lty=1 ,lwd=0.5")
Swolf
  • 329
  • 2
  • 7
2

very late answer, but for those interested, you can use the ggbrace package to add braces in ggplot2. To install and use it, check the code below. You can customize the brackets as you like, see ggbracegithub page.

devtools::install_github("NicolasH2/ggbrace")
library(ggbrace)
library(ggplot2)

ggplot() + geom_brace(aes(x=c(0,1), y=c(0,1)))

enter image description here

NicolasH2
  • 774
  • 5
  • 20