A Christmas Peril: Drawing Holiday Cards with ggplot2

R
ggplot2
Personal
Creativity
Author

Ken Vu

Published

December 25, 2023

Introduction

What do you do when you live in an area that hardly snows and you don’t have room for a Christmas tree to display in your house? The answer - subject yourself to your wildest imagination through art. And by art, I mean visualization through technical means.

I’ll be honest. I have very little artistic talents; however, I do have one bit of knowledge up my sleeve - programming in R. So what did I do to get that snowy Christmas setting I dreamed of earlier? I decided to use R code to draw a holiday card, using nothing more than the ggplot2 library, lots of trial and error, and a creative, artistic vision (as I drafted physically below prior to coding).

An initial physical draft I drew of the holiday card where I have drawn out a Christmas tree and a snowman with numbers marking coordiantes for different aprts of the Christmas tree as well as the measurements for the lengths, widths, and heights of different aspects of the tree.  The top of the paper reads, 'GOAL: Draw an Xmas Tree w/ Shiny App'.

An initial draft of the holiday card I wanted to make. Typically, before any project (even coding), I like to write out and brainstorm my plans on paper so I can have a clear vision of what I want to achieve with any given project. Boy, how much this draft differed so much from the final product below.

A drawing of a snowy ground featuring a black background with falling snow, a snowman at the right of the picture with a black top hat and blue scarf, and a green Christmas tree with a brown trunk, red, blue, and green ornaments hanging off it, light grey sashes wrapped around the tree, and a yelow star at the top of the tree.  The text at the top reads (in cursive),  'Happy Holidays!' as the photo is wrapped in a light brown frame with a black cursive caption at the bottom right thatreads, 'Made with RStudio'.

The final product, courtesy of RStudio and lots and lots of R code.

As ambitious and counterproductive this endeavor seemed to be (since I could’ve done it faster using Microsoft Paint), I actually learned a lot about coding in R through this project, which I’ll explore at the end of this article (and perhaps, subsequent ones). For now, here’s how I drew the holiday card above using RStudio.

Drawing the Holiday Card

To continue further with this article, you would need to have the libraries ggplot2, dplyr, pacman, and extrafont installed on your computer.

A quick way to go about it would be to install pacman and run the code chunk below.

library(pacman)
p_load(ggplot2, dplyr, extrafont)

Optionally, if you want to see the code here to follow along (and get the .tff files to get the Segue Script font), you can see my GitHub for this code here: https://github.com/Ken-Vu/Drawing-a-Holiday-Card-with-ggplot2

With the libraries installed, let’s start building a holiday card (much of which involved some trail and error and sketching to plan out the coordinates for the items on it).

A. Adding the Christmas Tree

To begin, we make the parts of the tree, which consists of the tree’s body as well as the trunk. Note that the coordinates here (as with any point locations moving forward) have been planned out and tweaked through countless trial and error to get right.

xmas_tree <- tribble(
  ~x, ~y,
  
   # left side of tree base
   -6, 5,
   -3, 10,
   -4.5, 10,
   -2, 15,
  
   # top of tree
   -3, 15,
   0, 20,
   3, 15,
  
   # right side of tree base
   2, 15,
   4.5, 10,
   3, 10,
   6, 5
)

# coordinates for the tree trunk
xmas_trunk <- tribble(
  ~x, ~y,
   -1, 5,
   1, 5,
   1, 0,
   -1, 0
)

Let’s include some Christmas ornaments and sashes to decorate the tree with as well.

# making christmas sashes
xmas_sashes <- tribble(
  ~x, ~y, ~xend, ~yend,
  -1.5, 17.6, 2.0, 16.6,
  -2.5, 14.0, 3.5, 12.0,
  -3.5, 9.4, 5.0, 6.8
)

xmas_ornaments <- tribble(
  ~x, ~y, 
   -6, 5, 
   -4.5, 10,
   -3, 15,
    3, 15,
    4.5, 10, 
    6, 5,
   -1, 15,
 0.25, 13,
 1.75, 14,
   -1, 9,
 0.75, 7,
  2.5, 8,
 -3.5, 7,
 -2.5, 11.7,
  0.5, 17.8
)

Then, we put all the tree parts together to make a well-decorated tree. Let’s also add a nice yellow star at the top of the tree to make it more festive.

Here, we use geom_polygon() to create shapes where each of the coordinates in a given data set serve as end points of different corners of this shape, geom_point() to plot a set of coordinates (which by default are dots, but can be modified to be any shape, including a star), and geom-curve() to plot a curved line going from one point to the next.

xmas_ggplot <- ggplot() +
  # adding tree
  geom_polygon(data = xmas_tree,
                mapping = aes(x, y),
            fill = "#1f6132") + 
  geom_polygon(data = xmas_trunk,
               mapping = aes(x, y),
               fill = "#413506"
               ) +
  
      # adding Christmas star
  geom_point(data = data.frame(x = 0, y = 20.25),
             mapping = aes(x, y),
             size = 15,
             shape="\u2605",
             color = "#f4d457" 

             ) +
  
  geom_point(data = xmas_ornaments,
             mapping = aes(x,y),
             color = "#800000",
             size = 3) +
  
  # adding sashes to wrap around xmas tree
    geom_curve(data = xmas_sashes,
             mapping = aes(x = x, y = y, 
                           xend = xend, 
                           yend = yend),
             size = 3,
             color = "darkgrey",
             
             # how curved the point is
             curvature = 0.35, 
             
             # how transparent the shape/point is 
             # alpha = 1 means it'll be solid, 
             # alpha = 0 means very transparent
             alpha = 0.8) +
  
  # use this instead of xlim() and ylim() as those two aforementioned
  # functions will discard points that're out of range
  coord_cartesian(ylim = c(0, 35), xlim = c(-15, 15)) +
  
  labs(caption = "Made with RStudio")
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
xmas_ggplot

B. Adding background details

Change background colors of plot

Let’s make the plotting area black to reflect a night sky as well as create a “golden frame” to put this picture in.

We can also remove the axis titles, axis text, gridlines, and tick marks.

xmas_ggplot <- xmas_ggplot +
  theme(
    # background of black sky
    panel.background = element_rect(fill = "black"), 
    
    # removing misc plotting elements
    axis.title = element_blank(),
    axis.ticks = element_blank(),
    axis.text = element_blank(),
    panel.grid = element_blank(),
    
    # adding golden frame around photo
    plot.background = element_rect(fill = "#5a410c"),
    
    # removing plotting legend
    legend.position = "none"
  )
xmas_ggplot

Adding snow

Let’s randomly generate some snow so we can add a level. We’ll use the random number generator based on the uniform distribution to get random coordinates we’ll plot as a scattered group of 300 white closed circles, which’ll create a “snowing” effect on our Christmas drawing.

# TRIVIA: the seed number is the release date 
# of the song "Last Christmas" by Wham! (aka December 3, 1984)
set.seed(120384) 

snow_points <- data.frame(
  x = runif(n = 300, min = -20, max = 17),
  y = runif(n = 300, min = -2, max = 36)
) 

xmas_ggplot <- xmas_ggplot + 
  geom_point(data = snow_points,
             mapping = aes(x, y),
             color = "white"
             )
xmas_ggplot

We can also add a snowy ground to the landscape to complete it. Here, we set up the points to get snowy ground with some slopes.

xmas_snowground <- tribble(
  ~x, ~y,
 -30, 2.5,
 -10, 0.5,
  -5, 1.5,
   0, 0.2,
   5, 0.8,
  12, 0.4,
  30, 1
)

Here, we use geom_ribbon() to shade in the area below the curve generated by geom_smooth(), which plots a smooth curved line through the data points in the tribble xmas_snowground to create a bumpy, snowy ground that enhances the flat ground made with geom_hline() (a horizontal line generator). geom_ribbon() allows you to shade in areas under a set of points or a line where you can control where the shading begins and where the shading ends using arguments such as xmax, xmin, ymax, ymin, etc.

Ex. ymax=y in the aes() function indicates you want the boundaries of the shaded area to depend on the values of the coordinates for the y-axis for a data set with coordinates for the x-axis and the y-axis. Here, the y-coordinates come from the tribble for setting the slopes of the snowy ground (i.e., xmas_snowground).

xmas_ggplot<- xmas_ggplot + 
  # plotting an uneven snowy ground
  geom_smooth(data = xmas_snowground,
             mapping = aes(x = x, y = y),
             color = "white",
             se = F) + 
  geom_ribbon(data = xmas_snowground,
                 aes(x = x, ymax=y), 
                 xmin = -Inf,
                 xmax = Inf,
                 ymin = 0, 
                 fill="white") +
  
  # adding a flat horizontal line for the snowy ground
  geom_hline(yintercept = seq(0.5, -50, -0.01),
             color = "white",
             fill = "white")
Warning in geom_hline(yintercept = seq(0.5, -50, -0.01), color = "white", :
Ignoring unknown parameters: `fill`
xmas_ggplot
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Write a holiday greeting

No holiday card’s fully complete without a holiday greeting. Let’s create one as a text label on the plot.

Before any labeling, we can import and prepare the Segoe Script fonts in the two .tff files in our project folder using the font_import and loadFonts functions from the extraFont package. My GitHub repository has the two .tff files in the project folder that you can use and put in your working directory.

Note that you should only run the following two code chunks below once as it’s quite taxing to run; it can take a couple of minutes to load.

If you’re interested, you can read the documentation here for the extraFont package for more details on how it works and how it may differ depending on your machine’s operating system: https://cran.r-project.org/web/packages/extrafont/index.html.

font_import(paths = getwd(), prompt = F)
loadfonts()

Let’s add a holiday greeting as well as stylize the caption with geom_text() for plotting labels onto your graph and the theme() layer for adjusting visual aspects of your plots.

xmas_ggplot <- xmas_ggplot +
  
  # adding holiday greeting
  geom_text(aes(label = "Happy Holidays!", 
                 x = 0, y = 29),
            color = "#f4d457", 
            size = 16,
            family = "Segoe Script") +
  
  # adjusting the caption text
  theme(plot.caption = element_text(family = "Segoe Script", face = "bold"))
xmas_ggplot
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

(Optional) C: Do you wanna build a snowman?

If you’re savvy enough, you can add a snowman to this picture to finish it off. It’s a common Christmas dream for holiday celebrators as well as fans of the movie Frozen.

We can use tribbles to create coordinates and data for the snowman’s physical body, eyes, chest, and arms.

# coordinates for three snowballs forming snowman's body
snowman_body <- tribble(
  ~x_val, ~y_val,
  10, 3, 
  10, 8,
  10, 12, 
)

snowman_parts <- tribble(
  ~x_val, ~y_val,
  
  # eyes
  9.55, 12.85, 
  10.45, 12.85,
  
  # central body dots
  10, 9.15,
  10, 8,
  10, 6.85
  
)

# storing data for snowman arms
snowman_arm1 <- tribble(
  ~x, ~y, ~xend, ~yend,
  9, 9, 7, 11
)

snowman_arm2 <- tribble(
  ~x, ~y, ~xend, ~yend,
  11, 9, 13, 11
)

# data for snowman's mouth
snowman_mouth <- tribble(
  ~x, ~y, ~xend, ~yend,
  9.25, 11.25, 10.75, 11.25
)

Then, we use the geom_segment() function to draw the arms of the snowman by specifying the endpoints of the line segments we want to draw. Additionally, we use geom_point() to plot the eyes, chest dots, and nose of the snowman as well as geom_curve() for drawing the snowman’s mouth.

xmas_ggplot <- xmas_ggplot +
  
  # draw arms
  geom_segment(data = snowman_arm1,
            mapping = aes(x = x, y = y,
                          xend = xend,
                          yend = yend),
            color = "#9b7115",
            linewidth = 1.6,
            lineend = "round") + 
  
  geom_segment(data = snowman_arm2,
            mapping = aes(x = x, y = y,
                          xend = xend,
                          yend = yend),
            color = "#9b7115",
            linewidth = 1.6,
            lineend = "round") + 
  
  
  # plotting snowman's body
  geom_point(data = snowman_body[1,],
             mapping = aes(x_val, y_val),
             size = 25,
             color = "white") + 
  geom_point(data = snowman_body[2,],
             mapping = aes(x_val, y_val),
             size = 19,
             color = "white") + 
  geom_point(data = snowman_body[3,],
             mapping = aes(x_val, y_val),
             size = 16,
             color = "white") + 
  
  # plotting the eyes and body dots of the snowman
  geom_point(data = snowman_parts,
             mapping = aes(x_val, y_val),
             color = "black",
             size = 1.6
             ) +
  
  # plotting the nose 
  geom_point(data = data.frame(x = 10, y = 11.85),
             mapping = aes(x, y),
             color = "darkorange",
             size = 1.6) +
  
  # draw the mouth
  geom_curve(data = snowman_mouth,
             mapping = aes(x = x, y = y, 
                           xend = xend, 
                           yend = yend),
             linetype = 2)
xmas_ggplot
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

As a final touch, let’s add a hat and scarf onto our fellow snowman here for extra warmth and comfort for the holidays.

We’ll do the same thing as before and use tribbles and data frames to store coordinates needed to draw parts of the hat.

# coordinates for top of the hat
snowman_hat_top <- tribble(
  ~x0, ~x1, ~y0, ~y1,
  9.25, 10.75, 14.5, 17
)

# two tribbles for coordinates for parts of the scarf
snowman_scarf <- tribble(
  ~x, ~y, ~xend, ~yend,
  9, 10, 11, 10
)

snowman_scarf2 <- tribble(
  ~x, ~y, ~xend, ~yend,
  9.25, 10, 8, 6
)
xmas_ggplot <- xmas_ggplot + 
  
  # hat base
  geom_segment(
    data = data.frame(x = 8.5, y = 14,
                      xend = 11.5, yend = 14),
    mapping = aes(x = x, y = y,
                  xend = xend, yend = yend),
    color = "#2c2c2c",
    lineend = "round",
    size = 2
  ) + 
  
    # hat top
    geom_rect(
      data = snowman_hat_top,
      mapping = aes(xmin = x0, ymin = y0,
                    xmax = x1, ymax = y1),
      fill = "#2c2c2c"
      
    ) + 
  
    # red hat band
    geom_segment(
    data = data.frame(x = 9.25, y = 14.45,
                      xend = 10.75, yend = 14.45),
    mapping = aes(x = x, y = y,
                  xend = xend, yend = yend),
    color = "#691916",
    lineend = "round",
    size = 2
  ) +
  # draw the scarf
  geom_curve(data = snowman_scarf2,
             mapping = aes(x = x, y = y, 
                           xend = xend, 
                           yend = yend),
             linewidth = 3,
             color = "#2b74b0",
             curvature = -0.25) +
  
  geom_curve(data = snowman_scarf,
             mapping = aes(x = x, y = y, 
                           xend = xend, 
                           yend = yend),
             linewidth = 3,
             color = "#1f639b") 
xmas_ggplot + 
  labs(alt = "A drawing of a snowy ground featuring a black background with falling snow, a snowman at the right of the picture with a black top hat and blue scarf, and a green Christmas tree with a brown trunk, red, blue, and green ornaments hanging off it, light grey sashes wrapped around the tree, and a yelow star at the top of the tree.  The text at the top reads (in cursive),  'Happy Holidays!' as the photo is wrapped in a light brown frame with a black cursive caption at the bottom right thatreads, 'Made with RStudio'.")
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

If you want, you can save it to your computer by running this code once. It’ll be in your working directory wherever you run this code.

ggsave("holiday_card.png", plot = xmas_ggplot,
       width = 8, height = 5)
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Conclusion

Creating a holiday card using RStudio started out as a passion project of mine to find ways to express holiday cheer and combine it with RStudio in some way; I took inspiration from all the wonderful visuals I’ve seen other programmers made in the past. I wondered if programming had potential to explore art creation (in a literal sense as I did here).

Along the way, I found out how challenging and practical using R to create drawings would be; I spent days sketching a rough draft of the holiday’s card layout, scrolling through numerous articles and Stackoverflow posts on ggplot2 functions, and rerunning and retyping code chunks over and over again. In a more practical sense, I would’ve saved more time and reduced a lot of head scratching moments by completing the drawing with application Microsoft Paint instead, which would only take maybe less than an hour versus a couple of days with RStudio.

At the same time, I ended up surprisingly picking up a lot of extra familiarity and useful understanding of ggplot2 through this project. I learned about using the coord_cartesian() function to keep ggplot2 graphs from throwing out out of range points (which xlim() and ylim() would do). I leaned about creating shapes and objects besides data points using functions such as geom_polygon(), geom_curve(), and geom_ribbon(). I also learned about how to engineer solutions on the fly when things never went as expected, knowing what I want in the end to keep myself focused.

Overall, it’s been a fun ride creating a holiday card using the power of RStudio and ggplot2. I hope you found this blog meaningful to you and I wish you all a great Christmas season and a wonderful new year!

Take care and stay turned for more from The R Files!

Resources

I recommend the following resources for enhancing your exploration and experimentation with the ggplot2 library.

A. Books

  • R for Data Science: This book (now in it’s second edition) is a classic and covers some of the bare essentials needed to work with and display all kinds of data as well as strategies for writing clean code. Chapters 10-12 are relevant for those focused on data visualization in general as well as Chapters 2-9 for generally good practices for writing and maintaining clean code. You can read it online here: https://r4ds.hadley.nz/

  • ggplot2: Elegant Graphics for Data Analysis: This is a good book that explains the grammar of graphics of ggplot2 and how it works under the surface. Chapters 3-5, 8, 9, 10-14, and 17 are some chapters I recommend for understanding more of the basics of ggplot2’s plotting functions, the steps for making ggplot2 graphs in general, and some of the ways in which the plot aesthetics are made and how they can be modified. You can read the work-in-progress version online here: https://ggplot2-book.org/

  • Data Visualization with R by Rob: It’s a direct guide on building plots with ggplot2 along with best practices for data visualizations in general. Chapters 3-6, 11, and 14 directly focus on ggplot2, its wide array of customization for the aesthetics of its plots, and best practices for creating effective and visually sound graphs. Chapter 10 is also interesting if you’d like to explore other graphs besides the conventional 2D bar plots and scatterplots you see often in ggplot2, such as dumbbell plots and heat maps. Currently, it’s only available as an online bookdown, but a book version is reportedly in the works of being available on Amazon soon.