Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rotate a custom annotation in ggplot?

I would like to rotate an image included in annotation_custom in ggplot2.

For an animation with gganimate, I would like to add images with specific angles to a line plot. Unfortunately, there is no angle parameter in annotation_custom.

library(tidyverse)
library(grid)
library(png)

gundf <- tibble(year = c(1999:2017),
                deaths = c(28874, 28663, 29573, 30242, 30136, 29569, 30694, 30896, 
                           31224, 31593, 31347, 31672, 32351, 33563, 33636, 33594, 
                           36252, 38658, 39773))

# Download png from cl.ly/47216db435d3
bullet <- rasterGrob(readPNG("bullet.png"))

gundf %>% 
  ggplot(aes(x=year, y=deaths)) + 
  geom_line(size=1.2) +
  mapply(function(x, y) {
    annotation_custom(bullet, xmin = x-0.5, 
                              xmax = x+0.5, 
                              ymin = y-500, 
                              ymax = y+500)
                         },
    gundf$year, gundf$deaths) + 
  theme_minimal()

Result:

image from cl.ly/b83aefc7a7fa/gunplot.png

As can be seen in the plot, all bullets are horizontally aligned. I would like to rotate the bullets to correspond to the slope of the line. In the animation, the line should emerge as if a bullet is shot (which will be another problem since there are no aes parameters in annotate_custom).

Thanks in advance for your suggestions!

like image 316
Sma Avatar asked Oct 27 '22 19:10

Sma


1 Answers

You could use the magick package to rotate the png file:

library(magick)

bullet <- magick::image_read("bullet.png")

## To remove white borders from the example png
bullet <- magick::image_background(bullet, "#FF000000")

## Create angle column
gundf$angle <- seq(0,360, length.out = nrow(gundf))

## Plot
gundf %>% 
  ggplot(aes(x=year, y=deaths)) + 
  geom_line(size=1.2) +
  mapply(function(x, y, angle) {
    annotation_custom(rasterGrob(magick::image_rotate(bullet, angle)),
                              xmin = x-0.5, 
                              xmax = x+0.5, 
                              ymin = y-500, 
                              ymax = y+500)
                         },
    gundf$year, gundf$deaths, gundf$angle) + 
  theme_minimal()

enter image description here

As for your question about making the bullet to follow the line, see the comments to this answer. Making objects to have the same slope than a line in ggplot2 is tricky because you need to know the aspect ratio of the plotting region (information that is not printed anywhere at the moment, as far as I know). You can solve this by making your plot to a file (pdf or png) using a defined aspect ratio. You can then use the equation from @Andrie (180/pi * atan(slope * aspect ratio)) instead of the one I used in the example. There might be a slight mismatch, which you can try to adjust away using a constant. Also, it might be a good idea to linearly interpolate one point between each point in your dataset because now you are plotting the bullet where the slope changes. Doing that in animation would work poorly. It would probably be easier to plot the bullet where the slope is constant instead.

like image 178
Mikko Avatar answered Nov 15 '22 07:11

Mikko