1
votes

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)
2
geforce::geom_circle? I added the tag, but please edit if this is from another librarytjebo
don't know why, when I plot your code it results in sth really different. Could you try using reprex for your example please?tjebo
My apologies @tjebo, I changed and broke my simplified example without realising. I've adjusted it now to properly reflect the original which sets the coord limits and shows that ggimage is scaling to the device as you suggest. All 3 of your alternatives work, I will just have to decide which is best. I want a good approach for creating output which includes multiple charts, bespoke shapes and images, quite likely in a faceted style where all the elements are repeated. I'm also looking at ggimage::geom_subview. Thanks very much for your help. I'll choose one of your answers later todayChris
No worries, I thought this may have been the case - that's why using the reprex package is such a good idea :) never again breaking code, because it will always reproduce your code in an empty environment.tjebo
Thanks for you help @tjebo, following your advice I've had a really good look at the options, which was great as this exercise was all about learning the best way of adding images and it turns out there's a few. I've documented these in the original post now for others.Chris

2 Answers

2
votes

I strongly feel you may have seen this thread: Can geom_image() from the ggimage package be made to preserve the image aspect ratio? - this was asked and answered by ggplot2 gurus such as Claus Wilke, Baptiste and Marco Sandri - it seems that ggimage is scaling to the device. Thus if you would like a square, you need to save to a device of square dimensions. Or, if you don't have a square image, of course, dimensions relative to your image used.

I used see::geom_point2 (no weird border), because I got the strong feeling that you have not used ggforce::geom_circle. Also mild changes where I added the aes call.

library(ggimage)
#> Loading required package: ggplot2

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

# g <- 
ggplot(mpg) + 
  see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
    scale_size_identity() +
  # Add Image
  geom_image(data = images,
             aes(x = 4, y = 20, image=url),
             size = 0.4,
             hjust = 0.0, 
             by = "height"
             )

Using reprex, with both fig width and height set to 3 inches

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

1
votes

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)