Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Annotate ggplot2 graphs using tikzAnnotate in tikzDevice

Tags:

r

ggplot2

I would like to use tikzDevice to include annotated ggplot2 graphs in a Latex document.

tikzAnnotate help has an example of how to use it with base graphics, but how to use it with a grid-based plotting package like ggplot2? The challenge seems to be the positioning of the tikz node.

playwith package has a function convertToDevicePixels (http://code.google.com/p/playwith/source/browse/trunk/R/gridwork.R) that seems to be similar to grconvertX/grconvertY, but I am unable to get this to work either.

Would appreciate any pointers on how to proceed.

tikzAnnotate example using base graphics

library(tikzDevice)
library(ggplot2)
options(tikzLatexPackages = c(getOption('tikzLatexPackages'),
                "\\usetikzlibrary{shapes.arrows}"))
tikz(standAlone=TRUE)

print(plot(15:20, 5:10))
#print(qplot(15:20, 5:10))

x <- grconvertX(17,,'device')
y <- grconvertY(7,,'device')
#px <- playwith::convertToDevicePixels(17, 7)
#x <- px$x
#y <- px$y

tikzAnnotate(paste('\\node[single arrow,anchor=tip,draw,fill=green] at (',
                x,',',y,') {Look over here!};'))
dev.off()

resulting image

like image 612
learnr Avatar asked Feb 16 '11 13:02

learnr


People also ask

How do you annotate a graph in R?

If you want to annotate your plot or figure with labels, there are two basic options: text() will allow you to add labels to the plot region, and mtext() will allow you to add labels to the margins. For the plot region, to add labels you need to specify the coordinates and the label.

How do I add a note in ggplot2?

You can use the annotate() function to add text to plots in ggplot2. where: x, y: The (x, y) coordinates where the text should be placed.

Can you filter within ggplot?

ggplot2 allows you to do data manipulation, such as filtering or slicing, within the data argument.

How do you use tikzDevice?

There are two way to use tikzDevice. One way is to produce standalone a tex file and then produce the picture (compile the tex file within your favoriate TeX editor or convert using the R function texi2dvi). Another way is using Yihui's amazing knitr package to integrate a plot into a Rmd file.


2 Answers

Currently, tikzAnnotate only works with base graphics. When tikzAnnotate was first written, the problem with grid graphics was that we needed a way of specifying the x,y coordinates relative to the absolute lower left corner of the device canvas. grid thinks in terms of viewports and for many cases it seems the final coordinate system of the graphic is not known until it is heading to the device by means of the print function.

It would be great to have this functionality, but I could not figure out a way good way to implement it and so the feature got shelved. If anyone has details on a good implementation, feel free to start a discussion on the mailing list (which now has an alternate portal on Google Groups) and it will get on the TODO list.

Even better, implement the functionality and open a pull request to the project on GitHub. This is guaranteed to get the feature into a release over 9000 times faster than if it sits on my TODO list for months.


Update

I have had some time to work on this, and I have come up with a function for converting grid coordinates in the current viewport to absolute device coordinates:

gridToDevice <- function(x = 0, y = 0, units = 'native') {
  # Converts a coordinate pair from the current viewport to an "absolute
  # location" measured in device units from the lower left corner. This is done
  # by first casting to inches in the current viewport and then using the
  # current.transform() matrix to obtain inches in the device canvas.
  x <- convertX(unit(x, units), unitTo = 'inches', valueOnly = TRUE)
  y <- convertY(unit(y, units), unitTo = 'inches', valueOnly = TRUE)

  transCoords <- c(x,y,1) %*% current.transform()
  transCoords <- (transCoords / transCoords[3])

  return(
    # Finally, cast from inches to native device units
    c(
      grconvertX(transCoords[1], from = 'inches', to ='device'),
      grconvertY(transCoords[2], from = 'inches', to ='device')
    )
  )

}

Using this missing piece, one can use tikzAnnotate to mark up a grid or lattice plot:

require(tikzDevice)
require(grid)
options(tikzLatexPackages = c(getOption('tikzLatexPackages'),
                "\\usetikzlibrary{shapes.arrows}"))

tikz(standAlone=TRUE)

xs <- 15:20
ys <- 5:10

pushViewport(plotViewport())
pushViewport(dataViewport(xs,ys))

grobs <- gList(grid.rect(),grid.xaxis(),grid.yaxis(),grid.points(xs, ys))

coords <- gridToDevice(17, 7)
tikzAnnotate(paste('\\node[single arrow,anchor=tip,draw,fill=green,left=1em]',
  'at (', coords[1],',',coords[2],') {Look over here!};'))

dev.off()

This gives the following output:

TikZ Annotation of Grid Graphics


There is still some work to be done, such as:

  • Creation of a "annotation grob" that can be added to grid graphics.

  • Determine how to add such an object to a ggplot.

These features are scheduled to appear in release 0.7 of the tikzDevice.

like image 157
Sharpie Avatar answered Sep 28 '22 19:09

Sharpie


I have made up a small example based on @Andrie's suggestion with geom_text and geom_polygon:

Initializing your data:

df <- structure(list(x = 15:20, y = 5:10), .Names = c("x", "y"), row.names = c(NA, -6L), class = "data.frame")

And the point you are to annotate is the 4th row in the dataset, the text should be: "Look over here!"

point <- df[4,]
ptext <- "Look over here!"

Make a nice arrow calculated from the coords of the point given above:

arrow <- data.frame(
    x = c(point$x-0.1, point$x-0.3, point$x-0.3, point$x-2, point$x-2, point$x-0.3, point$x-0.3, point$x-0.1),
    y = c(point$y, point$y+0.3, point$y+0.2, point$y+0.2, point$y-0.2, point$y-0.2, point$y-0.3, point$y)
)

And also make some calculations for the position of the text:

ptext <- data.frame(label=ptext, x=point$x-1, y=point$y)

No more to do besides plotting:

ggplot(df, aes(x,y)) + geom_point() + geom_polygon(aes(x,y), data=arrow, fill="green") + geom_text(aes(x, y, label=label), ptext) + theme_bw()

example image of annotating ggplot2 with arrow signs

Of course, this is a rather hackish solution, but could be extended:

  • compute the size of arrow based on the x and y ranges,
  • compute the position of the text based on the length of the text (or by the real width of the string with textGrob),
  • define a shape which does not overlaps your points :)

Good luck!

like image 40
daroczig Avatar answered Sep 28 '22 18:09

daroczig