0

I'm trying to create a basketball shot chart, and I've come to enjoy the look of a lattice shot chart plot, but it seems that I am unable to combine (or, place on top of) it with my ggplot court design. Is there anyway to do this? Or do I have to end up using the court as a raster image in the background (which is already giving me fits due to guessing and checking the image's coordinates)?

Here is a sample for the dat data frame

      location_x location_y points
1         5.5       22.1      2
2         6.0       24.1      2
3         6.1       39.4      2
4        25.3       38.7      0
5         8.0       24.4      2
6         8.8       12.7      0
7         9.0        6.0      0
8        10.6       31.6      0
9         4.2       47.5      0
10        4.3        8.9      2

EDIT: Below is my code, thanks to the help of the post: Using both color and size attributes in Hexagon Binning (ggplot2). The geom_path line is what's being used to draw the court. The error that I am currently getting is:

Error: Don't know how to add o to a plot

dat = mod_shots[mod_shots$player_id == 398068, ]

lattice_plot = xyplot(location_x~location_y, data = dat, panel = function(x,y,...)
{
  hbin = hexbin(dat$location_y, dat$location_x, xbins=40, IDs=TRUE)
  mtrans = hexTapply(hbin, dat$points, sum, na.rm=TRUE)
  cols = rev(heat.colors(7))
  grid.hexagons(hbin, style='lattice',
                minarea=0.1,maxarea=3,
                border="NA",
                pen=cols[mtrans+1])
})

ggplot() + geom_path(data = court_points,
          aes(x = x, y = y, z = NULL, group = desc, linetype = dash),
          color = "#000004") + lattice_plot
Community
  • 1
  • 1
Agrosel
  • 489
  • 3
  • 15
  • 4
    A small, reproducible example would make this question much better. – Gregor Thomas May 31 '16 at 18:27
  • @Gregor it has been edited – Agrosel May 31 '16 at 18:39
  • 2
    Better, still not reproducible. We don't have your `mod_shots` data, so we don't have `dat`... sharing `dput(dat)` or (if that's too big) `dput(droplevels(head(dat, 10)))` would make your data available for copy/pasting. – Gregor Thomas May 31 '16 at 18:55
  • @Gregor sorry about that, I put in sample rows for `dat` – Agrosel May 31 '16 at 19:11
  • as a dubious hack, if you make sure that the panel limits are exactly the same, you could place the lattice panel grob as an annotation_custom layer in ggplot2. – baptiste May 31 '16 at 20:30
  • I'll give this a try, thanks @baptiste – Agrosel May 31 '16 at 20:38
  • [Maybe relevant](https://github.com/toddwschneider/ballr)? – Henrik May 31 '16 at 20:42
  • @Henrik I have actually been looking a lot at Todd W. Schneider's work, however he has a really different approach to calculate the shot volumes for the different locations. With that said, I may end up scrapping this xyplot and looking more at what he does. – Agrosel May 31 '16 at 20:51
  • So @baptiste I tried to put the lattice_plot variable as in the annotation_custom grob arguments as shown: `ggplot(dat, aes(location_x, location_y)) + geom_blank() + theme_bw() + annotation_custom(grob = lattice_plot)` But I get this error: `Error in UseMethod("validGrob") : no applicable method for 'validGrob' applied to an object of class "trellis"` Any ideas? – Agrosel May 31 '16 at 21:17

1 Answers1

1

Here's a dubious strategy: place the lattice panel as an annotation layer inside ggplot. I've made the lattice layer semi-transparent for illustration (burn blending mode of sorts).

enter image description here

library(grid) 
library(lattice) 
library(ggplot2) 

ghost_grob <- function(m, ...){
  grob(m=m, cl="ghost") 
}

preDrawDetails.ghost <- function(x){
  pushViewport(viewport(xscale = c(0, 1 + ncol(x$m)), yscale = 
                          c(0, 1 + nrow(x$m))))
}
postDrawDetails.ghost <- function(x){
  upViewport(1)
}

drawDetails.ghost <- function(x, recording){
  panel.levelplot(col(x$m), row(x$m), x$m, alpha.regions = 0.5,
                  subscripts=TRUE, at = do.breaks(range(x$m), 30)) 
}

grid.newpage() 
gpanel <- ghost_grob(volcano)
# gpanel <- rectGrob()
# grid.draw(gpanel)

ggplot(faithfuld, aes(waiting, eruptions)) +
  theme_minimal() + 
  scale_x_continuous(expand=c(0,0)) +
  scale_y_continuous(expand=c(0,0)) +
  geom_raster(aes(fill = density), interpolate = TRUE) +
  scale_fill_gradient(low = "white", high="black") +
  annotation_custom(gpanel, xmin = -Inf, xmax=Inf, ymin=-Inf, ymax=Inf)

Adding a layer of hexagons requires some understanding of the grid functions provided by hexbin. I don't have a clue how to use them, so I just adapted an example from ?hexpolygon for illustration. Obviously the scales and aspect ratio are nonsensical here.

ghost_grob <- function(x=runif(20, -2, 2), y=x + rnorm(20), ...){
  grob(x=x, y=y, cl="ghost") 
}

preDrawDetails.ghost <- function(x){
  addBit <- function(bnds, f = 0.05) bnds + c(-f, f) * diff(bnds)
  sc <- addBit(rxy <- range(x$x, x$y))
  pushViewport(plotViewport(.1+c(4,4,2,1), xscale = sc, yscale = sc))
}
postDrawDetails.ghost <- function(x){
  upViewport(1)
}

drawDetails.ghost <- function(x, recording){
  require(hexbin)
  hexpolygon(x$x,x$y, hexcoords(dx = 0.1, sep=NULL), border = "blue", fill=NA)
}


grid.newpage() 
gpanel <- ghost_grob()
# gpanel <- rectGrob()
# grid.draw(gpanel)

ggplot(faithfuld, aes(waiting, eruptions)) +
  theme_bw() + 
  scale_x_continuous(expand=c(0,0)) +
  scale_y_continuous(expand=c(0,0)) +
  geom_raster(aes(fill = density), interpolate = TRUE) +
  scale_fill_gradient(low = "white", high="black") +
  annotation_custom(gpanel, xmin = -Inf, xmax=Inf, ymin=-Inf, ymax=Inf) 

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • I'm still getting an error when running your code but replacing the object `volcano` with `lattice_plot`. It's saying: 'Error in valid.viewport(x, y, width, height, just, gp, clip, xscale, yscale, : invalid 'xscale' in viewport ' But I'm not entirely sure if that's what I was supposed to do in all honesty. @baptiste – Agrosel May 31 '16 at 22:12