8

Given a ggplot of, for example, points, how would you find out the row of data that a given point corresponded to?

A sample plot:

library(ggplot2)
(p <- ggplot(mtcars, aes(mpg, wt)) +
    geom_point() +
    facet_wrap(~ gear)
)

We can get the grobs that contain points with grid.ls + grid.get.

grob_names <- grid.ls(print = FALSE)$name
point_grob_names <- grob_names[grepl("point", grob_names)]
point_grobs <- lapply(point_grob_names, grid.get)

This last variable contains details of the x-y coordinates, and pointsize, etc. (try unclass(point_grobs[[1]])), but it isn't obvious how I get the row of data in mtcars that each point corresponds to.


To answer kohske's question about why am I doing this, I'm using gridSVG to create an interactive scatterplot. When you roll the mouse over a point, I want to display contextual information. In the mtcars example, I could show a tooltip with the name of the car or other values from that row of the data frame.

My hacky idea so far is to include an id column as an invisible text label:

mtcars$id <- seq_len(nrow(mtcars))
p + geom_text(aes(label = id), colour = NA)

Then traverse the tree of grobs from the point grob to the text grob, and display the row of the dataset indexed by the label.

This is fiddly and not very generalisable. If there's a way to store the id value within the point grob, it would be much cleaner.

Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
  • No easy way. What is your final goal? There may be alternatives. – kohske Jan 23 '12 at 14:11
  • @kohske: Too long for a comment, so I've updated the question to explain what I'm doing. – Richie Cotton Jan 23 '12 at 14:51
  • Very interesting. I understand your purpose. Unfortunately we have no way to access data through grob (even in the next release, probably). But maybe it will be useful to have information about data (especially when `stat = "identity"). I will discuss. – kohske Jan 23 '12 at 15:55
  • and some technical problems is that ggplot2 supports multiple data on one panel. So, `id` may not be sufficient for identifying the data row. – kohske Jan 23 '12 at 16:02

1 Answers1

7

This script generate a SVG file wherein you can interactively annotate the points.

library(ggplot2)
library(gridSVG)

geom_point2 <- function (...) GeomPoint2$new(...)
GeomPoint2 <- proto(GeomPoint, {
  objname <- "point2"
  draw <- function(., data, scales, coordinates, na.rm = FALSE, ...) {
   data <- remove_missing(data, na.rm, c("x", "y", "size", "shape"), 
        name = "geom_point")
    if (empty(data)) 
        return(zeroGrob())
    name <- paste(.$my_name(), data$PANEL[1], sep = ".")
    with(coordinates$transform(data, scales), ggname(name,
        pointsGrob(x, y, size = unit(size, "mm"), pch = shape, 
            gp = gpar(col = alpha(colour, alpha), fill = fill, label = label,
                fontsize = size * .pt))))
  }}
  )

p <- ggplot(mtcars, aes(mpg, wt, label = rownames(mtcars))) + geom_point2() + facet_wrap(~ gear)
print(p)

grob_names <- grid.ls(print = FALSE)$name
point_grob_names <- sort(grob_names[grepl("point", grob_names)])
point_grobs_labels <- lapply(point_grob_names, function(x) grid.get(x)$gp$label)

library(rjson)
jlabel <- toJSON(point_grobs_labels)

grid.text("value", 0.05, 0.05, just = c(0, 0), name = "text_place", gp = gpar(col = "red"))

script <- '
var txt = null;
function f() {
    var id = this.id.match(/geom_point2.([0-9]+)\\.points.*\\.([0-9]+)$/);
    txt.textContent = label[id[1]-1][id[2]-1];
}

window.addEventListener("load",function(){
    var es = document.getElementsByTagName("circle");
    for (i=0; i<es.length; ++i) es[i].addEventListener("mouseover", f, false);

    txt = (document.getElementById("text_place").getElementsByTagName("tspan"))[0];

},false);
'

grid.script(script = script)
grid.script(script = paste("var label = ", jlabel))

gridToSVG()

Do you know some places I can upload SVG file?

kohske
  • 65,572
  • 8
  • 165
  • 155
  • Awesome! I had to remove the `filename = "s.js"` argument to get this to work for me, though. – joran Jan 24 '12 at 01:55
  • I appreciate this is old but I get the error `Error in eval(expr, envir, enclos) : could not find function "proto"`. Is this a versioning issue? – Hugh Feb 18 '14 at 11:16
  • maybe you need `library(proto)`, I didn't test though. – kohske Feb 18 '14 at 12:02
  • 1
    I got the same error, I added `library(proto)`, `library(grid)` and `library(scales)` and replaced `GeomPoint` by `ggplot2:::GeomPoint`, `pointsGrob` with `grid:::pointsGrob`, `zeroGrob` with `ggplot2:::zeroGrob`, which may have helped some, but still end up with `Error: attempt to apply non-function`... – PatrickT May 04 '14 at 17:43
  • No it doesnt work! `Error in proto(GeomPoint, { : object 'GeomPoint' not found Execution halted` – vjain419 Sep 25 '15 at 18:41