3

Sometimes I would like to use a mouse to draw a circle or squiggly shape around my plotted points to select these points specifically. Has anyone built functionality to do this yet? Perhaps something requiring Tcl/tk?

cylondude
  • 1,816
  • 1
  • 22
  • 55
  • theoretically possible in ggplot but (afaik) has not been implemented. – rawr May 13 '14 at 23:38
  • expecting a single function that can work in both base and ggplot graphics is a little ambitious (not that the solution below can't potentially be improved on ...) – Ben Bolker May 13 '14 at 23:57
  • by the way, this is an interesting question, but it isn't really solving a particular programming problem -- it's more "I would like this, can someone write it for me"? – Ben Bolker May 13 '14 at 23:57
  • 1
    I stand corrected: see `gglocator` http://cran.r-project.org/web/packages/ggmap/ggmap.pdf; apparently it was implemented via a stackoverflow thread http://stackoverflow.com/questions/9450873/locator-equivalent-in-ggplot2-for-maps?rq=1. Implementation is left as an exercise to the reader. – rawr May 14 '14 at 22:51
  • @BenBolker Can't say that I wouldn't like someone to write it for me but my attempt to write it myself runs into wall pretty quickly. Please reread the bounty where I say OR instead of AND. – cylondude May 14 '14 at 23:15
  • The problem I have is that I would like to quickly draw a squiggly shape around points that I've plotted to explore them more thoroughly. If I could create an object by drawing on my existing plots it would let me subset these points spatially. I could do this through clicking many times with gglocator/locator but it would be clunky. If there would be a way to simulate many clicks from holding mouse button and letting go like a lasso function in graphical software that would be ideal. Can you suggest an alternative method to this kind of data exploration that would work graphically? – cylondude May 14 '14 at 23:28
  • I don't think this is going to be easy -- R doesn't easily allow access to this kind of low-level mouse information. You're looking for a graphics device that allows access to a mouse handler, e.g. the [gWidgetsWWW](http://cran.at.r-project.org/web/packages/gWidgetsWWW/index.html) package (maybe??) – Ben Bolker May 15 '14 at 00:26
  • The R package `RIGHT` alluded to these capabilities https://www.youtube.com/watch?feature=player_embedded&v=thlwjYFC_yY . The selection used box boundary rather then freehand however. The package is on CRAN http://cran.r-project.org/web/packages/RIGHT/index.html . – jdharrison May 15 '14 at 01:08
  • that is not the functionality that is described in the question – rawr May 16 '14 at 02:18
  • Sorry if I was not clear in my original question but I don't see what I left out. – cylondude May 16 '14 at 16:56
  • the fact that your comments and explanations are longer than your original question should be a clue – rawr May 16 '14 at 22:00
  • Well after I put the bounty up and responded to the shotgun approach to snark from Ben Bolker, I think people chimed in with some good leads on solving my problem (thanks for the input on gglocator. I was already familiar though). So I see it as a positive step towards what I'm looking for. – cylondude May 16 '14 at 22:29
  • everything he said was accurate. this is a programming site, not a recommend me a tool site. questions like this are usually put on hold – rawr May 17 '14 at 03:44

1 Answers1

6

You could take advantage of locator, and then use the coordinates to put into a circle drawing function like from plotrix. Then put it into a function for ease of use:

plot(rnorm(100))
click.shape('circle', border = 'red', col = NA)

enter image description here

click.shape <- function(shape = c('circle', 'arrow', 'rect', 'cyl', 'line', 'poly'),
                        corners = 3L, ...) {
  shape  <- match.arg(shape)
  coords <- if (shape %in% 'poly')
    locator(as.integer(corners)) else unlist(locator(2L))

  ARROW <- function(...) {
    arrows(coords[1L], coords[3L], coords[2L], coords[4L], ...)
  }
  CIRCLE <- function(...) {
    require(plotrix)
    rad <- sqrt(((coords[2L] - coords[1L]) ^ 2) + ((coords[4L] - coords[3L]) ^ 2))
    draw.circle(coords[1L], coords[3L], radius = rad, ...)
  }
  CYL <- function(...) {
    require(plotrix)
    cylindrect(coords[1L], coords[3L], coords[2L], coords[4L], ...)
  }
  LINE <- function(...) {
    segments(coords[1L], coords[3L], coords[2L], coords[4L], ...)
  }
  POLY <- function(...) {
    polygon(coords, ...)
  }
  RECT <- function(...) {
    rect(coords[1L], coords[3L], coords[2L], coords[4L], ...)
  }

  suppressWarnings(
    switch(shape, arrow = ARROW(...), circle = CIRCLE(...), cyl = CYL(...),
           line = LINE(...), poly = POLY(...), rect = RECT(...),
           stop('Invalid shape'))
  )
}

Another option which I haven't had time lately to expand

set.seed(1618)
x <- runif(10)
y <- rnorm(10, mean = 5)

par(mfrow = c(1, 2))
plot(x, y, xlab = 'mean', ylab = 'sd')

zoomin(x, y)
## ESC to quit

enter image description here

code for zoomin

zoomin <- function(x, y, ...) {

  op <- par(no.readonly = TRUE)
  on.exit(par(op))

  ans <- identify(x, y, n = 1, plot = FALSE, ...)

  zoom <- function (x, y, xlim, ylim, xd, yd) {

    rxlim <- x + c(-1, 1) * (diff(range(xd)) / 20)
    rylim <- y + c(-1, 1) * (diff(range(yd)) / 20)

    par(mfrow = c(1, 2))
    plot(xd, yd, xlab = 'mean', ylab = 'sd')

    xext <- yext <- rxext <- ryext <- 0

    if (par('xaxs') == 'r') {
      xext <- diff(xlim) * 0.04
      rxext <- diff(rxlim) * 0.04
    }
    if (par('yaxs') == 'r') {
      yext <- diff(ylim) * 0.04
      ryext <- diff(rylim) * 0.04
    }

    rect(rxlim[1] - rxext, rylim[1] - ryext, rxlim[2] + rxext, rylim[2] + ryext)
    xylim <- par('usr')
    xypin <- par('pin')

    rxi0 <- xypin[1] * (xylim[2] - (rxlim[1] - rxext)) / diff(xylim[1:2])
    rxi1 <- xypin[1] * (xylim[2] - (rxlim[2] + rxext)) / diff(xylim[1:2])
    y01i <- xypin[2] * (xylim[4] - (rylim[2] + ryext)) / diff(xylim[3:4])
    y02i <- xypin[2] * ((rylim[1] - ryext) - xylim[3]) / diff(xylim[3:4])
    mu <- x

    curve(dnorm(x, mean = mu, sd = y), from = -4 * y + mu, to = 4 * y + mu, 
          xlab = paste('mean:', round(mu, 2), ', sd: ', round(y, 2)), ylab = '')

    xypin <- par('pin')
    par(xpd = NA)
    xylim <- par('usr')
    xymai <- par('mai')

    x0 <- xylim[1] - diff(xylim[1:2]) * (xymai[2] + xymai[4] + rxi0)/xypin[1]
    x1 <- xylim[1] - diff(xylim[1:2]) * (xymai[2] + xymai[4] + rxi1)/xypin[1]
    y01 <- xylim[4] - diff(xylim[3:4]) * y01i/xypin[2]
    y02 <- xylim[3] + diff(xylim[3:4]) * y02i/xypin[2]

    par(xpd = TRUE)

    xend <- xylim[1] - diff(xylim[1:2]) * xymai[2] / (2 * xypin[1])
    xprop0 <- (xylim[1] - xend) / (xylim[1] - x0)
    xprop1 <- (xylim[2] - xend) / (xylim[2] - x1)
    par(xpd = NA)
    segments(c(x0, x0, x1, x1), 
             c(y01, y02, y01, y02), 
             c(xend, xend, xend, xend), 
             c(xylim[4] - (xylim[4] - y01) * xprop0, 
               xylim[3] + (y02 - xylim[3]) * xprop0, 
               xylim[4] - (xylim[4] - y01) * xprop1, 
               xylim[3] + (y02 - xylim[3]) * xprop1))
    par(mfg = c(1, 1))

    plot(xd, yd, xlab = 'mean', ylab = 'sd')
  }

  if(length(ans)) {
    zoom(x[ans], y[ans], range(x), range(y), x, y)
    points(x[ans], y[ans], pch = 19)
    zoomin(x, y)
  }
}
rawr
  • 20,481
  • 4
  • 44
  • 78