Get width of plot area in ggplot2 - r

Is there any way to get the width of the plot area in the grid window? It grows or shrinks, for instance, if plot.margin is changed or if the y-axis labels' font-size is increased. Is is hidden somewhere in str(p)?
Any size measure would work. I need to be able to measure the relative change in the width of the plot area in different scenarios such as change of y-axis labels' font-size.
df = data.frame(x = (1:3),One=c(12, 8, 13),Two=c(13, 7, 11),Three=c(11, 9, 11))
df.melt = melt(df, id.vars="x")
p = ggplot(df.melt, aes(x=x, y=value, color=variable)) +
geom_line() +
coord_cartesian(xlim=c(min(df.melt$x),max(df.melt$x))) +
theme(legend.position="none", plot.margin = unit(c(1, 4, 1, 1), "cm"))
p
UPDATE – To clarify: Please help me calculate a/b.
p = ggplot(df.melt, aes(x=x, y=value, color=variable)) +
geom_line() + coord_cartesian(xlim=c(min(df.melt$x),max(df.melt$x))) +
theme(legend.position="none")
p1 = p + theme(plot.margin=unit(c(1,1,1,1),"cm"), axis.text.y=element_text(size=10))
p2 = p + theme(plot.margin=unit(c(1,1,1,2),"cm"), axis.text.y=element_text(size=30))
grid.arrange(p1, p2, ncol=2)

The plot in ggplot2 uses grid graphics. A graphical scene that has been produced
using the grid graphics package consists of grobs and viewports.
You can use gridDebug package for the inspection of the grobs.
showGrob show the locations and names of the grobs used to draw the scene
showGrob()
Get the gpath of the grob
sceneListing <- grid.ls(viewports=T, print=FALSE)
do.call("cbind", sceneListing)
name gPath
[1,] "ROOT" ""
[2,] "GRID.gTableParent.45019" ""
[3,] "background.1-5-6-1" "GRID.gTableParent.45019"
[4,] "spacer.4-3-4-3" "GRID.gTableParent.45019"
[5,] "panel.3-4-3-4" "GRID.gTableParent.45019"
[6,] "grill.gTree.44997" "GRID.gTableParent.45019::panel.3-4-3-4"
Retrieve the gorb
h <- grid.get(gPath="GRID.gTableParent.45019")
get h properties (e.g)
h$layoutvp$width
Application:
grid.get('x',grep=TRUE,global=T)
(polyline[panel.grid.minor.x.polyline.21899], polyline[panel.grid.major.x.polyline.21903], gTableChild[axis-l.3-3-3-3], gTableChild[axis-b.4-4-4-4], gTableChild[xlab.5-4-5-4])
> grid.get('x',grep=TRUE,global=T)[[3]]
gTableChild[axis-l.3-3-3-3]
> xx <- grid.get('x',grep=TRUE,global=T)[[3]]
> grobWidth(xx)
[1] sum(1grobwidth, 0.15cm+0.1cm)

This intrigued me enough to look into it deeper. I was hoping that the grid.ls function would give the information to navigate to the correct viewports to get the information, but for your example there are a bunch of the steps that get replaced with '...' and I could not see how to change that to give something that is easily worked with. However using grid.ls or other tools you can see the names of the different viewports. The viewports of interest are both named 'panel.3-4-3-4' for your example, below is some code that will navigate to the 1st, find the width in inches, navigate to the second and find the width of that one in inches.
grid.ls(view=TRUE,grob=FALSE)
current.vpTree()
seekViewport('panel.3-4-3-4')
a <- convertWidth(unit(1,'npc'), 'inch', TRUE)
popViewport(1)
seekViewport('panel.3-4-3-4')
b <- convertWidth(unit(1,'npc'), 'inch', TRUE)
a/b
I could not figure out an easy way to get to the second panel without poping the first one. This works and gives the information that you need, unfortunately since it pops the 1st panel off the list you cannot go back to it and find additional information or modify it. But this does give the info you asked for that could be used in future plots.
Maybe someone else knows how to navigate to the second panel without popping the first, or getting the full vpPath of each of them to navigate directly.

This answer is mainly in reply to comments by #java_xof. The reply is too long and includes code so it will not fit in a comment. However, it may help with the original question as well (or at least give a starting place).
Here is a function and some code using it (it requires the tcltk and tkrplot packages):
library(ggplot2)
library(tkrplot)
TkPlotLocations <- function(FUN) {
require(tkrplot)
cl <- substitute(FUN)
replot <- function() eval(cl)
tt <- tktoplevel()
img <- tkrplot(tt, replot, vscale=1.5, hscale=1.5)
tkpack(img)
tkpack(xfr <- tkframe(tt), side='left')
tkpack(yfr <- tkframe(tt), side='left')
xndc <- tclVar()
yndc <- tclVar()
xin <- tclVar()
yin <- tclVar()
tkgrid(tklabel(xfr, text='x ndc'), tklabel(xfr, textvariable=xndc))
tkgrid(tklabel(yfr, text='y ndc'), tklabel(yfr, textvariable=yndc))
tkgrid(tklabel(xfr, text='x inch'), tklabel(xfr, textvariable=xin))
tkgrid(tklabel(yfr, text='y inch'), tklabel(yfr, textvariable=yin))
iw <- as.numeric(tcl("image","width", tkcget(img, "-image")))
ih <- as.numeric(tcl("image","height",tkcget(img, "-image")))
cc <- function(x,y) {
x <- (as.real(x)-1)/iw
y <- 1-(as.real(y)-1)/ih
c(x,y)
}
mm <- function(x, y) {
xy <- cc(x,y)
tclvalue(xndc) <- xy[1]
tclvalue(yndc) <- xy[2]
tclvalue(xin) <- grconvertX(xy[1], from='ndc', to='inches')
tclvalue(yin) <- grconvertY(xy[2], from='ndc', to='inches')
}
tkbind( img, "<Motion>", mm)
invisible()
}
x <- runif(25)
y <- rnorm(25, x, 0.25)
plot(x,y)
par()$pin
par()$plt
TkPlotLocations(plot(x,y))
qplot(x,y)
par()$pin
par()$plt
TkPlotLocations(print(qplot(x,y)))
qplot(x,y) + xlab('Multi\nline\nx\nlabel')
par()$pin
par()$plt
TkPlotLocations(print(qplot(x,y) + xlab('Multi\nline\nx\nlabel')))
Defining the above function, then running the following lines will produce 3 plots of the same random data. You can see that the results of par()$pin and par()$plt (and other parameters) are exactly the same for the 3 plots even though the plotting regions differ in the plots.
There will also be 3 new windows that have popped up, in the windows you can move the mouse pointer over the graph and at the bottom of the window you will see the current location of the pointer in normalized device coordinates and in inches (both from the bottom left corner of the device region). You can hover the mouse pointer over the corners of the graph (or any other part) to see the values and compare between the 3 graphs.
This may be enough to answer at least part of the original question (just not programatically, which would be more useful). The functon can be modified to print out other measurements as well. I may expand this and include it in a package in the future if others would be interested.

Related

Is it possible to plot images in a ggplot2 plot, that don't get distorted when you save to any non-standard aspect ratio?

I'm looking for any solution to this problem, regardless of packages used.
The problem at hand is that plotted images get distorted when you save them using ggsave. Let me give an example:
image_links = data.frame(id = c(1,2,3,4,5),
image = c("https://cdn.shopify.com/s/files/1/1061/1924/products/Smiling_Emoji_with_Eyes_Opened_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Smiling_Emoji_with_Smiling_Eyes_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Hushed_Face_Emoji_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Disappointed_but_Relieved_Face_Emoji_large.png",
"https://cdn.shopify.com/s/files/1/1061/1924/products/Expressionless_Face_Emoji_large.png"))
mydata = data.frame(x = rnorm(100, mean = 50, sd = 20),
y = rnorm(100, mean = 50, sd = 5),
id = rep(c(1,2,3,4,5), 20))
mydata$y = mydata$y - 10*mydata$id
mydata = mydata %>% left_join(image_links, by='id')
g <- ggplot(mydata) + geom_image(aes(x=x, y=y, image=image), size=0.05)
ggsave(g, filename='[INSERT PATH HERE].png', width=width, height=height, dpi=300)
This works fine:
The problem arises when you adjust the width and height parameters of ggsave, for instance because you want the x and y-axis to be in the correct proportion:
width = (max(mydata$x) - min(mydata$x))/10
height = (max(mydata$y) - min(mydata$y))/10
ggsave(g, filename='[INSERT PATH HERE].png', width = width, height=height, dpi=300)
The x and y-axis are now fine, but the images are distorted:
This happens in ANY situation where you plot an image but the width/height aspect ratio is different than what was the original aspect ratio of the image you want to add.
I'm looking for any solution to this problem, not necessarily restricted to ggimage. It seems very weird to me that you can't properly add images to a ggplot, as I image it's very common for people to want to do that.
I don't know a lot about ggsave, but this seems like an issue related to relative versus absolute units. Probably the geom_image() calculates positions relative to the axes, which get distorted when the axes get resized (such as within ggsave). For example:
ggplot(mydata) + geom_image(aes(x=x, y=y, image=image), size=0.05)
Can look like:
Or can look like:
Depending on the device window that I can resize at will.
There are two ways I can see this getting fixed, both of which will involve re-calculating the sizes of the rasters at drawtime. The easier fix will be the one below.
# Get plot
g <- ggplot(mydata) + geom_image(aes(x=x, y=y, image=image), size=0.05)
# Convert to gtable
gt <- ggplotGrob(g)
# Get the imagegrobs, correct slots found by trial and error
imagegrobs <- gt$grobs[[6]]$children[[3]]$children
# Re-class them to a custom, made-up class
imagegrobs <- lapply(imagegrobs, function(image) {
class(image) <- c("fixasp_raster", class(image))
image
})
# Put them back into the gtable
gt$grobs[[6]]$children[[3]]$children <- imagegrobs
So now that we have a custom class for these images, we can write a piece of code that gets executed at the time of drawing by writing a method for our class using the S3 generic makeContent from the grid package.
library(grid)
makeContent.fixasp_raster <- function(x) {
# Convert from relative units to absolute units
h <- convertHeight(x$height, "cm", valueOnly = TRUE)
w <- convertWidth(x$width, "cm", valueOnly = TRUE)
# Decide how the units should be equal
x$height <- x$width <- unit(sqrt(h * w), "cm")
x
}
Note that taking the square root of the product is improvised, I don't know if this is the optimal procedure.
When we plot the data now, we'll have a consistent size of the images, regardless of the aspect ratio:
grid.newpage(); grid.draw(gt)
The second way to fix this is to file an issue in the github page of the ggimage package, motivating your use case and convice them to implement something that adresses your concerns. If they want, they could make a fix at the ggproto level, so that you don't have dabble with gtables.
#teunbrand's answer has been implemented in dev version 0.2.4 of ggimage. You can install the latest dev version like this:
setRepositories(ind=1:2)
## install.packages("devtools")
devtools::install_github("GuangchuangYu/ggimage")
This should fix the aspect ratio issues.

In R, how can I tell if the scales on a ggplot object are log or linear?

I have many ggplot objects where I wish to print some text (varies from plot to plot) in the same relative position on each plot, regardless of scale. What I have come up with to make it simple is to
define a rescale function (call it sx) to take the relative position I want and return that position on the plot's x axis.
sx <- function(pct, range=xr){
position <- range[1] + pct*(range[2]-range[1])
}
make the plot without the text (call it plt)
Use the ggplot_build function to find the x scale's range
xr <- ggplot_build(plt)$layout$panel_params[[1]]$x.range
Then add the text to the plot
plt <- plt + annotate("text", x=sx(0.95), ....)
This works well for me, though I'm sure there are other solutions folks have derived. I like the solution because I only need to add one step (step 3) to each plot. And it's a simple modification to the annotate command (x goes to sx(x)).
If someone has a suggestion for a better method I'd like to hear about it. There is one thing about my solution though that gives me a little trouble and I'm asking for a little help:
My problem is that I need a separate function for log scales, (call it lx). It's a bit of a pain because every time I want to change the scale I need to modify the annotate commands (change sx to lx) and occasionally there are many. This could easily be solved in the sx function if there was a way to tell what the type of scale was. For instance, is there a parameter in ggplot_build objects that describe the log/lin nature of the scale? That seems to be the best place to find it (that's where I'm pulling the scale's range) but I've looked and can not figure it out. If there was, then I could add a command to step 3 above to define the scale type, and add a tag to the sx function in step 1. That would save me some tedious work.
So, just to reiterate: does anyone know how to tell the scaling (type of scale: log or linear) of a ggplot object? such as using the ggplot_build command's object?
Suppose we have a list of pre-build plots:
linear <- ggplot(iris, aes(Sepal.Width, Sepal.Length, colour = Species)) +
geom_point()
log <- linear + scale_y_log10()
linear <- ggplot_build(linear)
log <- ggplot_build(log)
plotlist <- list(a = linear, b = log)
We can grab information about their position scales in the following way:
out <- lapply(names(plotlist), function(i) {
# Grab plot, panel parameters and scales
plot <- plotlist[[i]]
params <- plot$layout$panel_params[[1]]
scales <- plot$plot$scales$scales
# Only keep (continuous) position scales
keep <- vapply(scales, function(x) {
inherits(x, "ScaleContinuousPosition")
}, logical(1))
scales <- scales[keep]
# Grab relevant transformations
out <- lapply(scales, function(scale) {
data.frame(position = scale$aesthetics[1],
# And now for the actual question:
transformation = scale$trans$name,
plot = i)
})
out <- do.call(rbind, out)
# Grab relevant ranges
ranges <- params[paste0(out$position, ".range")]
out$min <- sapply(ranges, `[`, 1)
out$max <- sapply(ranges, `[`, 2)
out
})
out <- do.call(rbind, out)
Which will give us:
out
position transformation plot min max
1 x identity a 1.8800000 4.520000
2 y identity a 4.1200000 8.080000
3 y log-10 b 0.6202605 0.910835
4 x identity b 1.8800000 4.520000
Or if you prefer a straightforward answer:
log$plot$scales$scales[[1]]$trans$name
[1] "log-10"

Show a grid of equally spaced images with layout() and display()

I want to create a grid of 9 images with equal spacing between them. Until now I managed to get something to work with par() and layout().
layout(matrix(1:9, widths=rep(lcm(4),9), heights=rep(lcm(3),9))
for (i in 1:9) {
imNew <- readImage(img_ar[i])
EBImage::display(imNew, method="raster")
}
gives me this
while using par:
layout(matrix(1:9, widths=rep(lcm(4),9), heights=rep(lcm(3),9))
for (i in 1:9) {
imNew <- readImage(img_ar[i])
EBImage::display(imNew,method="raster")
}
gives me this:
I also tried different options for par() like oma, mai and mar but these didn't change the spacing in between the individual images. What I like to have is an equal distance between the individual images like this:
Could anyone help me please?
You can use your original idea to separate images by including additional empty rows and columns in your layout, as in the following example. Note, however, that in order to achieve equal horizontal and vertical spacing you will need to tweak the device dimensions.
library(EBImage)
# load sample image
img <- readImage(system.file("images", "sample-color.png", package="EBImage"))
# downsample to reduce memory consumption and for faster processing
img <- resize(img, 192)
# build the layout matrix with additional separating cells
nx <- 4 # number of images in a row
ny <- 3 # number of images in a column
cols <- 2*nx-1
rows <- 2*ny-1
m <- matrix(0, cols, rows)
m[2*(1:nx)-1, 2*(1:ny)-1] <- 1:(nx*ny)
m <- t(m)
# relative spacing
pad <- .1
w <- rep(1, cols)
w[!(1:cols)%%2] <- pad
h <- rep(1, rows)
h[!(1:rows)%%2] <- pad * dim(img)[1L]/dim(img)[2L]
layout(m, widths = w, heights = h)
layout.show(nx*ny)
for (i in 1:(nx*ny)) {
display(img, method="raster")
}
A better approach is to use display() on an image stack. Then individual frames can be displayed arranged in a grid by setting all=TRUE.
## construct sample image stack
img_stack <- combine(replicate(nx*ny, img, simplify=FALSE))
display(img_stack, method="raster", all=TRUE)
Unfortunately, until recently it was not possible to adjust the spacing between the frames. To facilitate this, I've added an argument to display() specifying the spacing. Currently this new feature is available in the development version of EBImage, which can be obtained either from GitHub devtools::install_github("aoles/EBImage"), or from the Bioconductor devel branch.
The spacing can be provided as a fraction of frame dimensions (positive numbers <1) or in pixels (numbers >=1). Additionally, you can have different horizontal and vertical separation by providing a vector, e.g. spacing = (10, 20) will separate the columns by 10px, and the rows by 20px.
display(img_stack, method="raster", all=TRUE, spacing=.1)
Furthermore, you can add a margin around the grid, and control its layout by nx. The background can be set through bg passed to par().
Finally, a completely different way of drawing images in a grid is to construct one big composite image with tile. This approach might be useful, for example, when saving the result to a file. Note the additional border around the whole grid.
## tiled composite image
img_tiles <- tile(img_stack, nx=nx, lwd=20, fg.col="white", bg.col="white")
display(img_tiles, method="raster")
The ggplot2 way with marrangeGrob() from gridExtra:
library(RCurl)
library(png)
library(grid)
library(gridExtra)
library(ggplot2)
# read a few MNIST images
urls <- c('https://i.imgur.com/TEbkTqu.png', 'https://i.imgur.com/tnsjMFJ.png', 'https://i.imgur.com/VUZgJBs.png', 'https://i.imgur.com/FZ28d3w.png')
imgs <- list()
for (i in 1:length(urls)) {
imgs[[i]] <- readPNG(getURLContent(urls[i]))
}
# plot grid and show images
plist <- list()
for (i in 1:length(imgs)) {
plist[[i]] <- ggplot() +
annotation_custom(rasterGrob(imgs[[i]], interpolate=TRUE), xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) +
labs(x = NULL, y = NULL) +
guides(x = "none", y = "none") +
theme_bw() +
theme(legend.position = "none", panel.border = element_blank(), panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
}
marrangeGrob(plist, nrow=2, ncol=2, respect=TRUE)
(Demonstrated here in the shiny app too)

R ggplot: geom_tile lines in pdf output

I'm constructing a plot that uses geom_tile and then outputting it to .pdf (using pdf("filename",...)). However, when I do, the .pdf result has tiny lines (striations, as one person put it) running through it. I've attached an image showing the problem.
Googling let to this thread, but the only real advice in there was to try passing size=0 to geom_tile, which I did with no effect. Any suggestions on how I can fix these? I'd like to use this as a figure in a paper, but it's not going to work like this.
Minimal code:
require(ggplot2)
require(scales)
require(reshape)
volcano3d <- melt(volcano)
names(volcano3d) <- c("x", "y", "z")
v <- ggplot(volcano3d, aes(x, y, z = z))
pdf("mew.pdf")
print(v + geom_tile(aes(fill=z)) + stat_contour(size=2) + scale_fill_gradient("z"))
This happens because the default colour of the tiles in geom_tile seems to be white.
To fix this, you need to map the colour to z in the same way as fill.
print(v +
geom_tile(aes(fill=z, colour=z), size=1) +
stat_contour(size=2) +
scale_fill_gradient("z")
)
Try to use geom_raster:
pdf("mew.pdf")
print(v + geom_raster(aes(fill=z)) + stat_contour(size=2) + scale_fill_gradient("z"))
dev.off()
good quality in my environment.
I cannot reproduce the problem on my computer (Windows 7), but I remember it was a problem discussed on the list for certain configurations. Brian Ripley (if I remember) recommended
CairoPDF("mew.pdf") # Package Cairo
to get around this
In the interests of skinning this cat, and going into waaay too much detail, this code decomposes the R image into a mesh of quads (as used by rgl), and then shows the difference between a raster plot and a "tile" or "rect" plot.
library(raster)
im <- raster::raster(volcano)
## this is the image in rgl corner-vertex form
msh <- quadmesh::quadmesh(im)
## manual labour for colour scaling
dif <- diff(range(values(im)))
mn <- min(values(im))
scl <- function(x) (x - mn)/dif
This the the traditional R 'image', which draws a little tile or 'rect()' for every pixel.
list_image <- list(x = xFromCol(im), y = rev(yFromRow(im)), z = t(as.matrix(im)[nrow(im):1, ]))
image(list_image)
It's slow, and though it calls the source of 'rect()' under the hood, we can't also set the border colour. Use 'useRaster = TRUE' to use 'rasterImage' for more efficient drawing time, control over interpolation, and ultimately - file size.
Now let's plot the image again, but by explicitly calling rect for every pixel. ('quadmesh' probably not the easiest way to demonstrate, it's just fresh in my mind).
## worker function to plot rect from vertex index
rectfun <- function(x, vb, ...) rect(vb[1, x[1]], vb[2,x[1]], vb[1,x[3]], vb[2,x[3]], ...)
## draw just the borders on the original, traditional image
apply(msh$ib, 2, rectfun, msh$vb, border = "white")
Now try again with 'rect'.
## redraw the entire image, with rect calls
##(not efficient, but essentially the same as what image does with useRaster = FALSE)
cols <- heat.colors(12)
## just to clear the plot, and maintain the plot space
image(im, col = "black")
for (i in seq(ncol(msh$ib))) {
rectfun(msh$ib[,i], msh$vb, col = cols[scl(im[i]) * (length(cols)-1) + 1], border = "dodgerblue")
}

Annotate ggplot2 graphs using tikzAnnotate in tikzDevice

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()
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:
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.
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()
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!

Resources