4

R has a number of standard symbols / shapes that can be used as markers of data points in an R line chart (in case ambiguous, see http://www.cookbook-r.com/Graphs/Shapes_and_line_types/ on what I mean).

What I want to do is use my own custom icons (e.g. GIFs of apple, banana, cherry, etc) in place of the standard shapes.

How can I do so in R? Doesn't have to be native R graphics, I'm open to packages eg ggplot2 etc.

My intended use is in a line chart where the markers will appear on the legend as well.

The answers here ( Labelling the plots with images on graph in ggplot2 ) addresses a similar question by plotting the image directly as if they are independent items, so not really tied to the data / legend.

Community
  • 1
  • 1
Ricky
  • 4,616
  • 6
  • 42
  • 72
  • 1
    Did you read [this SO post](http://stackoverflow.com/questions/20733328/labelling-the-plots-with-images-on-graph-in-ggplot2)? This seems to be a question which has been asked previously. – Tim Biegeleisen Jan 18 '16 at 05:07
  • 1
    I did but not exactly what I want; those are scatter plots. I'm looking for line chart where the points are the shapes, which would appear in a legend. I'll edit my question to clarify – Ricky Jan 18 '16 at 05:18
  • the best option is probably to [write your own geom](https://github.com/hadley/ggplot2/blob/master/vignettes/extending-ggplot2.Rmd#creating-a-new-geom) – baptiste Jan 18 '16 at 20:08

1 Answers1

10

I don't know of a way to use custom point markers that work with geom_point (though you could create your own geom as suggested by @baptiste). However, if you're willing to endure some coding pain, you can use annotation_custom to create something similar to what you'd get with geom_point. The painful part is that you have to make some manual adjustments (though you could probably create programmatic logic that would handle some of that for you if you're going to do this a lot). The plot also renders very slowly. Still, it was fun to take a crack at it.

Load packages and get two images that will become our point markers:

library(ggplot2)
library(RCurl)
library(jpeg)
library(grid)

## Image 1
URL1 = "http://www.entertainmentearth.com/images/AUTOIMAGES/QMSER0179lg.jpg"
serenity = readJPEG(getURLContent(URL1))

## Image 2
URL2 = "http://cdn.pastemagazine.com/www/articles/2010/03/12/malcolm_reynolds.jpg"
mal = readJPEG(getURLContent(URL2))

# Crop the mal image
mal = mal[40:250,,]

## Turn images into raster grobs
serenity = rasterGrob(serenity)
mal = rasterGrob(mal)

# Make the white background transparent in the serenity image
serenity$raster[serenity$raster=="#FFFFFF"] = "#FFFFFF00" 

Create fake data and set up plot:

# Create fake data
df = data.frame(x=rep(1:4,2), y=c(1,1,2,4,6.5,5,5.5,4.8), g=rep(c("s","m"),each=4))

# Set up a plot
p = ggplot(df, aes(x, y, group=g)) + 
  geom_line() +
  theme_bw()

Now, instead of a geom, use annotation_custom to plot the images as point markers. annotation_custom seems to work only with a single point at a time, rather than a vector of points, so we'll use a for loop to plot each point. We have two different images we want to plot, so we'll use a separate loop for each one:

a=0.8
for (i in rownames(df[df$g=="s",])) {
  p = p + annotation_custom(serenity, df[i,"x"]-a,df[i,"x"]+a,df[i,"y"]-a,df[i,"y"]+a)  
}

b=0.4
for (i in rownames(df[df$g=="m",])) {
  p = p + annotation_custom(mal, df[i,"x"]-b,df[i,"x"]+b,df[i,"y"]-b,df[i,"y"]+b)  
}

Finally, add the legend and turn off clipping so that the legend will show up on the final plot:

a = 0.8*a
b = 0.8*b

p = p + coord_cartesian(xlim=c(0,5), ylim=c(0,7)) +
  theme(plot.margin=unit(c(1,10,1,1),"lines")) +
  annotation_custom(serenity, 5.8-a,5.8+a,3.4-a,3.4+a) +
  annotate(geom="text", x=5.8+0.5, y=3.4, label="Serenity", hjust=0) +
  annotation_custom(mal, 5.8-b,5.8+b,4.6-b,4.6+b) +
  annotate(geom="text", x=5.8+0.5, y=4.6, label="Mal", hjust=0)

# Turn off clipping
p <- ggplot_gtable(ggplot_build(p))
p$layout$clip <- "off"
grid.draw(p)

enter image description here

UPDATE: The new ggimage package might ulimately make this a bit easier. Below is an example. There are a few things I'd like to improve about the resulting plot.

  • Changing the aspect ratio of the plot also changes the aspect ratio of the images, whereas I'd prefer the images to remain in their native aspect ratio.

  • The Serenity image has a white background that covers up the plot grid and the image of Mal (since Serenity is plotted after Mal). In the example above, I made the white background transparent. However, even when I saved the raster grobs created above as jpegs (so that I could use the version of Serenity with the transparent background) and reloaded them (instead of getting the images directly from the URLs as I do below) the image of Serenity still had a white background.

  • The legend doesn't use the images as the "point markers" and instead has a blank space where the markers would normally be.

Perhaps future versions of the package will create additional flexibility related to these issues, or perhaps there's already a way I'm not aware of to address these concerns.

df = data.frame(x=rep(1:4,2), y=c(1,1,2,4,6.5,5,5.5,4.8), 
                Firefly=rep(c("Serenity","Mal"),each=4),
                image=rep(c("http://www.entertainmentearth.com/images/AUTOIMAGES/QMSER0179lg.jpg",
                            "http://cdn.pastemagazine.com/www/articles/2010/03/12/malcolm_reynolds.jpg"), each=4))

ggplot(df, aes(x, y)) + 
  geom_line(aes(group=Firefly)) +
  geom_image(aes(image=image, size=Firefly)) +
  theme_bw() +
  scale_size_manual(values=c(0.1,0.15)) +
  coord_fixed(ratio=2/3, xlim=c(0.5, 4.5), ylim=c(0,7))

enter image description here

eipi10
  • 91,525
  • 24
  • 209
  • 285