A lot has changed with tvthemes 1.0.0 so please check the CRAN announcement blog post along with this blog post!

tvthemes 1.1.0 is on CRAN (Oct. 31, 2019), please checkout the update blog post

This blog post will provide an intro to {tvthemes} as well as some lessons learned (codecov, Github badges, creating a hexsticker, usethis::use_*(), unit testing for ggplot2, etc.) and the future direction of the package. What kick-started this idea was my blog post looking at simple TV stats on my current favorite TV show, Brooklyn Nine-Nine. I got a lot of good feedback on the colors I used for the custom ggplot2 theme and color palettes so I decided to expand it to other shows that I love!

Suggestions and Pull Requests for palettes/themes are welcome, you can find the Github repo for the package HERE!

pacman::p_load(ggplot2, dplyr, tvthemes, extrafont, 
               glue, gapminder, emo, patchwork, cowplot)
loadfonts()

Current list of TV shows

  • Avatar: The Last Airbender: theme + palettes (Fire Nation, Water Tribe, Earth Kingdom, & Air Nomads)
  • Brooklyn Nine-Nine: theme + palettes (regular & dark)
  • Game of Thrones/A Song of Ice & Fire: ‘The Palettes of Ice & Fire’ (currently: Stark, Lannister, Tully, Targaryen, Greyjoy, & Tyrell)
  • Rick & Morty: theme + palette
  • Parks & Recreation: two themes (light & dark) + palette
  • The Simpsons: theme + palette
  • Spongebob Squarepants: theme + palette + background images
  • More in future releases…

Installation

tvthemes is currently available only on Github, you can install it by:

## install.packages(devtools)
devtools::install_github("Ryo-N7/tvthemes")

I hope to have a CRAN version soon!

Fonts

The difficulty with a lot of the fonts used by TV shows in their logos and other marketing media is that they are made by font foundries and can be rather expensive (for a regular person like you or me) to purchase. So I endeavored to find free fonts to use that were somewhat similar to the real ones used by the shows from resources like Google Fonts. In the documentation you can find the actual fonts used by the TV shows if you are so inclined to buy them (some are just my best guesses though)! In some cases there were fan-made fonts such as “Some Time Later” for Spongebob or “Akbar” for The Simpsons that I included with the package.

Instead of dealing with extrafont yourself, I re-purposed the import_*() functions from the hrbrthemes package so you can import the included fonts very easily. Do note that you still might need to install the fonts directly on your computer from the .ttf files found in tvthemes/inst/fonts. When you’re done running the functions and installing the .ttf files on your computer, load the extrafont library and then run loadfonts(). If you’re having problems check out the documentation on extrafont’s Github repo or on CRAN.

The help files for each function tells you the recommended font names in case you forget!

import_simpsons()         ## "Akbar" font
import_theLastAirbender() ## "Slayer" font
import_rickAndMorty()     ## "Get Schwifty" font
import_roboto_condensed() ## "Roboto Condensed" Google Font import from hrbrthemes
import_titillium_web()    ## "Titillium Web" Google Font import from hrbrthemes
import_spongeBob()        ## "Some Time Later" font
import_cinzel()           ## "Cinzel" font to use with 'The Palettes of Ice & Fire'

## install.packages("extrafont")
library(extrafont)
loadfonts() ## You need to do this at the beginning of a session.

Colors

I gathered the colors/hex codes by looking at images online or re-watching some old episodes and then using various hex code websites and hex code extraction websites. Most of the color palettes were pretty straightforward as the characters or certain elements of the TV shows naturally provided some kind of differentiation by color. The colors in some of these palettes may still change from feedback and further experimentation.

You can check out all the colors for each palette by running scales::show_col(tvthemes:::name_of_palette). Some examples below:

scales::show_col(tvthemes:::rickAndMorty_palette)

scales::show_col(tvthemes:::lannister_palette)

scales::show_col(tvthemes:::simpsons_palette)

Example Plots

Brooklyn Nine-Nine

For the most part this theme and palette are unchanged from the original blog post. I just added a few more colors to both the normal palette (shown below) and the dark palette.

mpg %>% 
  ggplot(aes(displ)) +
  geom_histogram(aes(fill = class), 
                 col = "black", size = 0.1,
                 binwidth = 0.1) +
  scale_fill_brooklyn99() +
  labs(title = "Do you know what it means to 'clap back', Raymond?",
       subtitle = glue::glue("BE- {emo::ji('clap')} -CAUSE {emo::ji('clap')} I {emo::ji('clap')} DO {emo::ji('clap')} !"),
       caption = "Pizza bagels? Pizza rolls? Pizza poppers? Pizzaritos? Pizza pockets?") +
  theme_brooklyn99(title.font = "Titillium Web",
                   text.font = "Calibri Light",
                   subtitle.size = 14)

Spongebob Squarepants

I had a lot of fun with this one. The colors are taken from the characters and their clothes while I was able to find a fan-made font that looks similar to the one used in the transition slides of the show.

bobspog_plot <- mpg %>% 
  ggplot(aes(displ)) +
  geom_histogram(aes(fill = class), col = "black", size = 0.1) +
  scale_fill_spongeBob() +
  labs(title = "F is for Fire that burns down the whole town!",
       subtitle = "U is for Uranium... bombs! N is for No survivors when you're-",
       caption = "Plankton, those things aren't what fun is about!") +
  theme_spongeBob(title.font = "Some Time Later",
                  text.font = "Some Time Later",
                  title.size = 22,
                  subtitle.size = 16,
                  axis.title.size = 16,
                  axis.text.size = 14,
                  legend.title.size = 14)

bobspog_plot

In addition I was inspired by ggpomological::paint_pomological() and created a similar function that allows you to place your plot on top of a Spongebob-themed background.

paintBikiniBottom(plot = bobspog_plot,
                  background = "background") ## or "floral"

The Simpsons

Pretty simple one to do and I’m sure many of you have seen other Simpsons related color palettes around the internet. The blue background and yellow title font for the theme was inspired by Nathan Cunningham’s cool blog posts on The Simpsons like this one here.

data <- gapminder::gapminder %>% 
  filter(country %in% c("France", "Germany", "Ireland", "Italy", "Japan", "Norway", "Belarus")) %>% 
  mutate(year = as.Date(paste(year, "-01-01", sep = "", format='%Y-%b-%d')))

ggplot(data = data, aes(x = year, y = gdpPercap, fill = country)) +
  geom_area(alpha = 0.8) +
  scale_x_date(breaks = data$year, date_labels = "%Y") +
  scale_y_continuous(expand = c(0, 0), labels = scales::dollar) +
  scale_fill_simpsons() +
  labs(title = "The Simpsons",
       caption = glue::glue("
                      A 'Bake 'em Away, Toys!' Production"),
       x = "Wel-diddly-elcome neighborino!",
       y = "Price of Duff Beer") +
  theme_simpsons(title.font = "Akbar",
                 text.font = "Akbar",
                 axis.text.size = 8)

Rick and Morty

The colors from the show make for a pretty good discrete color palette while the font is a fan-made creation that tries to emulate co-creator Justin Roiland’s handwriting that was used on the show.

ggplot(diamonds, aes(price, fill = cut)) +
  geom_histogram(binwidth = 500) +
  scale_fill_rickAndMorty() +
  labs(title = "Dammit Morty, You Know Diamonds Aren't Forever Right?",
       subtitle = "They're blood diamonds, Morty **burp**",
       caption = "Wubbalubbadubdub!") +
  theme_rickAndMorty(title.font = "Get Schwifty",
                     text.font = "Get Schwifty",
                     title.size = 14)

Game of Thrones: Houses Stark, Lannister, Targaryen

As a fan of the books and medieval heraldry looking around for inspiration for the Great Houses of Westeros was quite fun. I hope to add others in the future (Martell, Arryn), even smaller houses if they have a great color scheme, like House Bolton, Dayne, Flint (Widow’s Watch), etc.

mpg %>% 
  ggplot(aes(displ)) +
  geom_histogram(aes(fill = class), col = "black", size = 0.1) +
  labs(title = "The winters are hard, but the Starks will endure.",
       subtitle = "We always have...",
       caption = "Winter Is Coming...") +
  scale_y_continuous(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  scale_fill_stark() +
  theme_minimal() +
  theme(text = element_text(family = "Cinzel", size = 14)) -> stark

colstully <- tully_pal()(5)

ggplot(diamonds, aes(price, fill = cut)) +
  geom_histogram(binwidth = 500) +
  scale_fill_manual(values = rev(colstully)) +
  #scale_fill_tully() +
  labs(title = "I've seen wet shits I like better than Walder Frey.",
       subtitle = "Pardon my lord, my lady. I need to find a tree to piss on.",
       caption = "- The Blackfish") +
  theme_minimal() +
  theme(text = element_text(family = "Cinzel", size = 10),
        title = element_text(family = "Cinzel", size = 14)) -> tully

ggplot(gapminder::gapminder, aes(x = log10(gdpPercap), y = lifeExp)) +
  geom_point(aes(color = continent)) + 
  scale_x_log10() +
  scale_color_targaryen() +
  labs(title = "I am the blood of the dragon. I must be strong.",
       subtitle = "I must have fire in my eyes when I face them, not tears.",
       caption = "- Fire & Blood.") +
  theme_minimal() +
  theme(text = element_text(family = "Cinzel", size = 10),
        title = element_text(family = "Cinzel", size = 14)) -> targaryen

## patchwork together:
stark + tully - targaryen + plot_layout(ncol = 1)

Game of Thrones: Houses Tyrell, Tully, Greyjoy

data <- gapminder::gapminder %>% 
  filter(country %in% c("France", "Germany", "Ireland", "Italy", "Japan", "Norway", "Belarus")) %>% 
  mutate(year = as.Date(paste(year, "-01-01", sep = "", format='%Y-%b-%d')))

ggplot(data = data, aes(x = year, y = gdpPercap, fill = country)) +
  geom_area(alpha = 0.8) +
  scale_x_date(breaks = data$year, date_labels = "%Y") +
  scale_y_continuous(expand = c(0, 0), labels = scales::dollar) +
  scale_fill_tyrell() +
  labs(title = "All men are fools, if truth be told, but",
       subtitle = "the ones in motley are more amusing than ones with crowns.",
       caption = "- The Queen of Thorns") +
  theme_minimal() +
  theme(text = element_text(family = "Cinzel", size = 10),
        plot.title = element_text(family = "Cinzel", size = 16),
        plot.subtitle = element_text(family = "Cinzel", size = 12)) -> tyrell

cols <- lannister_pal()(5)

ggplot(diamonds, aes(price, fill = cut)) +
  geom_histogram(binwidth = 500) +
  labs(title = "You are done with whores.",
       subtitle = "The next one I find in your bed, I'll hang.",
       caption = "Rains of Castamere") +
  scale_fill_manual(values = rev(cols)) +
  #scale_fill_lannister() +
  theme_minimal() +
  theme(text = element_text(family = "Cinzel", size = 14)) -> lannister

airquality %>% 
  mutate(Month = as.factor(Month)) %>% 
  ggplot(aes(x = Day, y = Temp, group = Month, color = Month)) +
  geom_line(size = 1.5) +
  scale_color_greyjoy() +
  labs(title = "I am the storm, my lord.",
       subtitle = "The first storm, and the last.",
       caption = "- Euron 'The Crow's Eye' Greyjoy") +
  theme_minimal() +
  theme(text = element_text(family = "Cinzel", size = 10),
        title = element_text(family = "Cinzel", size = 14)) -> greyjoy

## patchwork together:
tyrell + lannister - greyjoy + plot_layout(ncol = 1)

Avatar: The Last Airbender

With four very distinct nations (each based on a certain natural element) in the Avatar universe it was very easy to come up with color palettes.

mpg %>% 
  ggplot(aes(displ)) +
  geom_histogram(aes(fill = class), col = "black", size = 0.1) +
  scale_fill_fireNation() +
  labs(title = "Flameo, Hotman!",
       subtitle = "Fire. Wang Fire. This is my wife, Sapphire.",
       x = "Lion Vultures Owned",
       y = "Agni Kai Participation") +
  theme_theLastAirbender(title.font = "Slayer",
                         text.font = "Slayer") -> firenation

airquality %>% 
  mutate(Month = as.factor(Month)) %>% 
  ggplot(aes(x = Day, y = Temp, group = Month, color = Month)) +
  geom_line(size = 1.5) +
  scale_color_airNomads() +
  labs(title = "Let's head to the Eastern Air Temple!",
       subtitle = "Appa, Yip Yip!") +
  theme_theLastAirbender(title.font = "Slayer",
                         text.font = "Slayer",
                         title.size = 10) -> airnomads

ggplot(gapminder::gapminder, aes(x = log10(gdpPercap), y = lifeExp)) +
  geom_point(aes(color = continent)) + 
  scale_x_log10() +
  scale_color_waterTribe() +
  labs(title = "I am thinking maybe we could... do an activity together?",
       subtitle = "... Do an activity?",
       x = "GDP per Otter-Penguins",
       y = "Life Expectancy of Arctic Camels") +
  theme_theLastAirbender(title.font = "Slayer",
                         text.font = "Slayer",
                         title.size = 8,
                         subtitle.size = 8) -> watertribe

mpg %>% 
  ggplot(aes(displ)) +
  geom_histogram(aes(fill = class), col = "black", size = 0.1) +
  scale_fill_earthKingdom() +
  labs(title = "There is no war in Ba Sing Se",
       subtitle = "(Welcome to Lake Laogai)") +
  theme_theLastAirbender(title.font = "Slayer",
                         text.font = "Slayer",
                         title.size = 14) -> earthkingdom

## plot together:
plot_grid(firenation, airnomads, watertribe, earthkingdom, ncol = 2)

Parks and Recreation

This is probably a more literal interpretation of “Parks and Recreation” rather than any of the colors in the palette/theme representing the characters. This one was the one I took the most liberties with and just tried to use colors that felt very outdoorsy and “Parks”-like. The font used with this theme, “Titillium Web” is similar to the typeface used in the logo of “Parks and Rec”, “Champion HTF-Heavyweight”.

airquality %>% 
  mutate(Month = as.factor(Month)) %>% 
  ggplot(aes(x = Day, y = Temp, group = Month, color = Month)) +
  geom_point(size = 2.5) +
  labs(title = "Calzones are pointless.", subtitle = "They're just pizza that's harder to eat!",
       caption = "No one likes them. Good day, sir.") + 
  scale_color_parksAndRec() + 
  theme_minimal() +
  theme_parksAndRec(text.font = "Titillium Web",
                    title.font = "Titillium Web Black",
                    legend.font = "Titillium Web") -> parksandrec

mpg %>% 
  ggplot(aes(displ)) +
  geom_histogram(aes(fill = class), col = "black", size = 0.1) +
  labs(title = "Parks & Recreation",
       subtitle = "Gotta Spend Money To Make Money!",
       caption = "And I spent... all of my money!") +
  scale_fill_parksAndRec() + 
  theme_minimal() +
  theme_parksAndRec_light(title.font = "Titillium Web Black",
                    text.font = "Titillium Web") -> parksandreclight

## plot together:
plot_grid(parksandrec, parksandreclight, ncol = 2)

Lessons Learned & Future Steps

Although this package is mainly just for fun, I still learned quite a lot from the experience. I was able to get more practice with usethis and devtools for package development. Instead of manually creating package files, be it the DESCRIPTION, R scripts, license, etc. the usethis::use_*() set of functions creates them for you automatically along with the correct directories and when relevant adds them to .gitignore or makes a note of any changes in other relevant files. Below is an example of a really nifty usethis function that creates a unit test to run a spell check on all your documentation!

A lot of the best packages I’ve seen have always had the cool and informative badges at the top of the README. The “Lifecycle” badges described in detail here are pretty straightforward, it gives the viewer an idea of what stage of development the R package is in. The other badges I have on tvthemes are the ones for the license (GPL v3) and for codecov. I just used usethis::use_badge() but there are alternative ways to produce the badges such as the badger package by Guangchuang Yu.

On the topic of testing, I create a lot of unit tests for the R packages that I help maintain at the NGO I work for but until tvthemes I had never created tests for ggplot2 themes and palette objects before. After looking around at other theme/palette packages such as vapoRwave, hrbrthemes, and ggpomological I was able to get a sense for what to test for such as:

test_that("theme_brooklyn99 works", {
  thm <- theme_brooklyn99()
  expect_s3_class(thm, "theme")
  expect_equal(thm$text$family, "")
})

test_that("brooklyn99_pal raises warning with number greater than colors available, x > 10", {
  expect_warning(brooklyn99_pal()(11))
})

## EDIT (May 18): expanded tests for themes, check for all defaults
test_that("theme_brooklyn99 works", {
  thm <- theme_brooklyn99()
  expect_s3_class(thm, "theme")
  ## font
  expect_equal(thm$text$family, "")
  expect_null(thm$plot.title$family)
  expect_null(thm$legend.title$family)
  expect_null(thm$legend.text$family)
  ## size
  expect_equal(thm$text$size, 14)
  expect_equal(thm$plot.title$size, 18)
  expect_equal(thm$plot.subtitle$size, 12)
  expect_equal(thm$axis.text$size, 12)
  expect_equal(thm$axis.title$size, 14)
  expect_equal(thm$legend.text$size, 9)
  expect_equal(thm$legend.title$size, 10)
  ## color
  expect_equal(thm$text$colour, "#F9FEFF")
  expect_equal(thm$plot.title$colour, "#F9FEFF")
  expect_equal(thm$plot.subtitle$colour, "#F9FEFF")
  expect_equal(thm$axis.text$colour, "#F9FEFF")
  expect_equal(thm$axis.title$colour, "#F9FEFF")
  expect_equal(thm$legend.text$colour, "#F9FEFF")
  expect_equal(thm$legend.title$colour, "#F9FEFF")
  expect_equal(thm$legend.position, "bottom")
})

In addition, I used codecov to see the exact percentage of my code that my tests covers.

Although all of the actual theme or palette functions are “covered” I could still add more depth to them. Currently I only have a test that checks for one component of the theme, the font family, but I should expand this to include other key components of the particular theme I’m testing for (EDIT: see above code chunk for the expanded tests, managed to fix some bugs in a few themes that I missed, hooray for testthat!). For example, the exact color of the panel.background for theme_spongBob() or the correct default size of the title. I also need to figure out how to write tests for the import_*() functions and the add-background-to-plot paintBikiniBottom() function!

The next thing I want to try out is to use the vdiffr package to test the outputted plots themselves. From the README the steps are as follows:

  1. Add expectations to by including expect_doppelganger() in your test files.

  2. Run manage_cases() to generate the plots which vdiffr will test against in the future. This will launch a shiny gadget which will ask you to confirm that each plot is correct.

  3. Run devtools::test() to execute the tests as normal.

This can be extremely useful for tvthemes to check whether the individual themes and palettes are being applied properly whenever I make some changes in the code!

Last, but certainly not least, was the hex sticker for my package. Originally I wanted to use a character from one of the TV shows in the logo but in the end I went with something that wasn’t copyrighted. Also, I didn’t really have the photoshop skills to make it work! What I ended up choosing was using the popular Japanese site, irasutoya, which provides thousands of drawings for free use. It’s a great website that provides drawings for any object, person, and situation! After finding the best image (a child watching cartoons and sitting too close to the TV), I used the hexSticker package by Guangchuang Yu to create it in R:

## Download image:
download.file("https://3.bp.blogspot.com/-kHda-2huAF8/WUdZKshMQkI/AAAAAAABFDM/sXt-PT9dKtYp2Y0Y_OV64TJzs7PvOLZmgCLcBGAs/s800/tv_boy_chikaku.png",
              destfile = paste0(tempdir() , ".png"), mode="wb")
## Create sticker:
hexSticker::sticker(subplot = paste0(tempdir() , ".png"), 
                    package = "tvthemes",
                    p_size = 14, p_color = "#8B4411", 
                    p_x = 1, p_y = 1.65, 
                    s_x = 1.0, s_y = 0.95,
                    s_width = 0.6, s_height = 0.6, 
                    h_fill = "#f5f5dc", h_color = "black",
                    filename = "tvthemes_hexsticker.png")

Besides adding more themes and palettes the next big step would be releasing tvthemes to CRAN. I was rather confused by the process at first but after watching Jim Hester’s video of prepping vroom for a CRAN release, a lot of my hangups were cleared up for me. I definitely recommend it as he carefully guides you through the process!

I had a LOT of fun creating this package and I hope to continue with it in my free time!

Suggestions and Pull Requests for palettes/themes are most welcome, you can find the Github repo for the package HERE!