# Drawing an annotated unit circle with ggplot2

The unit circle: everybody’s favorite circle.

I recently needed to an annotated unit circle for some teaching material I was preparing. Rather than using one of the countless pictures already available, I thought it was a good excuse to play around a bit with using mathematical annotations in ggplot2. This post explains the process.

Here’s what we’ll be working towards:

We start by defining a function that, given a radius, generates a data frame of coordinates of a circle centered around the origin.

library(tidyverse)
library(scales)
library(stringr)

get_circle_coords <- function(r = 1, ...) {
data_frame(theta = seq(0, 2 * pi, ...),
x     = cos(theta) * r,
y     = sin(theta) * r)
}

circ <- get_circle_coords(length.out = 200)
qplot(circ$x, circ$y, geom = "path")

Next, we want to generate the coordinates where we go around the unit circle by one-sixth $$\pi$$ at each step (we drop the last observation to avoid overlap):

coords_pi <- get_circle_coords(by = pi / 6) %>% head(-1)

We can now plot the circle itself as a geom_path, the hubs as geom_point, and spokes as geom_segment. theme_void drops all unnecessary chart junk, and coord_equal makes sure that one unit on the x-axis is equivalent to one unit on the y-axis.

ggplot(coords_pi, aes(x = x, y = y)) +
geom_path(data = circ, color = "grey50") +
geom_point(color = "grey50") +
geom_segment(aes(xend = 0, yend = 0), color = "grey50") +
xlim(-1.2, 1.2) +
ylim(-1.2, 1.2) +
theme_void() +
coord_equal()

We add a character variable that gives the equivalent angles in degrees:

coords_pi$angle <- seq(0, 330, 30) %>% paste(" * degree") To properly typeset the fractions of $$\pi$$ we use R’s built-in support for mathematical annotation (?grDevices::plotmath). Since we only need a few annotations, we can hard-code these.1 coords_pi$pi <- c(
"0", "frac(pi, 6)", "frac(pi, 3)", "frac(pi, 2)",
"frac(2 * pi, 3)", "frac(5 * pi, 6)", "pi",
"frac(7 * pi, 6)", "frac(4 * pi, 3)", "frac(3 * pi, 2)",
"frac(5 * pi, 3)", "frac(11 * pi, 6)"
)

Finally, we use some more plotmath to typeset the coordinates of some of the extact trigonometric constants. This might look a bit fiddly, but we only need to figure out the pattern for the first quadrant, and then just make sure we get the signs right for the other quadrants.

coords_pi$trig <- c( "1*','* 0", "frac(sqrt(3), 2) *','* ~ frac(1,2)", "frac(1, 2) *','* ~ frac(sqrt(3), 2)", "0*','* 1", "-frac(1, 2) *','* ~ frac(sqrt(3), 2)", "-frac(sqrt(3), 2) *','* ~ frac(1,2)", "-1*','* 0", "-frac(sqrt(3), 2) *','* ~ -frac(1,2)", "-frac(1, 2) *','* ~ -frac(sqrt(3), 2)", "0*','* -1", "frac(1, 2) *','* ~ -frac(sqrt(3), 2)", "frac(sqrt(3), 2) *','* ~ -frac(1,2)" ) As pointed out by Rob Creel in a comment, we can also use the bgroup expression to make sure that fractions are enclosed by scalable parentheses. To avoid making the already messy hard-coded string even messier, we define a helper function for this. bgroup_ <- function(x) { sprintf("bgroup('(', %s, ')')", x) } coords_pi$trig <- bgroup_(coords_pi\$trig)

Since we’re going to plot several layers of geom_label we can use purrr::partial to partially apply all the common arguments that these will take:

geom_l <- partial(geom_label, size = 2.5,
label.size = NA, parse = TRUE,
color = "firebrick")

Lastly, we plot the final illustration:

ggplot(coords_pi, aes(x = x, y = y)) +
geom_path(data = circ, color = "grey50") +
geom_point(color = "grey50") +
geom_segment(aes(xend = 0, yend = 0), color = "grey50") +
geom_l(aes(label = angle, x = x / 2, y = y / 2)) +
geom_l(aes(label = pi, x = x * 4/5, y = y * 4/5)) +
geom_l(aes(label = trig), fill = NA,
vjust = "outward", hjust = "outward") +
xlim(-1.2, 1.2) +
ylim(-1.2, 1.2) +
theme_void() +
coord_equal()

1. Commenter Centerleft spotted a typo in an earlier version of this code.