Search code examples
rggplot2ggforceggimage

Maintaining Aspect Ratio of Shapes and Images in ggplot/ ggimage


I'm trying to build a visualisation with both drawn shapes (e.g. using geom_circle) and images. In both cases, I want to be able to position them on the page specifically with coordinates, rather than using one of the built in chart types.

See update further down...

However, I can either get the circles with the correct aspect ratio (i.e. round not oval) or the images, but not both. In the example below, you can see the image is not shown as square when it is.

enter image description here

I have tried various things including coord_fixed, scale_size_identity and coord_cartesian, but to no avail. The overall output will be landscape, which is why I have set the cartesian limits as I have.

This is a simplified version. In the full version, I'll get the coordinates from the data (which I'm fine with).

images <-data.frame(url = c("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"))

ggplot(mpg) + 
    ggforce::geom_circle(aes(x0 = displ * 50 - 60, y0 = hwy, r=cty)) +
    #scale_size_identity() +

    # Add Image
    ggimage::geom_image(data = images,
                 aes(x = 4, y = 20, image=url),
                 size = 0.4,
                 hjust = 0.0, 
                 by="height"
        ) +
        coord_cartesian(
            xlim = c(0, 120),
            ylim = c(0, 80),
            expand = FALSE,
            clip = "on"
        )

Update following really helpful input from @tjebo and further investigation.

I have now found there are at least 4 ways to add images to plots, each with their own advantages and disadvantages. I've listed these below to help others on this search.

Draw basic shapes to which images can be added

g <- ggplot(mpg) + 
  ggforce::geom_circle(aes(x0 = displ * 50 - 60, y0 = hwy, r=cty))

Plot with ggtexture - multiple images - aspect defined by x and y max - min

https://rdrr.io/github/clauswilke/ggtextures/man/geom_textured_rect.html

g + ggtextures::geom_textured_rect(data = images, 
                                   aes(xmin = 20, xmax = 60,
                                       ymin = 20, ymax = 60, image = url), 
                                   lty = "blank", # line type of blank to remove border
                                   fill="white", # used to fill transparent areas of image
                                   nrow = 1,
                                   ncol = 1,
                                   img_width = unit(1, "null"),
                                   img_height = unit(1, "null"),
                                   position = "identity") +
  coord_equal() # A fixed scale coordinate system forces a specified ratio between the physical representation of data units on the axes. 

Plot with ggimage - multiple images - aspect defined by device

g + ggimage::geom_image(data = images,
                        aes(x = 20, y = 40, image=url),
                        size = 0.2,
                        by="height"
  )

Plot with cowplot - single image - freedom over aspect

Independent drawing surface and scale (0-1)

cowplot::ggdraw(g) + 
  cowplot::draw_image(images[[1, "url"]], 
                      x = .5, y = .3, width = 0.5, height = 0.5)

Plot with annotation_custom (ggplot) - original aspect

Seems to use widest of width of height and centre on mid coordinates

image <- magick::image_read(images[[1, "url"]])
rasterImg <- grid::rasterGrob(image, interpolate = FALSE) 
g + annotation_custom(rasterImg, xmin = 00, xmax =200, ymin = 10, ymax = 50)

Solution

  • A different approach would be to not use ggimage - e.g., use cowplot for custom annotation, makes it super simple to add an image.

    library(ggplot2)
    library(cowplot)
    
    p <- ggplot(mpg) + 
      see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
      scale_size_identity() 
    
    ggdraw(p) + 
      draw_image("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG", 
                 x = .5, y = .3, width = 0.5, height = 0.5)
    

    Created on 2021-02-13 by the reprex package (v1.0.0)

    Or, use the ggtextures package, with a little tweak of the coordinate system

    this discussion seems relevant

    library(ggtextures)
    library(ggplot2)
    library(tibble)
    img_df <- tibble(
      xmin = 1, ymin = 1, xmax = 4, ymax = 4,
      image = "https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"
    )
    
    ggplot(mpg) + 
      see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
      geom_textured_rect(data = img_df, 
                         aes(xmin = xmin, xmax = xmax,
                             ymin = ymin, ymax = ymax, image = image),
                         nrow = 1,
                         ncol = 1,
                         img_width = unit(1, "null"),
                         img_height = unit(1, "null"),
                         position = "identity") +
      coord_equal() # this is then necessary... 
    

    Created on 2021-02-13 by the reprex package (v1.0.0)