Ggplot2: Fonts and cross-platform reproducibility

ggplot2
fonts

#1

Consider this neat little plot a friend of mine made for teaching purposes. It uses dice rolls to visualize how the distribution converges normally.

My attempt to make his code reproducible by being explicit about unicode characters resulted in the following code:

library(ggplot2)

w6_2 <- sample(c(1:6), 2000, replace = T) + sample(c(1:6), 2000, replace = T)

# see stringi::stri_escape_unicode
d1 <- "\u2680"
d2 <- "\u2681"
d3 <- "\u2682"
d4 <- "\u2683"
d5 <- "\u2684"
d6 <- "\u2685"

dice <- c(
  "2"  = paste0(d1, d1),
  "3"  = paste0(d1, d2, "\n", d2, d1),
  "4"  = paste0(d2, d2, "\n", d1, d3, "\n", d3, d1),
  "5"  = paste0(d1, d4, "\n", d2, d3, "\n", d3, d2, "\n", d4, d1),
  "6"  = paste0(d1, d5, "\n", d2, d4, "\n", d3, d3, "\n", d4, d2, "\n", d5, d1),
  "7"  = paste0(d1, d6, "\n", d2, d5, "\n", d3, d4, "\n", d4, d3, "\n", d5, d2, "\n", d6, d1),
  "8"  = paste0(d2, d6, "\n", d3, d5, "\n", d4, d4, "\n", d5, d3, "\n", d6, d2),
  "9"  = paste0(d3, d6, "\n", d4, d5, "\n", d5, d4, "\n", d6, d3),
  "10" = paste0(d4, d6, "\n", d5, d5, "\n", d6, d4),
  "11" = paste0(d5, d6, "\n", d6, d5),
  "12" = paste0(d6, d6)
)

ggplot(NULL, aes(x = w6_2)) +
  geom_histogram(bins = 11, color = "white", fill = "#1aadff", alpha = .5) +
  scale_x_continuous(breaks = 2:12, labels = dice) +
  labs(title = "Distribution of Dice Rolls",
       subtitle = "After 2000 Rolls with 2 Dice",
       x = "Combinations (row-wise)", y = "Count") +
  theme_minimal()

…Which works fine on his Ubuntu 17.10 machine, yet not on my macOS Sierra machine. On my machine, it looks like this, with the tell-tale boxes indicating missing glyphs. After much research I ended up being able to reproduce the correct plot by manually specifying this free font by adding the following line:

ggplot(NULL, aes(x = w6_2)) +
  geom_histogram(bins = 11, color = "white", fill = "#1aadff", alpha = .5) +
  scale_x_continuous(breaks = 2:12, labels = dice) +
  labs(title = "Distribution of Dice Rolls",
       subtitle = "After 2000 Rolls with 2 Dice",
       x = "Combinations (row-wise)", y = "Count") +
  theme_minimal() +
  theme(axis.text.x = element_text(size = rel(2), family = "FreeSerif", lineheight = .5))

Which now works on my macOS machine and on his Ubuntu machine, and possibly requires you to install/load the font via extrafont beforehand.

So, while I’m happy to have a reproducible plot now, my question is this: Why does it work on Linux out of the box, even with various different fonts? According to my research, the fonts that work on Linux with this plot don’t actually have glyphs for these dice symbols. I also tried the code on a fresh <rstudio.cloud> session without any extrafont additions and the code above just works fine without specifying the custom font.
Apparently there’s a lot of differences concerning fonts across different platforms, but I’m not familiar enough with the R/ggplot2 internals to wrap my head around this minor inconsistency here.

Any insights would be appreciated, as I’m writing code for teaching purposes and platform inconsistencies are good to be aware of.


#2

I’m not sure how to fix it, but I also tried both versions of code on Windows 10 and they both gave me the missing glyph boxes. I got the following error:

Warning messages:
1: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y,  :
  font family not found in Windows font database
2: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y,  :
  font family not found in Windows font database
3: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y,  :
  font family not found in Windows font database
4: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y,  :
  font family not found in Windows font database
5: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y,  :
  font family not found in Windows font database
6: In grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y,  :
  font family not found in Windows font database

#3

For windows I use the extrafont package to load all of the specific fonts for windows then call in the specific font I want to use then set the theme. I don’t have access to a mac or linux system - so no help there.

library(extrafont)
#font_import() only do this one time - it takes a while
loadfonts(device = "win")
windowsFonts(Times=windowsFont("TT Times New Roman"))
library(tidyverse)
theme_set(theme_bw(base_size=12, base_family = 'Times New Roman')+ 
  theme(panel.grid.major = element_blank(),
  panel.grid.minor = element_blank()))

#5

@dlsweet I haven’t tested this on windows myself yet, but presumably you need to install the free font I linked in my post to at least be able to run the second code example.

@wendigo As I stated, I’m aware of extrafont and how to switch fonts if needed. That’s not, however, what this post is about.


#6

The fact that it works cross-platform when you specify a common font suggests to me that this is an issue with the Linux distro defaulting to a font that doesn’t have the glyphs, rather than a problem with the graphics driver on Linux being unable to render the glyphs (which can be a nightmare with emoji packages). I think we’d need to identify that default Linux font in order to say that more conclusively, though.