Three Ways to Include Images in Your ggplots

[This article was first published on Albert Rapp, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In today’s blog post, I want to show you how you can use images in your ggplots. I will show you three ways for three different use cases. And as always, you can find the video version of this blog post on Youtube:

Using images inside the main panel with geom_image()

The first way to add images is the easiest. You can use any image inside your plot’s main panel using the geom_image() function from the ggimage package. So let’s say you have the following scatter plot:

library(tidyverse)
palmerpenguins::penguins |> 
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  geom_point(size = 4, color = 'dodgerblue4', alpha = 0.9) +
  theme_minimal(
    base_size = 12,
    base_family = 'Source Sans Pro'
  )

And now you want to add the following delightful image by Allison Horst to your plot:

You can do this by loading the ggimage package and adding a geom_image() layer to it. There, you will have to map the image aesthetic to that image file (that you should have downloaded at this point). Make sure that the data argument is a single-row data set, otherwise it will add the image multiple times.

library(ggimage)
palmerpenguins::penguins |> 
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  geom_point(size = 4, color = 'dodgerblue4', alpha = 0.9)+
  theme_minimal(
    base_size = 12,
    base_family = 'Source Sans Pro'
  ) +
  geom_image(
    data = tibble(bill_length_mm = 50, bill_depth_mm = 20),
    aes(image = "penguins.png")
  )

With the size argument, you can then adjust the size of the image.

palmerpenguins::penguins |> 
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm)) +
  geom_point(size = 4, color = 'dodgerblue4', alpha = 0.9)+
  theme_minimal(
    base_size = 12,
    base_family = 'Source Sans Pro'
  ) +
  geom_image(
    data = tibble(bill_length_mm = 50, bill_depth_mm = 20),
    aes(image = "penguins.png"),
    size = 0.5
  )

Using images inside geoms

The second strategy is similar, but it uses images inside of geoms that you are already using. For example, imagine that you have a waffle chart like this:

tibble(id = 0:22, row = id %% 5, col = id %/% 5) |> 
  ggplot(aes(x = col, y = row)) +
  geom_tile(fill = 'dodgerblue4', col = 'white', linewidth = 1) +
  coord_equal() +
  labs(title = 'Random waffle') +
  theme_void(base_size = 18, base_family = 'Source Sans Pro')

And now imagine that instead of the squares, we want to use an icon. This is where we’ll

  • use ggpattern,
  • replace geom_rect() with geom_rect_pattern(),
  • set the pattern to image, and
  • set the pattern_filename to the icon image you want to use.

This of course requires us to have the icon image downloaded and saved on our computer. For example, one of the icons from Flaticon will do. Assuming that we have downloaded and saved the image as sneaker.png, we can now use it in our waffle chart:

library(ggpattern)
tibble(id = 0:22, row = id %% 5, col = id %/% 5) |> 
  ggplot(aes(x = col, y = row)) +
  geom_tile_pattern(
    fill = 'dodgerblue4', 
    col = 'white', 
    linewidth = 1,
    pattern = 'image',
    pattern_filename = 'sneaker.png'
  ) +
  coord_equal() +
  labs(
    title = 'Random waffle',
    caption = 'Flaticon Icon by Us and Up'
  ) +
  theme_void(base_size = 18, base_family = 'Source Sans Pro')

Notice how the sneakers are now inserted into the tiles. So instead of filling the squares blue we can make the background white.

tibble(id = 0:22, row = id %% 5, col = id %/% 5) |> 
  ggplot(aes(x = col, y = row)) +
  geom_tile_pattern(
    fill = 'white',  # Change here and remove col and linewidth
    pattern = 'image',
    pattern_filename = 'sneaker.png'
  ) +
  coord_equal() +
  labs(
    title = 'Random waffle',
    caption = 'Flaticon Icon by Us and Up'
  ) +
  theme_void(base_size = 18, base_family = 'Source Sans Pro')

And you can specify a width and height to change the size of the icon.

tibble(id = 0:22, row = id %% 5, col = id %/% 5) |> 
  ggplot(aes(x = col, y = row)) +
  geom_tile_pattern(
    fill = 'white',  # Change here and remove col and linewidth
    pattern = 'image',
    pattern_filename = 'sneaker.png',
    width = 1.1,
    height = 1.1
  ) +
  coord_equal() +
  labs(
    title = 'Random waffle',
    caption = 'Flaticon Icon by Us and Up'
  ) +
  theme_void(base_size = 18, base_family = 'Source Sans Pro')

If you want to use differently colored versions of the icon, you can use the magick package to replace everything but the transparent bits with another color. Here’s how that works

library(magick)
raster <- image_read('sneaker.png') |> image_raster(tidy = FALSE)
raster[raster != "transparent"] <- 'dodgerblue4'
ragg::agg_png(
  'sneaker_blue.png', 512, 512, background = 'transparent'
)
plot(raster)  
dev.off()
## png 
##   2

That way, you have a new image file and can use it as part of the geom_rect_pattern() layer.

tibble(id = 0:22, row = id %% 5, col = id %/% 5) |> 
  ggplot(aes(x = col, y = row)) +
  geom_tile_pattern(
    fill = 'white',  # Change here and remove col and linewidth
    pattern = 'image',
    pattern_filename = 'sneaker_blue.png',
    width = 1.1,
    height = 1.1
  ) +
  coord_equal() +
  labs(
    title = 'Random waffle',
    caption = 'Flaticon Icon by Us and Up'
  ) +
  theme_void(base_size = 18, base_family = 'Source Sans Pro')

Using images as axis labels

The third trick requires a little bit of HTML and CSS notation. But it’s a good one. It allows you to use images as axis labels. For example, I’ve recently used this in the 30 Day Chart Challenge:

And to do that, you can use the ggtext package. Instead of regular labels, you can use <img> tags from HTML and CSS. All you have to do is to

  • replace the labels with something like <img src="file_for_label_replacement.png"> and
  • use this as labels for your axes.

But this requires you to have the png files on your computer or can find a url to the png file online. And for my recent contribution to the 30 Day Chart Challenge, I could only find svg images. Hence, I downloaded the svgs with magick and converted it to png. Here’s the code for that:

# market cat data from https://www.emarketer.com/chart/257463/top-10-neobanks-worldwide-by-market-capitalization-2022-billions
dat <- tribble(
  ~neo_bank, ~market_cap_bn, ~logo_url,
  "Nubank", 45, "https://upload.wikimedia.org/wikipedia/commons/f/f7/Nubank_logo_2021.svg",
  "Revolut", 33, "https://upload.wikimedia.org/wikipedia/commons/d/d6/Revolut.svg",
  "Chime", 25, "https://upload.wikimedia.org/wikipedia/commons/f/f6/Chime_company_logo.svg",
  "WeBank", 21, "https://upload.wikimedia.org/wikipedia/en/e/eb/WeBank_Logo.svg",
  "Kakaobank", 19, "https://upload.wikimedia.org/wikipedia/commons/4/48/KakaoBank_logo.svg",
  "Robinhood", 10, "https://upload.wikimedia.org/wikipedia/commons/d/da/Robinhood_%28company%29_logo.svg",
  "SoFi", 10, "https://upload.wikimedia.org/wikipedia/commons/1/16/SoFi_logo.svg",
  "Tinkoff", 9, "https://upload.wikimedia.org/wikipedia/commons/1/19/Tinkoff_logo_2024.svg",
  "N26", 9, "https://upload.wikimedia.org/wikipedia/commons/b/bf/N26_logo_2019.svg",
  "K Bank", 7, "https://upload.wikimedia.org/wikipedia/commons/2/26/Kbank_logo.svg"
) 
map(dat$logo_url, magick::image_read_svg) |>
  walk2(
    dat$neo_bank,
    \(x, y) magick::image_write(x, path = paste0("logos/", y, ".png"))
  )

This will save the images to a logos/ directory. And once you have those, you can use a combination of glue() and scale_y_discrete() to wrap the bank names into the <img> tags.

dat |> 
  mutate(neo_bank = fct_reorder(neo_bank, market_cap_bn)) |>
  ggplot(aes(x = market_cap_bn, y = neo_bank)) +
  geom_col() +
  scale_y_discrete(
    labels = \(x) glue::glue("<img src='logos/{x}.png' height=10 />")
  ) +
  theme_minimal(
    base_size = 12,
    base_family = 'Source Sans Pro'
  ) +
  labs(
    title = 'Top 10 neobanks worldwide by market capitalization 2022',
    y = element_blank()
  )

However, this will give you too much text on the axis rather than rendering this as image. The trick is to go into theme() and set the labels to element_markdown() from the ggtext package. This enables Markdown rendering and allows for HTML and CSS notation. This way, the <image> tag is turned into an actual image, and you have your image right where you want it to be.

dat |> 
  mutate(neo_bank = fct_reorder(neo_bank, market_cap_bn)) |>
  ggplot(aes(x = market_cap_bn, y = neo_bank))  +
  geom_col() +
  scale_y_discrete(
    labels = \(x) glue::glue("<img src='logos/{x}.png' height=20 />")
  ) +
  theme_minimal(
    base_size = 12,
    base_family = 'Source Sans Pro'
  ) +
  labs(
    title = 'Top 10 neobanks worldwide by market capitalization 2022',
    y = element_blank()
  ) +
  theme(
    axis.text.y = ggtext::element_markdown()
  )

Conclusion

And there you have it! Three ways to use images inside your ggplots. Hope you enjoyed this little tutorial. Have a great day and see you next time. And if you found this helpful, here are some other ways I can help you:

To leave a comment for the author, please follow the link and comment on their blog: Albert Rapp.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)