Search code examples
rimageggplot2facet-wrappolar-coordinates

How do I add a website image to a facet-wrap barplot with polar coordinates?


I have created a circular (polar) barplot with facets and would like to place headshots of two baseball players in the center of each plot. I have tried several methods including annotation_custom (only works with cartesian plots, not coord_polar) and a similar, questionable solution found here: Trying to add an image to a polar plot gives "Error: annotation_custom only works with Cartesian coordinates".

Would be grateful for any and all help. The headshot links for the two players are included in the org column of the my_data_long data frame. Also attaching a screenshot of the existing plot (sans headshots, of course) below for context.enter image description here

my_data_long <- structure(data.frame(player = c("Jackson Holliday", "Jackson Holliday", 
                                          "Jackson Holliday", "Jackson Holliday", "Jackson Holliday", "Jackson Holliday", 
                                          "Jackson Holliday", "James Wood", "James Wood", "James Wood", 
                                          "James Wood", "James Wood", "James Wood", "James Wood", "Jackson Holliday", 
                                          "Jackson Holliday", "Jackson Holliday", "Jackson Holliday", "Jackson Holliday", 
                                          "Jackson Holliday", "Jackson Holliday", "James Wood", "James Wood", 
                                          "James Wood", "James Wood", "James Wood", "James Wood", "James Wood"
), names = c("hit", "raw_power", "game_power", "spd", "fld", 
             "bat_ctrl", "pitch_sel", "hit", "raw_power", "game_power", "spd", 
             "fld", "bat_ctrl", "pitch_sel", "hit", "raw_power", "game_power", 
             "spd", "fld", "bat_ctrl", "pitch_sel", "hit", "raw_power", "game_power", 
             "spd", "fld", "bat_ctrl", "pitch_sel"), variable = structure(c(1L, 
              1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 
              2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), levels = c("current", 
"future"), class = "factor"), value = c(45, 50, 25, 50, 40, 60, 
60, 30, 70, 55, 60, 45, 40, 50, 60, 60, 60, 50, 50, 60, 60, 35, 
80, 80, 50, 50, 40, 50), org = c("https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/702616/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current", 
"https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_360/v1/people/695578/headshot/milb/current")))

library(tidyverse)

ggplot(data=my_data_long, aes(x=names, y=value, fill=variable, color=variable, alpha=variable))+ 
  geom_hline(
    aes(yintercept = y), 
    data.frame(y = c(4:8) * 10),
    color = "lightgrey"
  ) + 
  
  geom_bar(stat="identity", position ="identity") +
  scale_colour_manual(values=c("lightblue4", "red")) +
  scale_fill_manual(values=c("lightblue", "pink")) +
  scale_alpha_manual(values=c(.8, .3)) +
  scale_y_continuous(limits = c(-40,82),
                     expand = c(0,0), 
                     breaks = c(40,50,60,70,80)) +
  theme_bw() +
  facet_wrap(~player) +
  coord_polar() +
  
  annotate(
    x = 4, 
    y = 40, 
    label = "40", 
    geom = "text", 
    color = "gray12", 
    family = "Trebuchet MS"
  ) +
  
  annotate(
    x = 4, 
    y = 60, 
    label = "60", 
    geom = "text", 
    color = "gray12", 
    family = "Trebuchet MS"
  ) +
  
  annotate(
    x = 4, 
    y = 80, 
    label = "80", 
    geom = "text", 
    color = "gray12", 
    family = "Trebuchet MS"
  ) +
  
  labs(title = "Top MLB Prospect Tools",
    subtitle = "Using 20-80 Scouting Scale",
    caption = "Data: FanGraphs") +
  # Customize general theme
  theme(
    
    # Set default color and font family for the text
    text = element_text(color = "gray12", family = "Trebuchet MS"),
    
    # Customize the text in the title, subtitle, and caption
    plot.title = element_text(face = "bold", hjust = 0.0, size = 20),
    plot.subtitle = element_text(hjust = 0.0, size = 14),
    plot.caption = element_text(size = 12),
    axis.title = element_blank(),
    axis.text.y = element_blank(), 
    axis.ticks.y = element_blank(),
    axis.text.x = element_text(size = 10),
    strip.text = element_text(size = 16),
    legend.title = element_blank(),
    
    # Make the background white and remove extra grid lines
    panel.background = element_rect(fill = "white", color = "white"),
    panel.grid = element_blank(),
    panel.grid.major.x = element_blank()
  ) 

Solution

  • One option would be to use ggimage

    library(tidyverse)
    library(ggimage)
    
    images <- my_data_long |>
      distinct(player, org)
    
    ggplot(data = my_data_long, aes(
      x = names, y = value, fill = variable,
      color = variable, alpha = variable
    )) +
      geom_hline(
        aes(yintercept = y),
        data.frame(y = c(4:8) * 10),
        color = "lightgrey"
      ) +
      geom_bar(stat = "identity", position = "identity") +
      geom_image(
        data = images, aes(image = org), x = 4, y = -40, size = .1,
        inherit.aes = FALSE
      ) +
      scale_colour_manual(values = c("lightblue4", "red")) +
      scale_fill_manual(values = c("lightblue", "pink")) +
      scale_alpha_manual(values = c(.8, .3)) +
      scale_y_continuous(
        limits = c(-40, 82),
        expand = c(0, 0),
        breaks = c(40, 50, 60, 70, 80)
      ) +
      theme_bw() +
      facet_wrap(~player) +
      coord_polar() +
      geom_text(
        data = data.frame(x = 4, y = c(40, 60, 80)),
        aes(x = x, y = y, label = y),
        color = "gray12",
        family = "Trebuchet MS",
        inherit.aes = FALSE
      ) +
      labs(
        title = "Top MLB Prospect Tools",
        subtitle = "Using 20-80 Scouting Scale",
        caption = "Data: FanGraphs"
      ) +
      # Customize general theme
      theme(
        # Set default color and font family for the text
        text = element_text(color = "gray12", family = "Trebuchet MS"),
        # Customize the text in the title, subtitle, and caption
        plot.title = element_text(face = "bold", hjust = 0.0, size = 20),
        plot.subtitle = element_text(hjust = 0.0, size = 14),
        plot.caption = element_text(size = 12),
        axis.title = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        axis.text.x = element_text(size = 10),
        strip.text = element_text(size = 16),
        legend.title = element_blank(),
        # Make the background white and remove extra grid lines
        panel.background = element_rect(fill = "white", color = "white"),
        panel.grid = element_blank(),
        panel.grid.major.x = element_blank()
      )
    

    UPDATE Perhaps an even prettier approach would be to additionally use the cropcircles package:

    library(cropcircles)
    
    images <- my_data_long |>
      distinct(player, org) |>
      mutate(
        img_circle = circle_crop(org, border_size = 12, border_colour = "black")
      )
    
      ... +
      geom_image(
        data = images, aes(image = img_circle), x = 4, y = -40, size = .2,
        inherit.aes = FALSE
      ) +
      ...
    
    

    enter image description here