Skip to contents

Setup

Advantages of ob_label over ob_latex

The ob_label function uses ggtext::geom_richtext to create labels. It’s primary advantage is that it is simple and renders quickly. Wherever possible, it is the recommended way to create labels. It understands some markdown formatting (e.g., italics and bolding) and some HTML tags (e.g., span, sub, sup, img, and style).

Advantages of ob_latex over ob_label

If something more elaborate is needed than italics, bolding, subscripts, and superscripts, we can use LaTeX instead. The ob_latex function can place an image of a LaTeX equation in a ggplot diagram.

For example, suppose I want to label a latent variable’s variance with the symbol σe2\sigma_e^2. This symbol would be difficult to render in HTML, so we can render it in LaTeX instead.

ggdiagram(font_family = "Roboto Condensed") +
  {l <- ob_circle(label = ob_label("*e*", size = 48))} +
  {lv <- ob_variance(l)} +
  ob_latex(tex = "\\sigma_e^2",
           center = lv@midpoint(), 
           width = .4) 
Figure 1: Latent variable with variance

If we want the symbol to be in the same font as the rest of the figure, we can trick LaTeX into giving us any font we have installed on our system. I often use Roboto Condensed:

ggdiagram(font_family = "Roboto Condensed") +
  {l <- ob_circle(label = ob_label("*e*", size = 48))} +
  {lv <- ob_variance(l)} +
  ob_latex(tex = r"(\text{\emph{σ}}_{\text{\emph{e}}}^{\text{2}})",
           center = lv@midpoint(), 
           width = .4, 
           family = "Roboto Condensed") 
Figure 2: Latent variable with variance rendered in Roboto Condensed

If you need an equation in a plot that requires something other than a 1:1 aspect ratio, you can set the aspect ratio of the equation to be the same as the aspect ratio as the plot.

ggplot() +
  coord_fixed(ratio = 1000) +
  theme_classic() +
  stat_function(
    fun = \(x) dnorm(x, mean = 50, sd = 10),
    geom = "area",
    n = 1000,
    fill = "dodgerblue",
    alpha = .5
  ) +
  scale_x_continuous(breaks = seq(10,90,10), limits = c(10, 90)) +
  scale_y_continuous(NULL, breaks = NULL, limits = c(0, dnorm(50,50,10))) +
  ob_latex(
    r"(f(x) = 
    \frac{1}{\sigma\sqrt{2\pi}}
    e^{-\frac{1}{2}
    \left(\frac{x-\mu}{\sigma}\right)^2})", 
           width = 30,
           aspect_ratio = 1000,
           border = 1,
           filename = "zscore", 
           density = 600) %>% 
  place(ob_point(57, dnorm(57, 50, 10)), 
        where = "right", 
        sep = 3)
Figure 3: Normal distribution’s probability density function

Image quality

The default density for ob_latex images is 300 dots per inch. If a small expression is displayed as a large image, it will appear pixelated.

ggdiagram() +
  ob_circle(radius = 1) +
  ob_latex("X_i^2", 
           width = 1.25)
Figure 4: A latex expression with poor image quality

Setting the density to a higher value will usually create a better image.

ggdiagram() +
  ob_circle(radius = 1) +
  ob_latex("X_i^2", 
           width = 1.25,
           density = 900)
Figure 5: A latex expression with better image quality

Higher densities are not always better. In addition to using more memory and rendering more slowly, images with very high densities will sometimes appear blurry or pixelated.

How does ob_latex work?

The ob_latex function works through these steps:

  1. Create a .tex file with content based on the LaTeX standalone package.
  2. Create a .pdf file via the tinytex::xelatex function, if tinytex is available. Otherwise, use xelatex via a shell command.
  3. Import the .pdf file as raster bitmap via the magick::image_read_pdf function.
  4. Store raster bitmap in the ob_latex@image slot.

When rendered in ggplot2, the bitmap is displayed via ggplot2::annotation_raster.