On 11/18/05, Pierre-Luc Brunelle <pierre-luc.brunelle at polymtl.ca>
wrote:> Hi,
>
> I am using function wireframe from package lattice to draw a 3D surface.
> I would like to add a few points on the surface. I read in a post from
> Deepayan Sarkar that "To do this in a wireframe plot you would
probably
> use the panel function panel.3dscatter". Does someone have an example?
Here goes. Let's say the surface you want is:
surf <-
expand.grid(x = seq(-pi, pi, length = 50),
y = seq(-pi, pi, length = 50))
surf$z <-
with(surf, {
d <- 3 * sqrt(x^2 + y^2)
exp(-0.02 * d^2) * sin(d)
})
To draw just the surface, you can of course do
library(lattice)
wireframe(z ~ x * y, g, aspect = c(1, .5),
scales = list(arrows = FALSE))
To modify the display, you will want to write your own
panel.3d.wireframe function (as suggested in ?panel.cloud), which
defaults to 'panel.3dwire'. The first thing to realise is that
lots of funny things go on inside panel functions that you don't
really want to know about. The trick is to make liberal use of the
... argument, only naming arguments that you need to work with or
override. So our first try might be to write an explicit but minimal
panel.3d.wireframe function that does nothing new:
wireframe(z ~ x * y, g, aspect = c(1, .5),
scales = list(arrows = FALSE),
panel.3d.wireframe = function(...) {
panel.3dwire(...)
})
Now, you want to add points using 3dscatter, and that's obviously
going to use different data. However, most of the rest of the
arguments will be the same for the panel.3dwire and panel.3dscatter
calls (giving details of the 3-D to 2-D projection, mostly), so you need to
capture the data passed in to panel.3d.wireframe, without worrying
about the exact form of the rest of the arguments. This can be done by:
wireframe(z ~ x * y, g, aspect = c(1, .5),
scales = list(arrows = FALSE),
panel.3d.wireframe = function(x, y, z, ...) {
panel.3dwire(x = x, y = y, z = z, ...)
})
which is of course the same as before. Now let's add a few points
using panel.3dscatter:
wireframe(z ~ x * y, g, aspect = c(1, .5),
scales = list(arrows = FALSE),
panel.3d.wireframe = function(x, y, z, ...) {
panel.3dwire(x = x, y = y, z = z, ...)
panel.3dscatter(x = runif(10, -0.5, 0.5),
y = runif(10, -0.5, 0.5),
z = runif(10, -0.25, 0.25),
...)
})
Note that the ... arguments have been passed on to panel.3dscatter.
Without it, you would have the error that you reported.
Now, presumably the points that you want to add are in the original
data scale, whereas panel.3dwire wants data in a different
(linearly shifted and scaled) scale suitable for 3-D
transformations. I happened to know what that transformed scale is
(usually [-0.5, 0.5]), and the call above makes use of that
knowledge. In practice, you would have to make the conversion from
data scale to transformed scale yourself, and that's where the *lim
and *lim.scaled arguments come in. They contain the range of the
data cube in the original and transformed scales respectively. So
let's say the points you want to add (in the original scale) are:
pts <-
data.frame(x = runif(10, -pi, pi),
y = runif(10, -pi, pi),
z = runif(10, -1, 1))
Then the suitable transformation can be done as follows:
wireframe(z ~ x * y, g, aspect = c(1, .5),
scales = list(arrows = FALSE),
pts = pts,
panel.3d.wireframe function(x, y, z,
xlim, ylim, zlim,
xlim.scaled, ylim.scaled, zlim.scaled,
pts,
...) {
panel.3dwire(x = x, y = y, z = z,
xlim = xlim,
ylim = ylim,
zlim = zlim,
xlim.scaled = xlim.scaled,
ylim.scaled = ylim.scaled,
zlim.scaled = zlim.scaled,
...)
xx <-
xlim.scaled[1] + diff(xlim.scaled) *
(pts$x - xlim[1]) / diff(xlim)
yy <-
ylim.scaled[1] + diff(ylim.scaled) *
(pts$y - ylim[1]) / diff(ylim)
zz <-
zlim.scaled[1] + diff(zlim.scaled) *
(pts$z - zlim[1]) / diff(zlim)
panel.3dscatter(x = xx,
y = yy,
z = zz,
xlim = xlim,
ylim = ylim,
zlim = zlim,
xlim.scaled = xlim.scaled,
ylim.scaled = ylim.scaled,
zlim.scaled = zlim.scaled,
...)
})
Hope this helps,
-Deepayan