4

I'm working with 3 dimensional coordinates data, which i'm plotting in a scatterplot, i have ~30.000 datapoints, and i've included the first 10 here so that you can reproduce it

library(rgl)
library(plot3D)
library(car)

df <- data.frame(meanX = c(147.34694,
                       173.89244,
                       135.73004,
                       121.93766,
                       109.72152,
                       92.53709,
                       165.46588,
                       169.77744,
                       127.01796,
                       99.34347),
             meanY = c(140.40816,
                       110.99128,
                       134.56023,
                       164.18703,
                       166.04051,
                       155.97329,
                       105.29377,
                       104.42683,
                       130.17066,
                       155.99696),
             avgDist = c(40.788118,
                         12.957329,
                         14.24348,
                         39.10424,
                         34.694258,
                         25.532335,
                         21.491695,
                         23.528944,
                         9.309201,
                         31.916879))

I've been using the scatter3d function to plot this

scatter3d(x = df$meanX, y = df$meanY, z = df$avgDist, surface = FALSE)

Now my "problem", is that I would like to have a 2d surface with an external image file overlayed onto it at z=0, and as a bonus, if i could project a heatmap/contours from the scatterplot data (meanX and meanY used for the contours) over that image as well, that would be great.

This is the image i'd like to have draped at z = 0:

https://i.stack.imgur.com/BOnnv.png

That image was made with this ggplot:

map.colors <- colorRampPalette(c("green","yellow","red"))

densityPlot <- ggplot(direData, aes(x = meanX, y = ,meanY)) + 
  stat_density2d(geom="tile", aes(fill=..density.., alpha=sqrt(sqrt(..density..))), contour=FALSE, n=100) +
  scale_alpha(range = c(0, 1.0)) + scale_fill_gradientn(colours = map.colors(5)) + 
  xlim(70,185) + ylim(70,185)

minimap <- readPNG('~/yasp/minimap.png')

densityPlot + annotation_raster(minimap, ymin = 70 ,ymax=185 ,xmin = 70,xmax = 185) + 
  stat_density2d(geom="tile", aes(fill=..density.., alpha=10*sqrt(..density..)), contour=FALSE, n=100)

Is there any way to do this? I've googled quite a bit for a solution but found no real way of doing this. I don't mind creating the image first in ggplot2 with the heatmap, saving that, and then using that as input for the surface draping, but it would of course be quite cool if it could all be done in one call to plot.

Mike Wise
  • 22,131
  • 8
  • 81
  • 104
  • Is this not what you wanted? – Mike Wise May 24 '16 at 14:07
  • @KG I think fish666's answer deserves a look. I don't mind you moving the correct check if you think it is better - although it doesn't address the texture map. Maybe that could be incorporated. – Mike Wise May 27 '16 at 09:47
  • 1
    Hey Mike, thanks for all your help on this topic, it's greatly appreciated, since his solution makes make sme able to call a ggplot2 function i just used teh annotation_raster in his code and everything works perfectly, it also solves my issues with the ranges by using plot3d rather than scatter3d (found out in my other question that scatter3d seems to have some issues with ranges, while plot3d doesn't). – Kári Gunnarsson May 29 '16 at 19:50
  • Totally okay with it. It is a nice effort for a SO beginner too. Anyway, I am really far more interested in the upvotes (for badges) than I am in the solution points. – Mike Wise May 29 '16 at 20:12

2 Answers2

3

(2nd Edit) I try to write something better code and confirm two xy-coordinates are the same. ggplot2 theme with no axes or grid help me to plot only the panel region.

library(rgl); library(grid); library(gtable)

df <- data.frame(meanX = c(147.34694, 173.89244, 135.73004, 121.93766,
                           109.72152,  92.53709, 165.46588, 169.77744,
                           127.01796,  99.34347),
                 meanY = c(140.40816, 110.99128, 134.56023, 164.18703,
                           166.04051, 155.97329, 105.29377, 104.42683,
                           130.17066, 155.99696),
                 avgDist = c(40.788118, 12.957329, 14.24348, 39.10424,
                             34.694258, 25.532335, 21.491695,23.528944,
                             9.309201,  31.916879))

map.colors <- colorRampPalette(c("green","yellow","red"))

# set scale_*_continuous() to plot only the panel region. limits mean xlim (or ylim)
# change "tile" into "raster" because of making noise lines on my screen
densityPlot <- ggplot(df[,1:2], aes(x = meanX, y = ,meanY)) + 
  stat_density2d(geom="raster", aes(fill=..density.., alpha=sqrt(sqrt(..density..))), contour=FALSE, n=100) +
  scale_alpha(range = c(0, 1.0)) + scale_fill_gradientn(colours = map.colors(5)) + 
  scale_x_continuous(limits=c(70,185), expand = c(0,0)) + scale_y_continuous(limits=c(70,185), expand = c(0,0)) +
  geom_point(size=4)               # to test XY-coordinate (black points on the map)

open3d()
plot3d( df, type="s", radius=1, col="red", axes=F, 
        xlim = c(70,185), ylim = c(70,185),
        expand = 1 )
plot3d( df, type="h", col="blue", add=T )  # to test XY-coordinate (line segments from z = 0)
axes3d(c("x","y","z") )
show2d({                  # show2d uses 2D plot function's output as a texture on a box.
  grid.draw(gtable_filter(ggplotGrob(densityPlot), "panel"))
},
expand = 1 , texmipmap = F )   # texmipmap = F makes tone clear (not essential)

# I think this is clearly better than using a intermediate file,
# so I deleted related code. Thanks Mike !

plot

Community
  • 1
  • 1
cuttlefish44
  • 6,586
  • 2
  • 17
  • 34
  • is minimap the same file as he specified? you should explain what you are doing (at least a little) and say why this is different / better. It looks interesting. – Mike Wise May 27 '16 at 06:19
  • Thank you for your advice and vote. I will make efforts to be a good answerer. – cuttlefish44 May 27 '16 at 09:31
  • I know it is more work, but nice answers get more upvotes, and ensure your reputation in real life too :) – Mike Wise May 27 '16 at 09:46
  • 1
    This is great! I added the minimap using the annotation_raster when defining a new "densityPlot2", minimap <- readPNG('~/yasp/minimap.png') densityPlot2 <- densityPlot + annotation_raster(minimap, ymin = 70 ,ymax=185 ,xmin = 70,xmax = 185) + stat_density2d(geom="tile", aes(fill=..density.., alpha=10*sqrt(..density..)), contour=FALSE, n=100). I then use just call densityPlot2 in your code instead and everything works perfectly :) – Kári Gunnarsson May 29 '16 at 19:48
  • I’m glad you like it. – cuttlefish44 May 30 '16 at 06:06
2

How about this?

I stored your lined image file in a png in the local directory, there is probably a way to do that without an intermediate file, but I would ask that as a separate question.

Note that this is actually a simple case of texture mapping. The texture is saved in the gameshot.png file you specified. You could warp the text around a more complicated object by adding more points to the geometry and adjusting the texture map coordinates accordingly.

While they should not have been absolutely necessary here, I added texture map coordinates as it looked like the file and the data were not aligned by default - and in fact the gameshot.png file was displaying reversed. It looks to me like the png file you specified does not quite match the data, I think there is an inversion somewhere before you saved it.

library(rgl)
library(plot3D)
library(car) 

df <- data.frame(meanX = c(147.34694, 173.89244, 135.73004, 121.93766,
                           109.72152,  92.53709, 165.46588, 169.77744,
                           127.01796,  99.34347),
                 meanY = c(140.40816, 110.99128, 134.56023, 164.18703,
                           166.04051, 155.97329, 105.29377, 104.42683,
                           130.17066, 155.99696),
                 avgDist = c(40.788118, 12.957329, 14.24348, 39.10424,
                             34.694258, 25.532335, 21.491695,23.528944,
                             9.309201,  31.916879))

car::scatter3d(x = df$meanX, y = df$meanY, z = df$avgDist, surface = FALSE)

xvek <- c(0,1)
yvek <- c(0,1)
lnx <- length(xvek)
lny <- length(yvek)
zmat <- matrix(0,lnx,lny)

# Setup the Texture coordinates - defaults seem to invert image
# tms <- matrix(c(0,0,1,1),lnx,lny) # generic case (xy-maped texture looks like png file)
# tmt <- matrix(c(0,1,0,1),lnx,lny)   

tmt <- matrix(c(1,1,0,0),lnx,lny) # "correct case" (ball density look more like picture)
tms <- matrix(c(1,0,1,0),lnx,lny) # I think the gameshot.png is in error  


# Texture file specified in question was stored locally in "gameshot.png"
surface3d(xvek,yvek,zmat,coord=c(3,1),texture_s=tms,texture_t=tmt,
          lit=F,fog=T,color="white",textype="rgb",texture="gameshot.png",add=T)

Yields this:

enter image description here

Mike Wise
  • 22,131
  • 8
  • 81
  • 104
  • Thanks a lot Mike! I fixed the orientation of the map a bit and plotted with ~ 5000 points here http://i.imgur.com/1UwnQsc.png. My issue now is that i need to find a way limit either the image or the plot to x and y limts, but xlim and ylim don't seem to do anything, any idea if this is possible? It should be between 70 and 185 in both x and y, but it won't allow me to use xlim = c(70,185). I know rgl doesn't allow clipping, but there are no datapoints above 180, so i'm a little unsure why it's not cooperating. – Kári Gunnarsson May 24 '16 at 19:21
  • I'm guessing that's also why the heatmap wasn't matching up with the datapoints as you pointed out, since the image is created with the x and y limits 70,185, but it's binding it to a xlim = c(80,200) and ylim=c(90,200), evne though no points go higher than the 185 limit. – Kári Gunnarsson May 24 '16 at 19:32
  • Out having a beer at th moment will look again tomorrow. – Mike Wise May 24 '16 at 20:09
  • So this question and answer were fairly successful. I would mark it correct and post another question about that. Try and make it a minimal example. One more upvote on a question and then you can upvote too - which makes SO much more rewarding for everyone concerned. And `rgl` needs more activity. – Mike Wise May 25 '16 at 08:05
  • Let's chat when you come online. – Mike Wise May 25 '16 at 10:28
  • Sounds like a plan, sorry only have a few hours in the evenings for personal work so it takes a bit of time for me to respond. – Kári Gunnarsson May 25 '16 at 18:13
  • Actually I saw to it that you could upvote this one too :). Just saying. – Mike Wise May 25 '16 at 18:29