Add a logo to your plot

Put a bird on it - Portlandia.

Thomas Mock https://example.com/norajones
01-09-2019

via GIPHY

If you missed out on Portlandia, you should take some time to watch this clip of the “Put a bird on it” episode.

Just like Bryce and Lisa - we can put birds on anything with the magick package from ROpenSci!

So let’s get started putting birds on things!

Magic with magick!

The magick package is an R interface to the ImageMagick STL, one of the most comprehensive open source image processing libraries. It gives a lot of power to R users, and we’ll briefly cover the workflow before getting into our real example.

If you want a deeper dive on all the features of magick, check out their awesome vignette!


# Load the library
library(magick)

# "read" in an image
dog <- image_read("https://images.pexels.com/photos/1564506/pexels-photo-1564506.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940")

image_info(dog)

  format width height colorspace matte filesize density
1   JPEG  1880   1208       sRGB FALSE    85910   72x72

We can see a few things about the r_logo object, it is a .png, it’s height and width, filesize, and DPI/density.

We can also view the image in line.


print(dog)

  format width height colorspace matte filesize density
1   JPEG  1880   1208       sRGB FALSE    85910   72x72

We also have image manipulation options like image_rotate, image_resize, all of which are pipeable with the %>%.


dog %>% 
  image_rotate(30) %>% # rotate 30 degrees
  image_resize("400x400") %>%  # change size to 400 x 400 pixels
  image_flop() # flip the image horizontally

Most importantly we can put a bird on it!


bird <- image_read("https://user-images.githubusercontent.com/29187501/38769895-edae85d0-3fcf-11e8-95f9-bbd530b32771.png") %>% 
  image_resize("150x150")

image_composite(dog, bird)

Now please note where the bird showed up, in the top left corner. We can control the placement of composite images by specifying the offset.


dog %>% 
  image_composite(bird, offset = "+500+1000")

Notice that offset reads as x-axis pixels, y-axis pixels, so "+500+1000 indicates from the top left corner place the image 500 pixels to the right and 1000 pixels down.

Now that we’ve been oriented to how the magick package works, and had some fun by putting a bird on our lovely Boston Terrier model, let’s dive into the real purpose of this post - adding a logo to your ggplot!

Building a ggplot with a logo

In the exampe I will show below you will have already saved the plot as an image, so we can skip with the normal ggplot2 code. However, please note that if you do want to build in some extra whitespace to your plot to save “space” for your logo you can do so by changing the margins of your overall plot. You can also add a figure caption with labs(caption = "text to change here"), which inherently adds some whitespace to incorporate the caption.

Margins can take multiple units arguments including: “cm”, “in”, “lines”, “pt”, for more info check out the ggplot2 docs.

Notice: The margins read as t, r, b, l or c(top, right, bottom, left). I typically see logos on the bottom left or top right so you can:

Notice the below plot has extra space on the bottom, and I have added a grey background purely to highlight this extra space.


library(tidyverse)

mtcars %>% 
  ggplot(aes(x = cyl, y = mpg)) +
  geom_point() +
  theme(plot.margin = unit(c(0.5, 0.5, 2.5, 0.5), "lines"),
        plot.background = element_rect("lightgrey"))


# plot background = lightgrey for highlight, not necessary for sizing

Read in a plot

So let’s read in our publication plot to work with! Please notice that this can be a local file with traditional path structure or a hosted image as a url. I’m using URLs so you can try these out yourself!


pub_plot <- image_read("https://raw.githubusercontent.com/jthomasmock/tomtom/master/vignettes/basic_plot.png")

logo <- image_read("https://www.rstudio.com/wp-content/uploads/2018/10/RStudio-Logo-Flat.png") %>% 
  image_resize(300)

print(pub_plot)

# A tibble: 1 x 7
  format width height colorspace matte filesize density
  <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
1 PNG     2222   1951 sRGB       TRUE    168897 72x72  


print(logo)

# A tibble: 1 x 7
  format width height colorspace matte filesize density
  <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
1 PNG      300    105 sRGB       TRUE         0 57x57  

We can then overlay the logo over the plot, let’s try bottom left, and we want ~ 1% padding for aesthetics - so we can use the following code to get dimensions and pixel numbers.


# get dims of the plot
plot_height <- magick::image_info(pub_plot)$height
plot_width <- magick::image_info(pub_plot)$width

# get dims of the logo
logo_width <- magick::image_info(logo)$width
logo_height <- magick::image_info(logo)$height

# get number of pixels to be 1% from the bottom of the plot
# while accounting for the logo height
plot_height - logo_height - plot_height * 0.01

[1] 1826.49

# get number of pixels to be 1% from the left of the plot
plot_width * 0.01

[1] 22.22

By using offset = "+22+1826" we indicate that we are placing the logo 22 pixels to the right and 1826 pixels down.


pub_plot %>% 
  image_composite(logo, offset = "+22+1826")

Boom! We have our logo overlay on the plot in the right location! But we had to manually figure out and set the logo position, which is less than ideal for programmatic use down the road. I also manually resized the logo to 300 pixels in an earlier step - we should make that automagick as well!

Put it in a function! (Bird not included)

To make the logo add process more reproducible, we can build a function to take the various heights and widths of the logo/plot and automatically resize the logo to match as well as calculating the number of pixels to put it in each respective corner. The logo_scale argument defaults to 10 - so the logo will be 1/10th the width of the plot.


plot_with_logo <- add_logo(
  plot_path = "", # url or local file for the plot
  logo_path = "", # url or local file for the logo
  logo_position = "" # choose a corner
  # 'top left', 'top right', 'bottom left' or 'bottom right'
  #logo_scale = 10 as default, but can change to manually make logo bigger
)

# save the image and write to working directory
magick::image_write(plot_with_logo, "plot_with_logo.png")

The function details

This function takes in two images, gets the dimension information, then pastes the logo at 1/10th scale in the specified corner with 1% padding. The function also has a warning if you input a logo_position that is not included.


add_logo <- function(plot_path, logo_path, logo_position, logo_scale = 10){

    # Requires magick R Package https://github.com/ropensci/magick

    # Useful error message for logo position
    if (!logo_position %in% c("top right", "top left", "bottom right", "bottom left")) {
        stop("Error Message: Uh oh! Logo Position not recognized\n  Try: logo_positon = 'top left', 'top right', 'bottom left', or 'bottom right'")
    }

    # read in raw images
    plot <- magick::image_read(plot_path)
    logo_raw <- magick::image_read(logo_path)

    # get dimensions of plot for scaling
    plot_height <- magick::image_info(plot)$height
    plot_width <- magick::image_info(plot)$width

    # default scale to 1/10th width of plot
    # Can change with logo_scale
    logo <- magick::image_scale(logo_raw, as.character(plot_width/logo_scale))

    # Get width of logo
    logo_width <- magick::image_info(logo)$width
    logo_height <- magick::image_info(logo)$height

    # Set position of logo
    # Position starts at 0,0 at top left
    # Using 0.01 for 1% - aesthetic padding

    if (logo_position == "top right") {
        x_pos = plot_width - logo_width - 0.01 * plot_width
        y_pos = 0.01 * plot_height
    } else if (logo_position == "top left") {
        x_pos = 0.01 * plot_width
        y_pos = 0.01 * plot_height
    } else if (logo_position == "bottom right") {
        x_pos = plot_width - logo_width - 0.01 * plot_width
        y_pos = plot_height - logo_height - 0.01 * plot_height
    } else if (logo_position == "bottom left") {
        x_pos = 0.01 * plot_width
        y_pos = plot_height - logo_height - 0.01 * plot_height
    }

    # Compose the actual overlay
    magick::image_composite(plot, logo, offset = paste0("+", x_pos, "+", y_pos))

}

So there you go! Happy logo adding and bird putting!

For more information on adding logos to plots with magick::image_append() instead, check out this blog post by Daniel Hadley on using magick::image_append to add a logo or his RStudioConf talk on the same idea.

Cheers!