locator equivalent in ggplot2 (for maps)
Asked Answered
A

5

28

Note: This question is specific for mapping but I'd like to be able to use it when I plot in a standard Cartesian coordinate system.

I love base graphics but also like ggplot2 for many things. One of my most used base functions for fine tuning a graph is locator(n) but this produces an error in ggplot2.

library(ggplot2) 
county_df <- map_data('county')  #mappings of counties by state
ny <- subset(county_df, region=="new york")   #subset just for NYS
ny$county <- ny$subregion

ggplot(ny, aes(long, lat, group=group)) +  geom_polygon(colour='black', fill=NA)
locator(1)

Now grid.locator() as pointed out to me by Dason on talkstats.com (HERE) can return something. I just don't know how to use that something to get a map coordinate.

> grid.locator()
$x
[1] 286native

$y
[1] 133native

Units didn't seem to help as they are not map coordinates. Maybe I need some sort of conversion.

Thank you in advance.

EDIT: (based on DWin's response)

Dwin's got the right idea but the conversion factor is a little bit off. Help with that would be appreciated. In the example below I have a map with a red dot on it at the coordinates (x = -73 & y = 40.855). I threw Dwin's response into a function to return the coordinates. I would expect the results to be the coordinates I put in but they're not.

Ideas?

require(maps); library(ggplot2); require(grid)

county_df <- map_data('county')  #mappings of counties by state
ny <- subset(county_df, region=="new york")   #subset just for NYS
ny$county <- ny$subregion


NY <- ggplot(ny, aes(long, lat)) +  
          geom_polygon(aes(group=group), colour='black', fill=NA) +
          coord_map() + geom_point(aes(-73, 40.855, colour="red"))
NY  

gglocator <- function(object){
    require(maps); require(grid)
    z <- grid.locator("npc")
    y <- sapply(z, function(x) as.numeric(substring(x, 1, nchar(x))))
    locatedX <- min(object$data$long) + y[1]*diff(range(object$data$long))
    locatedy <- min(object$data$lat)  + y[2]*diff(range(object$data$lat))
    return(c(locatedX, locatedy))
}

#click on the red dot
gglocator(NY)  #I expect the results to be x = -73 & y = 40.855

EDIT 2: (Going off of Baptise's answer)

We're there

NY <- ggplot(ny, aes(long, lat)) +  
          geom_polygon(aes(group=group), colour='black', fill=NA) +
          coord_map() + geom_point(aes(-73, 40.855, colour="red")) +
          scale_x_continuous(expand=c(0,0)) + scale_y_continuous(expand=c(0,0))


NY 
x <- grid.ls()[[1]][grep("panel-", grid.ls()[[1]])] #locate the panel
seekViewport(x)
y <-  grid.locator("npc")
y <- as.numeric(substring(y, 1, nchar(y)-3))

locatedX <- min(NY$data$long) + y[1]*diff(range(NY$data$long))
locatedy <- min(NY$data$lat) + y[2]*diff(range(NY$data$lat))
locatedX; locatedy 

UPDATE: The gglocator function of the ggmap package now contains this functionality.

Alnico answered 26/2, 2012 at 5:41 Comment(1)
like due to changes in ggplot2, ggLocator from ggmap no longer works: github.com/dkahle/ggmap/issues/87Kinesiology
J
7

I get the correct result if I add scale_x_continuous(expand=c(0,0)) + scale_y_continuous(expand=c(0,0)) to the plot, and seekViewport("panel-3-4") before grid.locator()

Jumna answered 26/2, 2012 at 20:8 Comment(9)
I follow the scale_x and y part but the seekViewport I do not. I het the following error: > seekViewport("panel-3-4") Error in grid.Call.graphics(L_downviewport, name$name, strict) : Viewport 'panel-3-4' was not foundAlnico
maybe your plot panel has a different name. Try finding it with grid.ls()Jumna
@batise I found it with grid.ls and have tried to automate the process (I want to make a function out of this) with x <- grid.ls()[[1]][grep("panel-", grid.ls()[[1]])] #locate the panel which I then feed to seekViewport as seekViewport(x). This appears to give the correct latitude but not longitude (though I've only tested it on this one point). It's getting closer.Alnico
that's probably because you use y[2] instead of y[1]Jumna
also, convertUnit might be more recommendable than substring: y <- c(convertUnit(y$x, "npc", valueOnly=TRUE), convertUnit(y$y, "npc", "y", valueOnly=TRUE))Jumna
@What a dope I am (I edited my post to reflect this). That does it. Thank you both to yo baptise and DWin. I'll write up some sort of function and return it back here as an answer but this answer gets the check mark.Alnico
And as far as the convert unit and nchar this can all be avoided with a simple as.numericAlnico
it would be nice to have a more reliable way to do this. I wonder what the naming scheme is for ggplot2 panels, especially when there are facets.Jumna
@baptise I added a function from David Kahle for ggplot locator and thought I'd shareAlnico
H
11

Need to use a unit system that makes sense and save the information in the ggplot object so you can convert from "npc" units to map units:

require(maps)
require(grid)
NY <- ggplot(ny, aes(long, lat, group=group)) +  geom_polygon(colour='black', fill=NA)
 grid.locator("npc")
# clicked in middle of NY State:

#$x
#[1] 0.493649231346082npc
#
#$y
#[1] 0.556430446194226npc
 range(NY$data$long)
#[1] -79.76718 -71.87756
 range(NY$data$lat)
#[1] 40.48520 45.01157
 locatedX <- min(NY$data$long) + 0.493649231346082*diff(range(NY$data$long))
 locatedX
#[1] -75.87247
locatedY <- min(NY$data$lat) +  0.556430446194226*diff(range(NY$data$lat))
locatedY
#[1] 43.00381
Halakah answered 26/2, 2012 at 17:54 Comment(4)
Thank you for your response. Your response makes a lot of sense and I tried it. It results in a conversion factor that's getting us on the right track but not there. Any idea of how to fix it? Please check out my edit in the OP.Alnico
I imagine that for "npc" units to make sense they need to be obtained from within the plot panel viewport. Try ?seekViewport() first to navigate to the viewport, then the units should be related to the data.Jumna
I tried editing from my iPhone but the scroll bars not rendered, soo will need to address later today.Halakah
@DWin baptise took over and solved from you;re answer. Thank you for your help.Alnico
J
7

I get the correct result if I add scale_x_continuous(expand=c(0,0)) + scale_y_continuous(expand=c(0,0)) to the plot, and seekViewport("panel-3-4") before grid.locator()

Jumna answered 26/2, 2012 at 20:8 Comment(9)
I follow the scale_x and y part but the seekViewport I do not. I het the following error: > seekViewport("panel-3-4") Error in grid.Call.graphics(L_downviewport, name$name, strict) : Viewport 'panel-3-4' was not foundAlnico
maybe your plot panel has a different name. Try finding it with grid.ls()Jumna
@batise I found it with grid.ls and have tried to automate the process (I want to make a function out of this) with x <- grid.ls()[[1]][grep("panel-", grid.ls()[[1]])] #locate the panel which I then feed to seekViewport as seekViewport(x). This appears to give the correct latitude but not longitude (though I've only tested it on this one point). It's getting closer.Alnico
that's probably because you use y[2] instead of y[1]Jumna
also, convertUnit might be more recommendable than substring: y <- c(convertUnit(y$x, "npc", valueOnly=TRUE), convertUnit(y$y, "npc", "y", valueOnly=TRUE))Jumna
@What a dope I am (I edited my post to reflect this). That does it. Thank you both to yo baptise and DWin. I'll write up some sort of function and return it back here as an answer but this answer gets the check mark.Alnico
And as far as the convert unit and nchar this can all be avoided with a simple as.numericAlnico
it would be nice to have a more reliable way to do this. I wonder what the naming scheme is for ggplot2 panels, especially when there are facets.Jumna
@baptise I added a function from David Kahle for ggplot locator and thought I'd shareAlnico
A
6

I wrote to the ggplot help list and received a very helpful response from David Kahle who happened to be interested in the same problem. His function is great in that:

1) you don't have to add scale y and scale x to the plot

2) it can find multiple points at once and return them as a data frame

3) it works on any type of ggplot, not just maps

gglocator <- function(n = 1, object = last_plot(), 
    message = FALSE, xexpand = c(.05,0), yexpand = c(.05, 0)){ 

  #compliments of David Kahle
  if(n > 1){
    df <- NULL
    for(k in 1:n){
      df <- rbind(df, gglocator(object = object, message = message, 
        xexpand = xexpand, yexpand = yexpand))
    }
    return(df)
  }

  x <- grid.ls(print = message)[[1]]
  x <- x[ grep("panel-", grid.ls(print=message)[[1]]) ] #locate the panel
  seekViewport(x)
  loc <-  as.numeric(grid.locator("npc"))

  xrng <- with(object, range(data[,deparse(mapping$x)]))
  yrng <- with(object, range(data[,deparse(mapping$y)]))    

  xrng <- expand_range(range = xrng, mul = xexpand[1], add = xexpand[2])
  yrng <- expand_range(range = yrng, mul = yexpand[1], add = yexpand[2])    

  point <- data.frame(xrng[1] + loc[1]*diff(xrng), yrng[1] + loc[2]*diff(yrng))
  names(point) <- with(object, c(deparse(mapping$x), deparse(mapping$y)))
  point
}

#Example 1
require(maps); library(ggplot2); require(grid)
county_df <- map_data('county')  #mappings of counties by state
ny <- subset(county_df, region=="new york")   #subset just for NYS
ny$county <- ny$subregion


NY <- ggplot(ny, aes(long, lat)) +  
          geom_polygon(aes(group=group), colour='black', fill=NA) +
          coord_map() + geom_point(aes(c(-78, -73), c(41, 40.855), 
          colour=c("blue", "red"))) + opts(legend.position = "none") 


NY 
gglocator(2)

#Example 2
df <- data.frame(xvar = 2:10, yvar = 2:10)
ggplot(df, aes(xvar, yvar)) + geom_point() + geom_point(aes(x = 3, y = 6))
gglocator()

UPDATE: The gglocator function of the ggmap package now contains this functionality.

Alnico answered 27/2, 2012 at 18:44 Comment(1)
it's a step forward, yet still not entirely satisfactory: 1) it probably returns incorrect results if the axes are not linear, 2) it probably fails in some way when there are multiple panels/plots on the page.Jumna
A
5

Here's the final results using everything DWin and Baptise gave me wrapped up into a function. I also inquired on the ggplot help list and will report and additional information back here.

require(maps); require(ggplot2); require(grid)

ny <- map_data('county', 'new york')

NY1 <- ggplot(ny, aes(long, lat)) +  
          geom_polygon(aes(group=group), colour='black', fill=NA) +
          coord_map() + geom_point(aes(c(-78, -73), c(41, 40.855), 
          colour=c("blue", "red"))) + opts(legend.position = "none") 

NY <- NY1 + scale_x_continuous(expand=c(0,0)) + 
          scale_y_continuous(expand=c(0,0))
          #the scale x and y have to be added to the plot

NY 

ggmap.loc <- function(object){
    x <- grid.ls()[[1]][grep("panel-", grid.ls()[[1]])] #locate the panel
    seekViewport(x)
    y <-  as.numeric(grid.locator("npc"))
    locatedX <- min(object$data$long) + y[1]*diff(range(object$data$long))
    locatedy <- min(object$data$lat) + y[2]*diff(range(object$data$lat))
    return(c(locatedX, locatedy))
}

ggmap.loc(NY)
Alnico answered 27/2, 2012 at 6:36 Comment(0)
I
4

These posts were very helpful, but it's been a few years so a few things are broken. Here is some new code that works for me. The code to find the correct viewport didn't work, so instead I used current.vpTree() to manually search for the right viewport, then copied that into seekViewport(). Note the viewport for me was 'panel.6-4-6-4' and not the old style panel-*. Finally, I wasn't getting the right answers when rendering in rstudio, instead I had to use x11().

Here is a complete example. Hope this is helpful.

library(ggplot2)
library(grid)

object<-ggplot(dat=data.frame(x=1:5,y=1:5),aes(x=x,y=y)) + 
  geom_point()  +
  scale_x_continuous(expand=c(0,0)) +
  scale_y_continuous(expand=c(0,0))
x11()
print(object)
formatVPTree(current.vpTree()) #https://www.stat.auckland.ac.nz/~paul/useR2015-grid/formatVPTree.R
seekViewport('panel.6-4-6-4')
y <-  as.numeric(grid.locator("npc"))
locatedX <- min(object$data$x) + y[1]*diff(range(object$data$x))
locatedY <- min(object$data$y) + y[2]*diff(range(object$data$y))
locatedX; locatedY
Igraine answered 22/9, 2017 at 19:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.