Search code examples
rggplot2triangle

How to make squares on each side of the triangle without hard-coding the squares?


I have a data.frame containing 4 points, which creates a triangle when connected using geom_path:

library(ggplot2)

triangle = data.frame(x = c(0, 0.5, 1, 0),
                      y = c(0, 0.5, 0, 0))

ggplot(triangle, aes(x, y)) +
        geom_path()

enter image description here

Now, I want to create a new data.frame (based on triangle), that has 4 points (e.g. xmin, xmax, ymin, ymax) that creates squares from the sides of the triangle (hence, this data.frame will have 3 rows (for each square) and 4 columns (for each point).

Here is an example:

enter image description here

Is it possible to do it without hard-coding the sides of the squares?


Solution

  • Since the squares will be at an angle, you probably need the output to be in terms of x, y co-ordinates of the vertices. This just requires a bit of trig. The following function takes a vector of x, y points representing a closed triangle and returns a data frame of the vertices of the squares on each side:

    make_squares <- function(x, y) {
      
      x1    <- x[-4]
      x2    <- x[-1]
      xdiff <- (x2 - x1)
        
      y1    <- y[-4]
      y2    <- y[-1]
      ydiff <- (y2 - y1)
      
      lengths <- sqrt(xdiff^2 + ydiff^2)
      angles  <- atan2(ydiff, xdiff)
      
      x3 <- x2 - sin(angles) * lengths
      x4 <- x1 - sin(angles) * lengths
      
      y3 <- y2 + cos(angles) * lengths
      y4 <- y1 + cos(angles) * lengths
      
      
      df <- data.frame(x = round(c(x1, x2, x3, x4, x1), 3),
                       y = round(c(y1, y2, y3, y4, y1), 3),
                       square = rep(1:3, 5))
      `row.names<-`(df[order(df$square),], NULL)
    }
    

    The output looks like this:

    make_squares(triangle$x, triangle$y)
    #>       x    y square
    #> 1   0.0  0.0      1
    #> 2   0.5  0.5      1
    #> 3   0.0  1.0      1
    #> 4  -0.5  0.5      1
    #> 5   0.0  0.0      1
    #> 6   0.5  0.5      2
    #> 7   1.0  0.0      2
    #> 8   1.5  0.5      2
    #> 9   1.0  1.0      2
    #> 10  0.5  0.5      2
    #> 11  1.0  0.0      3
    #> 12  0.0  0.0      3
    #> 13  0.0 -1.0      3
    #> 14  1.0 -1.0      3
    #> 15  1.0  0.0      3
    
    

    And you can use it in your plot like this:

    ggplot(triangle, aes(x, y)) + 
      geom_path() +
      geom_polygon(data = make_squares(triangle$x, triangle$y),
                   aes(group = square), fill = "green4", color = "black") +
      coord_equal()
    

    enter image description here