Tutorial: Images as Symbols in Plots
Difficulty Level:
Intermediate
Category: Visualizing Data
Libraries
Required: ‘png’
Using images of objects as symbols (instead of dots or other abstract shapes) is an increasingly popular strategy when creating scientific plots. When designed well, such plots can be highly intuitive and aesthetically appealing. For example, using animal silhouettes as symbols is more informative than plotting dots or triangles. The latter tell us nothing about objects they represent. Here, simple examples are used to show how to produce such image-enhanced plots in R.
This example uses standard plot functions available in R. There are also tutorials showing how to do this using ggplot (e.g., https://rpubs.com/jalapic/animals). The example below will involve the following steps:
Part 1. Getting Images
Part 2. Formatting and Adjusting Images
Part 3. Generating Figures
Images can be obtained, created, and stored digitally in various ways (a topic beyond the scope of this example). The R script presented below is designed for images in png format. The specific images used here came from http://phylopic.org/. This website archives images of organisms with minimal copyright restrictions, other than giving credit to the authors of those files (check for ‘attribution’ statements when downloading images). In many cases, creating own custom images based on own objects is the best approach. The script below should work for any black and white png-formatted file with transparent background.
The three images used here are a clam, a snail, and a sea urchin. You can download those images at the following URLs (once you access the webpage, click on ‘RasterFile’ icons to download a high-resolution png file):
Clam: http://phylopic.org/image/0178e456-7b3c-4c52-8112-bf76b4ecc056/
Snail: http://phylopic.org/image/6c2e67f0-14e7-4ba0-ba73-2420cacfa9a3/
Sea Urchin: http://phylopic.org/image/efde204d-4b28-4b5e-9acb-c58014a6b9b9/
In this example, the downloaded images were saved using original file names to retain info about authors and the original URL of the image. The library “png” can be used to upload png-formatted images into R. Here is an example of a script that uploads those images and plots them in R while adding some simple annotations.
library(png)
ClamB <- readPNG("PhyloPic.0178e456.Katie-S-Collins.Bivalvia_Neonucula_Neonucula-pratasensis_Nuculida_Nuculidae_Nuculoidea_Protobranchia.png")
SnailB <- readPNG("PhyloPic.6c2e67f0.Tauana-J-Cunha.Gastropoda_Phasianellidae_Phasianelloidea_Tricolia_Tricolia-pullus_Vetigastropoda.png")
UrchinB <- readPNG("PhyloPic.efde204d.Didier-Descouens-vectorized-by-T-Michael-Keesey.Cidaridae_Cidarinae_Cidaris_Cidaris-cidaris_Cidaroida_Echinoidea_Perischoechinoidea.png")
plot(0,0, xlim=c(0,11), ylim=c(0,3), type='n', axes=F,
xlab='', ylab='')
rasterImage(ClamB, 0,0,3,3)
rasterImage(SnailB, 4,0,7,3)
rasterImage(UrchinB, 8,0,11,3)
mtext(side=1, line=1, adj=0.1, 'Image by Katie S. Collins', cex=0.7)
mtext(side=1, line=1, adj=0.5, 'Image by Tauna J. Cunha', cex=0.7)
mtext(side=1, line=1, adj=0.9, 'Image by T. Michael Keesey', cex=0.7)
mtext(side=1, line=3, cex=1, 'FIGURE 1. Images of three marine animals used in this example.', col='red4')
The images used here are black-and-white silhouettes. For plotting, converting them to color images may be desirable. Making silhouettes semi-transparent may be also useful, especially for plots in which images overlap. Since color and transparency conversion may need to be apply to multiple images, as is the case in this example, it is efficient to write a custom function for objects produced by readPNG function from .png files. The custom function below (‘colorImageF’) accepts any R color name (e.g., ‘red3’, ‘forestgreen’, etc.) and also allows for setting the transparency level “alpha”. Alpha ranges from 0 (transparent) to 1 (opaque). Alpha values between 0 and 1 produce semi-transparent colors. This function is designed for black and white images. It may not work correctly for grayscale images or images with non-transparent backgrounds.
colorImageF <- function(imageX, col, alpha=0.5) {
image2 <- as.raster(imageX)
image2[image2 == "#000000FF"] <- rgb(t(col2rgb(col)), maxColorValue = 255, alpha=alpha*255)
return(image2)
}
Clam <- colorImageF(imageX = ClamB, col='firebrick', alpha = 0.6)
Snail <- colorImageF(imageX = SnailB, col='forestgreen', alpha = 0.6)
Urchin <- colorImageF(imageX = UrchinB, col='gray30', alpha = 0.6)
plot(0,0, xlim=c(0,11), ylim=c(0,5), type='n', axes=F,
xlab='', ylab='')
rasterImage(Clam, 0,0,5,5)
rasterImage(Snail, 3,0,8,5)
rasterImage(Urchin, 6,0,11,5)
mtext(side=1, line=1, adj=0.1, 'Image by Katie S. Collins', cex=0.7)
mtext(side=1, line=1, adj=0.5, 'Image by Tauna J. Cunha', cex=0.7)
mtext(side=1, line=1, adj=0.9, 'Image by T. Michael Keesey', cex=0.7)
mtext(side=1, line=3, cex=1, 'FIGURE 2. Semi-transparent, color images of the three animals from Fig. 1.', col='red4')
The images in Fig. 1 are distorted when compared to original images from which they were derived. This is because, rasterImage statements in the script above defined image dimensions using arbitrary coordinates. For example, “rasterImage(Clam, 0,0,5,5)” means that the image is placed using the following x-y plot coordinates: 0,0, 0,5, 5,5, and 5,0. To render images with correct length-width ratios, one needs to consider png image ratios, scaling of x and y axes, and figure dimensions. The R objects produced by the readPNG function are 3D arrays, with the first two dimensions representing image coordinates. Consequently, the ratio of the first two dimensions are the length/width ratio of the image object in R.
( ClamRt <- nrow(ClamB)/ncol(ClamB) )
## [1] 0.8799314
( SnailRt <- nrow(SnailB)/ncol(SnailB) )
## [1] 1.630573
( UrchinRt <- nrow(UrchinB)/ncol(UrchinB) )
## [1] 0.609375
None of those images are squares (square-shaped images would have ratios of 1). Thus, we will need to adjust raterImage coordinates using the image-specific x-y ratios.
To create an example plot, let’s create a body size estimate (cex.size) and two continuous ratio variables (x and y) for 20 clams, 20 snails, and 15 urchins.
#--------------------------------------------------
# dummy data representing body size (cex.size),
# and x and y variables
# Let there be 20 clams
cex.size1 <- abs(rnorm(20, 0.2, 0.1))
x1 <- rnorm(20, 3, 1)
y1 <- rnorm(20, 36.5, 8)
# Let there be 20 snails
cex.size2 <- abs(rnorm(20, 0.2, 0.05))
x2 <- rnorm(20, 7.5, 1)
y2 <- rnorm(20, 10, 20)
# Let there be 15 urchins
cex.size3 <- abs(rnorm(20, 0.3, 0.1))
x3 <- rnorm(15, 3, 0.5)
y3 <- rnorm(15, 75, 10)
All pieces are in place now: (1) numerical data to plot, (2) semi-transparent, custom-colored images; and (3) image ratio info. This can be all integrated to plot our final plot. To make sure that images are not distorted, the width and height of the plot was set in Rmarkdown to square (not shown in the script below) and the raterImage parameters for the y coordinates were adjusted using image ratios stored previously as objects “ClamRt”, “SnailRt”, and “UrchinRt”.
plot(0,0, xlim=range(c(x1,x2,x3)), ylim=range(c(y1,y2,y3)),
type='n', las=1, xlab='', ylab='variable 2')
for(i in 1:length(x1)) {
rasterImage(Clam, x1[i] - cex.size1[i], y1[i] - ClamRt*cex.size1[i],
x1[i] + cex.size1[i], y1[i] + ClamRt*cex.size1[i])
}
for(i in 1:length(x2)) {
rasterImage(Snail, x2[i] - cex.size2[i], y2[i] - SnailRt*cex.size2[i],
x2[i] + cex.size2[i], y2[i] + SnailRt*cex.size2[i])
}
for(i in 1:length(x3)) {
rasterImage(Urchin, x3[i] - cex.size3[i], y3[i] - UrchinRt*cex.size3[i],
x3[i] + cex.size3[i], y3[i] + UrchinRt*cex.size3[i])
}
mtext(side=1, line=2, 'variable 1')
mtext(side=1, line=3, 'FIGURE 3: A scatter plot using images of animals as symbols.', col = 'red4')
mtext(side=1, line=3.8, cex=0.7, 'images by K.S. Collins (clam), T.J. Cunha (snail), T.M Keesey (urchin)')
Despite the fact that the plot’s dimensions were set to be equal and coordinates were adjusted for specific png image ratios, the images shown on the resulting plot are highly distorted. This is because dimensions of images are rendered using data-specific x and y coordinates. Thus, unless x and y axes are scaled over the same range of values, the images will be distorted. The ratio of the range of y values to the range of x values (‘yxr’ in the script below) can be used as an extra parameter to correct for this problem and adjust images.
yxr <- diff(range(c(y1, y2, y3))) / diff(range(c(x1, x2, x3)))
plot(0,0, xlim=range(c(x1, x2, x3)), ylim=range(c(y1, y2, y3)),
type='n', las=1, xlab='', ylab='variable 2')
for(i in 1:length(x1)) {
rasterImage(Clam, x1[i] - cex.size1[i], y1[i] - yxr*ClamRt*cex.size1[i],
x1[i] + cex.size1[i], y1[i] + yxr*ClamRt*cex.size1[i])
}
for(i in 1:length(x2)) {
rasterImage(Snail, x2[i] - cex.size2[i], y2[i] - yxr*SnailRt*cex.size2[i],
x2[i] + cex.size2[i], y2[i] + yxr*SnailRt*cex.size2[i])
}
for(i in 1:length(x3)) {
rasterImage(Urchin, x3[i] - cex.size3[i], y3[i] - yxr*UrchinRt*cex.size3[i],
x3[i] + cex.size3[i], y3[i] + yxr*UrchinRt*cex.size3[i])
}
mtext(side=1, line=2, 'variable 1')
mtext(side=1, line=3, 'FIGURE 4: A scatter plot using rescaled images of animals as symbols.', col='red4')
mtext(side=1, line=3.8, cex=0.7, 'Images by K.S. Collins (clam), T.J. Cunha (snail), T.M Keesey (urchin)')
Comments or Questions? Please email Michal at kowalewski@ufl.edu.
Copyright / Attributions
This
work is licensed under a
Creative
Commons Attribution-NonCommercial 4.0 International License.