Make your own ggclock

I have once again been inspired by a tweet! This one came from @WeAreRLadies, which was being moderated by Alison Hill at the time.

Alison was at RStudio headquarters in Boston, when she noticed a ggplot2 themed clock! To which I had a totally normal reaction.

I decided that I must have one. So I set about trying to recreate the ggclock.

Create the data

First things first. If we are going to create a plot, we need some data. Specifically, we need a point a for every minute, and then larger points for each hour. We create a point for every minute, 0 through 60, and we can mark the hours by selecting the 5 minute marks. I’ve set y to be 1, as this will end up being the radius of our circle.

library(tidyverse)

minutes <- data_frame(x = 0:60, y = 1)
minutes
#> # A tibble: 61 x 2
#>        x     y
#>    <int> <dbl>
#>  1     0     1
#>  2     1     1
#>  3     2     1
#>  4     3     1
#>  5     4     1
#>  6     5     1
#>  7     6     1
#>  8     7     1
#>  9     8     1
#> 10     9     1
#> # … with 51 more rows

hours <- filter(minutes, x %% 5 == 0)
hours
#> # A tibble: 13 x 2
#>        x     y
#>    <int> <dbl>
#>  1     0     1
#>  2     5     1
#>  3    10     1
#>  4    15     1
#>  5    20     1
#>  6    25     1
#>  7    30     1
#>  8    35     1
#>  9    40     1
#> 10    45     1
#> 11    50     1
#> 12    55     1
#> 13    60     1

Now that we have data, we can start making the plot!

Making the ggclock

We start by plotting each of the point (minutes and hours). Consistent with the original ggclock, we will make the hour markers slightly larger and colored, with the colors starting at 12 o’clock (the 60 minute mark).

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 4, show.legend = FALSE)

The next step is to put these points into a cirle. This can be done with coord_polar.

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 4, show.legend = FALSE) +
  coord_polar()

Now it’s starting to look more like a clock! In order to not waste any space, we can expand the limits of the y-axis.

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 5, show.legend = FALSE) +
  coord_polar() +
  expand_limits(y = c(0, 1))

Next, we can set the breaks of the x-axis to show the necessary hour marks. We can also set the breaks of the y-axis, which control the circles on the interior of the clock.

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 5, show.legend = FALSE) +
  coord_polar() +
  expand_limits(y = c(0, 1)) +
  scale_x_continuous(breaks = seq(15, 60, 15), labels = c(3, 6, 9, "0/12")) +
  scale_y_continuous(breaks = seq(0, 1, 0.25))

So close! Now we just need some formatting. First we specify that we are using theme_grey, and scaling the colors using scale_color_discrete. This is not strictly necessary, as these are the default, but being explict will allow us to easily change these options later on. We then modify the theme using the theme function. We need to remove axis titles and ticks, and labels on the y-axis. This can be done with element_blank. For the x-axis labels (our hour markers) we increase the font size using element_text. Finally, we can make the grid lines more distinct by increase the size of the panel grid using element_line.

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 5, show.legend = FALSE) +
  coord_polar() +
  expand_limits(y = c(0, 1)) +
  scale_x_continuous(breaks = seq(15, 60, 15), labels = c(3, 6, 9, "0/12")) +
  scale_y_continuous(breaks = seq(0, 1, 0.25)) +
  scale_color_discrete() +
  theme_grey() +
  theme(
    axis.title = element_blank(),
    axis.ticks = element_blank(),
    axis.text.x = element_text(size = 20),
    axis.text.y = element_blank(),
    panel.grid.major = element_line(size = 2),
    panel.grid.minor = element_line(size = 2)
  )

And now we have our clock face! There are just a couple of details remaining to fully replicate the original: the “ggclock” text, and the RStudio ball. The text can be added using geom_text.

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 5, show.legend = FALSE) +
  geom_text(aes(x = 30, y = 0.5, label = "ggclock"), vjust = 1, size = 8) +
  coord_polar() +
  expand_limits(y = c(0, 1)) +
  scale_x_continuous(breaks = seq(15, 60, 15), labels = c(3, 6, 9, "0/12")) +
  scale_y_continuous(breaks = seq(0, 1, 0.25)) +
  scale_color_discrete() +
  theme_grey() +
  theme(
    axis.title = element_blank(),
    axis.ticks = element_blank(),
    axis.text.x = element_text(size = 20),
    axis.text.y = element_blank(),
    panel.grid.major = element_line(size = 2),
    panel.grid.minor = element_line(size = 2)
  )

Finally, the RStudio ball can be added using geom_image from Guangchuang Yu’s ggimage package.

library(ggimage)
logo <- "https://www.rstudio.com/wp-content/uploads/2014/06/RStudio-Ball.png"

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 5, show.legend = FALSE) +
  geom_text(aes(x = 30, y = 0.5, label = "ggclock"), vjust = 1, size = 8) +
  geom_image(aes(x = 60, y = 0.75, image = logo), size = 0.08) +
  coord_polar() +
  expand_limits(y = c(0, 1)) +
  scale_x_continuous(breaks = seq(15, 60, 15), labels = c(3, 6, 9, "0/12")) +
  scale_y_continuous(breaks = seq(0, 1, 0.25)) +
  scale_color_discrete() +
  theme_grey() +
  theme(
    axis.title = element_blank(),
    axis.ticks = element_blank(),
    axis.text.x = element_text(size = 20),
    axis.text.y = element_blank(),
    panel.grid.major = element_line(size = 2),
    panel.grid.minor = element_line(size = 2)
  )

And there we have it! Our very own ggclock!

Changing the theme

As I mentioned earlier, we can also change the theme to give the ggclock a different look. For example, here is the clock with theme_dark and scale_color_viridis_d.

ggplot() +
  geom_point(data = minutes, aes(x = x, y = y)) +
  geom_point(data = hours, aes(x = x, y = y, 
                               color = factor(x, levels = c(60, seq(5, 55, 5)))),
             size = 5, show.legend = FALSE) +
  geom_text(aes(x = 30, y = 0.5, label = "ggclock"), vjust = 1, size = 8) +
  geom_image(aes(x = 60, y = 0.75, image = logo), size = 0.08) +
  coord_polar() +
  expand_limits(y = c(0, 1)) +
  scale_x_continuous(breaks = seq(15, 60, 15), labels = c(3, 6, 9, "0/12")) +
  scale_y_continuous(breaks = seq(0, 1, 0.25)) +
  scale_color_viridis_d() +
  theme_dark() +
  theme(
    axis.title = element_blank(),
    axis.ticks = element_blank(),
    axis.text.x = element_text(size = 20),
    axis.text.y = element_blank(),
    panel.grid.major = element_line(size = 2),
    panel.grid.minor = element_line(size = 2)
  )

Conclusion

If you are interested in just the code to generate the ggclock, it can be found in this gist. If you actually want to get the ggclock made, you can order a custom clock here. I’ve also included larger, high resolution ggclock images in the comments of the gist that are suitable for printing. If you do actually get a ggclock made, tweet a picture at me (@wjakethompson)! I’d love to see more out in the wild!

Session info

devtools::session_info()
#> ─ Session info ──────────────────────────────────────────────────────────
#>  setting  value                       
#>  version  R version 3.5.1 (2018-07-02)
#>  os       macOS High Sierra 10.13.6   
#>  system   x86_64, darwin15.6.0        
#>  ui       X11                         
#>  language (EN)                        
#>  collate  en_US.UTF-8                 
#>  ctype    en_US.UTF-8                 
#>  tz       America/Chicago             
#>  date     2018-12-02                  
#> 
#> ─ Packages ──────────────────────────────────────────────────────────────
#>  package      * version     date       lib
#>  assertthat     0.2.0       2017-04-11 [1]
#>  backports      1.1.2       2017-12-13 [1]
#>  base64enc      0.1-3       2015-07-28 [1]
#>  blogdown       0.9         2018-10-23 [1]
#>  bookdown       0.7         2018-02-18 [1]
#>  broom          0.5.0       2018-07-17 [1]
#>  callr          3.0.0       2018-08-24 [1]
#>  cellranger     1.1.0       2016-07-27 [1]
#>  cli            1.0.1       2018-09-25 [1]
#>  colorspace     1.4-0       2018-06-23 [1]
#>  crayon         1.3.4       2017-09-16 [1]
#>  curl           3.2         2018-03-28 [1]
#>  debugme        1.1.0       2017-10-22 [1]
#>  desc           1.2.0       2018-05-01 [1]
#>  devtools       2.0.1       2018-10-26 [1]
#>  digest         0.6.18      2018-10-10 [1]
#>  dplyr        * 0.7.99.9000 2018-11-03 [1]
#>  evaluate       0.12        2018-10-09 [1]
#>  fansi          0.4.0       2018-11-03 [1]
#>  forcats      * 0.3.0       2018-02-19 [1]
#>  fs             1.2.6       2018-08-23 [1]
#>  gganimate    * 0.1.1       2018-11-25 [1]
#>  ggimage      * 0.1.7       2018-06-19 [1]
#>  ggplot2      * 3.1.0       2018-10-25 [1]
#>  ggplotify      0.0.3       2018-08-03 [1]
#>  glue           1.3.0       2018-07-17 [1]
#>  gridGraphics   0.3-0       2018-04-03 [1]
#>  gtable         0.2.0       2016-02-26 [1]
#>  haven          1.1.2       2018-06-27 [1]
#>  hms            0.4.2       2018-03-10 [1]
#>  htmltools      0.3.6       2017-04-28 [1]
#>  httr           1.3.1       2017-08-20 [1]
#>  jsonlite       1.5         2017-06-01 [1]
#>  knitr          1.20.15     2018-09-04 [1]
#>  labeling       0.3         2014-08-23 [1]
#>  lattice        0.20-35     2017-03-25 [2]
#>  lazyeval       0.2.1       2017-10-29 [1]
#>  lubridate      1.7.4       2018-04-11 [1]
#>  magick         2.0         2018-10-05 [1]
#>  magrittr       1.5         2014-11-22 [1]
#>  memoise        1.1.0       2017-04-21 [1]
#>  modelr         0.1.2       2018-05-11 [1]
#>  munsell        0.5.0       2018-06-12 [1]
#>  nlme           3.1-137     2018-04-07 [1]
#>  pillar         1.3.0.9001  2018-11-03 [1]
#>  pkgbuild       1.0.2       2018-10-16 [1]
#>  pkgconfig      2.0.2       2018-08-16 [1]
#>  pkgload        1.0.2       2018-10-29 [1]
#>  plyr           1.8.4       2016-06-08 [1]
#>  prettyunits    1.0.2       2015-07-13 [1]
#>  processx       3.2.0       2018-08-16 [1]
#>  ps             1.2.0       2018-10-16 [1]
#>  purrr        * 0.2.5       2018-05-29 [1]
#>  R6             2.3.0       2018-10-04 [1]
#>  Rcpp           1.0.0.1     2018-11-25 [1]
#>  readr        * 1.1.1       2017-05-16 [1]
#>  readxl         1.1.0       2018-04-20 [1]
#>  remotes        2.0.2       2018-10-30 [1]
#>  rlang          0.3.0.1     2018-10-25 [1]
#>  rmarkdown      1.10        2018-06-11 [1]
#>  rprojroot      1.3-2       2018-01-03 [1]
#>  rstudioapi     0.8         2018-10-02 [1]
#>  rvcheck        0.1.1       2018-09-27 [1]
#>  rvest          0.3.2       2016-06-17 [1]
#>  scales         1.0.0.9000  2018-11-25 [1]
#>  sessioninfo    1.1.0       2018-09-25 [1]
#>  stringi        1.2.4       2018-07-20 [1]
#>  stringr      * 1.3.1       2018-05-10 [1]
#>  testthat       2.0.1       2018-10-13 [1]
#>  tibble       * 1.4.99.9005 2018-11-25 [1]
#>  tidyr        * 0.8.2       2018-10-28 [1]
#>  tidyselect     0.2.5       2018-10-11 [1]
#>  tidyverse    * 1.2.1       2017-11-14 [1]
#>  usethis        1.4.0       2018-08-14 [1]
#>  utf8           1.1.4       2018-05-24 [1]
#>  viridisLite    0.3.0       2018-02-01 [1]
#>  withr          2.1.2       2018-03-15 [1]
#>  xfun           0.4         2018-10-23 [1]
#>  xml2           1.2.0       2018-01-24 [1]
#>  yaml           2.2.0       2018-07-25 [1]
#>  source                              
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  R-Forge (R 3.5.0)                   
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.1)                      
#>  CRAN (R 3.5.0)                      
#>  Github (tidyverse/dplyr@6123e0e)    
#>  CRAN (R 3.5.0)                      
#>  Github (brodieG/fansi@ab11e9c)      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  Github (thomasp85/gganimate@279d9be)
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  Github (yihui/knitr@4cf7a6f)        
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.1)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  Github (r-lib/pillar@c5bf622)       
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  Github (RcppCore/Rcpp@4f168e6)      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  Github (hadley/scales@7cd8121)      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  Github (tidyverse/tibble@984ffae)   
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#>  CRAN (R 3.5.0)                      
#> 
#> [1] /Users/jakethompson/R
#> [2] /Library/Frameworks/R.framework/Versions/3.5/Resources/library
comments powered by Disqus