rggplot2justifyggtext

Justify multiline text in ggplot2 - title, subtitle, caption, geom_text() etc


I would like to justify (not just align!) multiline text in my ggplots, i.e. mostly title, subtitle, caption and annotations via geom_text() etc.

>> For a solution see the update at the bottom of this post <<

Justified Text

To be clear, by "justified text" I mean adding space between words so that both edges of each line are aligned with both margins. The last line in the paragraph is aligned left. In the following image it would be the format on the right, highlighted in yellow:

enter image description here

reprex

Below is a reproducible example trying it with both {ggplot2} and {ggtext}. As you can see, I can only think of using hjust= (and halign= with {ggtext}) but they only align and do not justify multiline text.

library(ggtext)
library(tidyverse)

my_text <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
my_subtitle <- paste(rep(my_text, 5), collapse = " ")

df <- tibble(
  my_text = rep(my_text, 3),
  x = c(1, 2, 3),
  y = c(2, 1, 0),
  hjust = c(0, 0.5, 1),
  vjust = c(0, 0.5, 1)
)

ggplot() +
  ggtitle("ggplot2::geom_text() with line breaks inserted via str_wrap()") +
  ggplot2::geom_text(data = df,
                     aes(
                       label = str_wrap(my_text, 45),
                       x = vjust,
                       y = hjust,
                       hjust = hjust,
                       vjust = vjust
                     )) +
  labs(subtitle = str_wrap(my_subtitle, 100)) +
  theme(plot.subtitle = element_text(hjust = 0))

ggplot() +
  ggtitle("ggtext::geom_richtext() with automatic word-wrapping") +
  ggtext::geom_textbox(
    data = df,
    width = unit(2, "inch"),
    box.color = NA,
    fill = NA,
    aes(
      label = my_text,
      x = vjust,
      y = hjust,
      hjust = hjust,
      vjust = vjust,
      halign = hjust,
      valign = vjust
    )
  ) +
  labs(subtitle = my_subtitle) +
  theme(plot.subtitle = ggtext::element_textbox_simple(hjust = 0, halign = 0))

Created on 2022-08-23 with reprex v2.0.2

Edit:

You can +1 the feature request issue on GitHub here.

Update: Works via {marquee}

Thanks to Michiel Duvekot's answer I made it work via the code below. It is not perfect because it needs a manually set width, but for the first time, I now have justified text as a geom and element in my ggplots.

library(marquee)
library(tidyverse)

my_text <-
  "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
my_subtitle <- paste(rep(my_text, 5), collapse = " ")

df <- tibble(
  my_text = rep(my_text, 3),
  x = c(1, 2, 3),
  y = c(2, 1, 0),
  hjust = c(0, 0.5, 1),
  vjust = c(0, 0.5, 1)
)

ggplot() +
  ggtitle("marquee::geom_marquee() and marquee::element_marquee()") +
  marquee::geom_marquee(
    data = df,
    aes(
      x = vjust,
      y = hjust,
      hjust = hjust,
      vjust = vjust,
      label = my_text
    ),
    width = unit(6, "cm"), # needs manually fixed width
    style = marquee::classic_style(align = "justified-left")
  ) +
  labs(subtitle = my_subtitle) +
  theme(plot.subtitle = marquee::element_marquee(
    hjust = 0,
    width = unit(20, "cm"), # needs manually fixed width
    style = marquee::classic_style(align = "justified-left")
  ))

enter image description here


Solution

  • the {marquee} package (https://github.com/r-lib/marquee/) might do what you want. It seems to require the very latest version of things, but this worked for me:

    library(ggplot2)
    library(tidyverse)
    library(marquee)
    
    my_text <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    my_subtitle <- paste(rep(my_text, 5), collapse = " ")
    
    df <- tibble(
      my_text = rep(my_text, 3),
      x = c(1, 2, 3),
      y = c(2, 1, 0),
      hjust = c(0, 0.5, 1),
      vjust = c(0, 0.5, 1)
    )
    
    ggplot() +
      ggtitle("ggplot2::geom_text() with line breaks inserted via str_wrap()") +
      marquee::geom_marquee(
        data = df,
        aes(x = x, y = y, label = my_text), 
        width = unit(6, "cm"),
        style = classic_style(
          align = "justified-left")
      ) +
      labs(subtitle = str_wrap(my_subtitle, 100)) +
      scale_x_continuous(limits = c(0,4)) +
      scale_y_continuous(limits = c(-1,3)) +
      theme(plot.subtitle = element_text(hjust = 0))