18

I'm hoping I can combine the spiffy importing and drawing powers of grImport with the awesome graphing power of ggplot2, but I simply don't understand the grid system well enough to find an elegant way of achieving what I want. What I want is to replace the x-axis tick labels in a ggplot2 graph with images imported using grImport.

Since both packages use grid functions, I'm hoping there is a way to use grid.symbols() either within the ggplot2 framework, which would be ideal, or at least on an existing plot in a device. Does anyone with more knowledge of such things know of a robust way to do this? Or, can anyone point me to more information to help me learn about grobs, viewports, etc.? I have read through the free Chapter of Paul Murrel's R graphics book on the Grid graphics model, but I'm not familiar enough with the inner workings of ggplot2 to make the link.

My question is very similar to an existing question about using an image as a plot point, although I'm more interested in the axis labels rather than the plot points. However, I would be curious as to how similar the two tasks are, and if that solution can be adapted for this purpose. I wasn't able to figure it out on my own.

This is my first post, so I'm not allowed to post an image, but this code achieves something close to what I want. Note: this approach uses an ugly, ugly hack that is not portable, and unsatifactory. The final plot I want to produce will have facets (facet_grid), and the x-axis is for a factor with different pictures at each axis tick, not a continuous variable, which is why I'm looking for a more general / robust solution that doesn't require a lot of trial & error.

library(ggplot2)
## library(grImport)  # not needed for this example, but would be for grid.symbols()

p <- ggplot(mtcars, aes(cyl, mpg)) + stat_summary(fun.data = "mean_cl_boot")
print(p)

## Replace (in this case, overlay) x-axis tick labels with a graphic / grob
iconSize  <- 0.05
iconHt    <- 0.2 
padding   <- 0.09    # horizontal padding around axis: I found this by trial & error
tickSp    <- (1-padding)/(4*2)
downViewport("axis_h-5-3")
## I would use grid.symbols() with an imported Picture in place of grid.circle(),
## but the idea is the same: draw a shape at the ticks along the axis.
for (i in 0:(max(mtcars$cyl) - min(mtcars$cyl)) )
{
  grid.circle(x = (padding/2 + tickSp*(i*2)), y = iconHt, 
              r = iconSize*(min(mtcars$cyl)+i), gp = gpar(fill="black"))
}

upViewport()
Community
  • 1
  • 1
  • an alternative option would be to use `tikzDevice`, and get LaTeX to [insert the vector graphics](http://groups.google.com/group/ggplot2/browse_thread/thread/38f8695ad55cc2d6?pli=1) in place of the labels. – baptiste Jan 18 '12 at 06:15
  • Thanks for all the instructive examples @baptiste !, it took me a while to process it all, but they were very helpful (see comments below). I'll keep `tikzDevice` in mind, although it seems like a little overkill at this point. I did try the example in the link, but it failed with errors (LaTeX couldn't understand the commands for the chemical compounds), and made some changes to my R session that I couldn't figure out how to reverse without re-starting R :( – Jonathan Whiteley Jan 19 '12 at 02:52
  • So close, but I want to have a different picture at each axis tick. Can any of the solutions be adapted? Alternatively, is there a way to extract the positions of the x-axis ticks in such a way that they can be used in successive calls to `grid.symbols()` or `symbolsGrob()`? – Jonathan Whiteley Jan 19 '12 at 02:56
  • using `tikzDevice` you should be able to use labels like `scale_x_continuous(labels = c("\\includegraphics{img1}", "\\includegraphics{img2}"))`. The example linked above required a LaTeX package to draw the symbols on-the-fly. – baptiste Jan 19 '12 at 03:47

3 Answers3

24

here is an example:

# convert ps to RGML
PostScriptTrace(file.path(system.file(package = "grImport"), "doc", "GNU.ps"), "GNU.xml")
PostScriptTrace(file.path(system.file(package = "grImport"), "doc", "tiger.ps"), "tiger.xml")
# read xml
pics <- list(a = readPicture("GNU.xml"), b = readPicture("tiger.xml"))

# custom function for x axis label.
my_axis <- function () {
  structure(
      function(label, x = 0.5, y = 0.5, ...) {
         absoluteGrob(
           do.call("gList", mapply(symbolsGrob, pics[label], x, y, SIMPLIFY = FALSE)),
           height = unit(1.5, "cm"))
    }
)}

qplot(factor(c("a", "b")), 1:2) + opts( axis.text.x = my_axis())

enter image description here

kohske
  • 65,572
  • 8
  • 165
  • 155
  • 1
    in future ggplot2 may have an axis-ing system such as: `+axis(x = axis_picture(), y = axis_text)`, but in future. – kohske Jan 19 '12 at 07:34
  • ok, I take back my "ugly hack" described below ;) this is quite neat make up for the lacking vectorisation of `grid.symbols`. – baptiste Jan 19 '12 at 08:07
  • Brilliant! Thank you, and keep up the great work. It's great to see cool features like this being planned for ggplot2, but it's still impressive that much of it can already be accomplished with the right code ;) I was able to make some minor tweaks to make a custom function that accepts the list of pictures as an argument (I called mine `picture_axis()`), but this is exactly what I had mind. You guys rock. – Jonathan Whiteley Jan 20 '12 at 03:27
  • 1
    I think that `structure()` is redundant – hadley Jan 20 '12 at 14:44
  • Exactly. Officially(?) there should be `class = "theme", type = "custom", call = match.call())`. – kohske Jan 20 '12 at 14:48
  • I am getting following error in above example. qplot(factor(c("a", "b")), 1:2) + opts( axis.text.x = my_axis()) Error in el(...) : could not find function "absoluteGrob" – Manish Jul 11 '12 at 03:04
6

For a single panel, it is fairly straight-forward to extract information from the x axis grob, and replace it with your own. I'm not sure how this could be extended cleanly to automatic, multi-panel axes.

library(grid)
library(ggplot2)

p <- qplot(1:12, rnorm(12))
grid.newpage()
g <- ggplotGrob(p)
grid.draw(g)
g0 <- getGrob(g, gPath("axis.text.x"), grep=TRUE)
grid.set(gPath("axis.text.x"),
         pointsGrob(x = g0$x, y=0*g0$x + unit(0.5,"npc"),
                    pch=19, gp=gpar(col=seq_along(g0$x)),
                    name = g0$name), grep = TRUE)

screenshot

baptiste
  • 75,767
  • 19
  • 198
  • 294
5

Below is a hack to use a custom grob for axis labels.

library(grid)
library(ggplot2)

## convert the labels to some parameter to be used in the custom grob
## here simply an index that will be interpreted as color
mapping <- function(x, ...){
  seq_along(x)
}

library(grImport)

hourglass <- new("Picture",
paths= list(new("PictureFill",
x=c(0, 1, 0, 1),
y=c(0, 0, 1, 1))),
summary= new("PictureSummary",
numPaths=1,
xscale=c(0, 1),
yscale=c(0, 1)))

## bare bones edit of theme_text()
my_axis <- function () 
{
    structure(function(label, x = 0.5, y = 0.5, default.units = "npc", ...) {
      cols <- mapping(label)

      symbolsGrob(hourglass, x, 0*x + unit(0.5, "npc"),
                  use.gc=FALSE,size=unit(5,"mm"), gp=gpar(fill=cols))

    }, class = "theme", type = "custom", call = match.call())
}

qplot(1:12, rnorm(12)) +
  opts( axis.text.x = my_axis(), axis.ticks.margin = unit(0.5, "cm"))

screenshot

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • I'm not sure one can (elegantly) use different symbol shapes at each location. – baptiste Jan 18 '12 at 08:04
  • I really like this approach, because it keeps the plot relatively self-contained, and should even work with ggsave()! I can confirm it works with multi-panel plots, BUT ... I really do need to have a different picture at each axis tick. Any ideas? – Jonathan Whiteley Jan 19 '12 at 02:48
  • ask Paul Murrell? I haven't seen examples of `grid.symbols` with multiple pictures. You could write an ugly hack to "vectorize" `grid.symbols` with respect to the picture argument using essentially a for loop (for an example, see `drawDetails.pattern` in package `gridExtra`) – baptiste Jan 19 '12 at 03:43
  • I need hourglass at position x=1 only. How can i implement it? – Manish Jul 11 '12 at 04:05