How to create 3D - MATLAB style - surface plots in R - r

I find it challenging to create aesthetically pleasing 3D surfaces in R. I am familiar with the solutions (persp, image, wireframe, lattice, rgl and several other solutions in other questions in SO), but the results are not nice.
Is it possible to create 3D surface plots in R like in MATLAB?
Here is the MATLAB code
% Create a grid of x and y points
points = linspace(-2, 0, 20);
[X, Y] = meshgrid(points, -points);
% Define the function Z = f(X,Y)
Z = 2./exp((X-.5).^2+Y.^2)-2./exp((X+.5).^2+Y.^2);
% "phong" lighting is good for curved, interpolated surfaces. "gouraud"
% is also good for curved surfaces
surf(X, Y, Z); view(30, 30);
shading interp;
light;
lighting phong;
title('lighting phong', 'FontName', 'Courier', 'FontSize', 14);
The plot is modern, colorful, aesthetically pleasing, the code syntax is very readable.
Is this possible in base R?

jet.colors is the R-answer to one of hte Matlab color palettes:
points = seq(-2, 0, length=20)
#create a grid
XY = expand.grid(X=points,Y=-points)
# A z-function
Zf <- function(X,Y){
2./exp((X-.5)^2+Y^2)-2./exp((X+.5)^2+Y^2);
}
# populate a surface
Z <- Zf(XY$X, XY$Y)
zlim <- range(Z)
zlen <- zlim[2] - zlim[1] + 1
jet.colors <- # function from grDevices package
colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan",
"#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))
colorzjet <- jet.colors(100) # 100 separate color
require(rgl)
open3d()
rgl.surface(x=points, y=matrix(Z,20),
coords=c(1,3,2),z=-points,
color=colorzjet[ findInterval(Z, seq(min(Z), max(Z), length=100))] )
axes3d()
rgl.snapshot("copyMatlabstyle.png")
I will admit that getting the colors to line up with the "Z-axis" (which is actually the rgl y-axis) seemed very unintuitive. If you want the shiny, specular effect that Matlab delivers you can play with the angle of illumination.
You can also add or remove lighting:
clear3d(type = "lights")
light3d(theta=0, phi=0)
light3d(theta=0, phi=0) # twice as much light.
After:
grid3d("x")
grid3d("y")
grid3d("z")
rgl.snapshot("copyMatlabstyle3.png")
You could have put the y-grid "behind" the surface with:
grid3d("y+")
Similar tweaks to the axes3d or axis3d calls could move the location of the scales.
For further examples, look at http://rgm3.lab.nig.ac.jp/RGM/R_image_list and search for 'plot3d' which brings up examples of the R2BayesX::plot3d function, Look at Karline Soetaert's plot3D package vignette, "50 ways to plot a volcano"

This may well not do everything you want, but I'm posting it in hopes of attracting better answers.
X <- Y <- seq(-2, 0, length.out= 20)
Z <- outer(X,Y,
function(X,Y) 2/exp((X-.5)^2+Y^2)-2/exp((X+.5)^2+Y^2))
cc <- colorRamp(rev(rainbow(10)))
Zsc <- (Z-min(Z))/diff(range(Z))
rgbvec2col <- function(x) do.call(rgb,c(as.list(x),list(max=255)))
colvec <- apply(cc(Zsc),1,rgbvec2col)
library(rgl)
surface3d(X,Y,Z,col=colvec)
bbox3d(color=c("white","black"))

Related

"interp" for discrete points to get heatmap/contour R

I have a sitation in which I generate data from simulation and then would like to plot (heat map/contour/3d plot etc); however, for these, data needs to be interpolated using functions like interp. Here is the sample dataset.
Here is the piece of code I tried...
library(akima)
library(GA) # for persp3D; there exists another package for same function "fields"
data <- read.table(commandArgs()[3], header=T,sep="\t")
data <- na.omit(data)
qmax = max(data$q)
kmax = max(data$k)
x <- data$k_bike/kmax
y <- data$k_car/kmax
z <- data$q/qmax
matrix = interp(x,y,z)
persp3D(matrix ,nlevels=30, asp=1, xlim=c(0,1), ylim=c(0,1), color.palette=colorRampPalette(c("green3","yellow", "red"),space = "rgb") )
so the result is --
Now, due to interpolation, there are many points, which have red/orange color instead of green or so. For e.g, if I use levelplot of lattice
levelplot(z~x*y, xlim=c(0,1), ylim=c(0,1), col.regions=colorRampPalette(c("green3","yellow", "red"),space = "rgb") )
The outcome is --
Now, it is clearly visible that there are very few data points having zero (or almost zero) zvalue. Now, the problem is, with levelplot, I get artefacts (white color for missing data points) and I would like to have a better interpolation. Is there any other function to perform this?
I also tried contour plots as follows:
scale <- (qmax+10) / qmax * c(0.000, 0.01, 0.05, 0.10, 0.25, 0.5, 0.75, 1.0)
filled.contour(matrix, nlevels=30, asp=1, xlim=c(0,1), ylim=c(0,1), levels=scale,color.palette=colorRampPalette(c("green3","yellow", "red"),space = "rgb") )
and result is again (kind of wrong color indication).
In short -- I would like to have contour plot or 3d plot but with a
clear (or correct) color indication of zero (about zero) zvalue data
points similar to level plot.
I approached your question with deldir and rgl packages (they allow plotting of surfaces defined by irregular collections of points).
library(deldir); library(rgl)
# Below two lines require time depending on the machine power, be careful
dxyz <- deldir(x, y, z = z) # do Delaunay triangulation
mxyz <- as.mesh3d(dxyz) # convert it to triangle.mesh3d.obj
bgyr <- colorRampPalette(c("blue", "green", "yellow", "red")) # colour func
# use z values for colouring
plot3d(mxyz, col=bgyr(256)[cut(mxyz$vb[3,], 256)][mxyz$it], type="shade")
light3d() # if you want vivit colours
# another approach
# you can solve it by just increasing interp()'s arguments, nx and ny.
library(akima); library(lattice); library(dplyr)
df <- interp(x,y,z, nx=150, ny=150) %>% interp2xyz() %>% data.frame()
levelplot(z ~ x * y, df, xlim=c(0,1), ylim=c(0,1),
col.regions = colorRampPalette(c("green3", "yellow", "red"), space = "rgb"))

replicating an rgl viewpoint in lattice

It would be convenient to interactively select a decent viewpoint using rgl and then adopt the same orientation in a lattice 3d-plot. For example, given the following plot using a non-informative viewpoint.
library(lattice)
wireframe(volcano, screen = list(x=0, y=0, z=0))
The same can be opened in rgl by
library(rgl)
persp3d(volcano)
view3d(0, 0)
Interactively it is easy to rotate the plot to an informative view.
The matrix giving the current rgl viewpoint in can be extracted by
p <- par3d()
p$userMatrix
How can this matrix be converted into corresponding x,y,z screen parameters to replicate the view in lattice?
UPDATE 1
I tried out 42's conversion below. The code shows the rgl plot and the corresponding lattice plot per row. If I implemented it correctly (see code below), there appears to still be an issue.
# convert rgl viewpoint into lattice
# screen orientation
rgl_to_lattice_viewpoint <- function()
{
p <- par3d()
rotm <- p$userMatrix
B = 360*atan(rotm[1,2]/rotm[2,2])/(2*pi)
P = 360*asin(-rotm[3,2])/(2*pi)
H = 360*atan(rotm[3,1]/rotm[3,3])/(2*pi)
list(x=-B, y=-P, z=-H)
}
# read and plot PNG image
plot_png <- function(f)
{
img <- readPNG(f)
rimg <- as.raster(img) # raster multilayer object
plot(NULL, xlim=c(0,1), ylim=c(0,1), xlab = "", ylab = "",
asp=1, frame=F, xaxt="n", yaxt="n")
rasterImage(rimg, 0, 0, 1, 1)
}
# create rgl snapshot with random rotation and
# corresponding lattice wireframe plot
lattice_plus_rgl_plot <- function()
{
# rgl plot random rotation
persp3d(volcano, col = "green3")
theta <- sample(-180:180, 1)
phi <- sample(-90:90, 1)
view3d(theta, phi, fov=40)
v <- rgl_to_lattice_viewpoint()
f <- tempfile(fileext = ".png")
rgl.snapshot(f)
rgl.close()
# lattice plot
f2 <- tempfile(fileext = ".png")
png(f2)
print(wireframe(volcano, screen = v))
dev.off()
# plot both
plot_png(f)
plot_png(f2)
}
# CREATE SOME PLOTS
library(rgl)
library(lattice)
library(png)
par(mfrow=c(3,2), mar=c(0,0,0,0))
replicate(3, lattice_plus_rgl_plot())
I used the answer to this question for conversion from a rotation matrix to angles: Conversion euler to matrix and matrix to euler . I admit to concern that I see another somewhat different answer here: How to calculate the angle from Roational matrix . (My linear algebra is not good enough to determine which of these is correct.)
p <- par3d()
rotm <- p$userMatrix
B = 360*atan(rotm[1,2]/rotm[2,2])/(2*pi)
P = 360*asin(-rotm[3,2])/(2*pi)
H = 360*atan(rotm[3,1]/rotm[3,3])/(2*pi)
> print(list(B,P,H))
[[1]]
[1] 41.54071
[[2]]
[1] 40.28412
[[3]]
[1] 41.24902
At that point I had already rotated the RGL-object to roughly the "viewing point" that you had suggested. I discovered by experimentation that the negative values supplied to the wireframe call delivered apparently correct results. "Viewer rotation angles" are plausibly seen as the negative for "object rotation angles".
png(); print(wireframe(volcano, screen = list(x=-B, y=-P, z=-H)) ); dev.off()
There is a rotate.wireframe function in the TeachingDemos package but it does not play well with concurrently running rgl plots. (No plot was apparent until I closed the rgl device.) It also seemed kind of buggy when running on a Mac (thick black line across the lattice plot). It uses the X11/XQuartz facilities to manage interaction via tk/tcl functions and I was unable to reproduce the plots from the angles being displayed. Looking at the code I'm not able to understand why that should be so. But your mileage may vary.
This version of your function uses conversions from the orientlib package, and makes the rotation matrix an argument:
rgl_to_lattice_viewpoint <- function(rotm = par3d("userMatrix"))
{
e <- -orientlib::eulerzyx(orientlib::rotmatrix(rotm[1:3, 1:3]))#x*180/pi
list(z = e[1], y = e[2], x = e[3])
}
Note that the z, y, x order is essential.
Using it in place of your function, I get this output:
These get the rotation right. I don't know if it's also possible to get the perspective to match.
Edited to add: rgl version 0.95.1468, so far available only on R-forge,
contains a version of this function and one for base graphics as well.

Stacking of several Surface plots in 3D-View

Lets consider that I have five 2D-Matrices which describe the magnetic field at different z-Layers. A nice, smoothed version of a 2D-Surface plot can be obtained as follows:
data2_I<-matrix(c(1.0,1.0,0.6,0.6,0.7,0.9,0.9,0.5,0.5,0.5,0.7,0.9,0.9,0.6,0.3,0.4,0.7,0.9,0.9,0.7,0.5,0.5,0.6,0.9,0.9,0.7,0.6,0.6,1.0,1.0), nrow=5)
Z = as.vector(data2_I)
length(Z)
XY=data.frame(x=as.numeric(gl(5,1,30)),y=as.numeric(gl(5,6,30)))
t=Tps(XY,Z)
surface(t)
Now it would be great if I could get a 3D-plot where at different z-Positions these surfaces are plotted. Is there a possibility to do that?
I found an alternative approach: With the package rgl I and the function surface 3D I can stack several 3D-Surface plots within one open3d-window. Lets look at a small example:
library("rgl")
data2_I<-matrix(c(1.0,1.0,0.6,0.6,0.7,0.9,0.9,0.5,0.5,0.5,0.7,0.9,0.9,0.6,0.3,0.4,0.7,0.9,0.9,0.7,0.5,0.5,0.6,0.9,0.9,0.7,0.6,0.6,1.0,1.0), nrow=5)
data0_I<-matrix(c(1.0,1.0,0.6,0.6,0.7,0.9,0.9,0.5,0.5,0.5,0.7,0.9,0.9,0.6,0.3,0.4,0.7,0.9,0.9,0.7,0.5,0.5,0.6,0.9,0.9,0.7,0.6,0.6,1.0,1.0), nrow=5)
data1_I<-2*data0_I
data2_I<-1/data1_I
elv=0
offs=5*elv+1
z0 <- scale*data0_I
z1 <- scale*data1_I
z2 <- scale*data2_I
x <- 1:nrow(z0)
y <- 1:ncol(z0)
palette <- colorRampPalette(c("blue","green","yellow", "red"))
col.table <- palette(256)
open3d(windowRect=c(50,50,800,800))
surface3d(x, y, elv*z0, color = col.table[cut(z0, 256)], back = "lines")
surface3d(x, y, elv*z1+1*offs, color = col.table[cut(z1, 256)], back = "lines")
surface3d(x, y, elv*z2+2*offs, color = col.table[cut(z2, 256)], back = "lines")
axes3d()
aspect3d(1,1,2)
The variables offsand elv are included for cosmetic purposes: offs controls the space between two surface plots and elevation how the z-axes of the surface3d-plots should scale. As I wanted to have a 2D surface plot without any elevation I set it to zero.

Easiest way to plot inequalities with hatched fill?

Refer to the above plot. I have drawn the equations in excel and then shaded by hand. You can see it is not very neat. You can see there are six zones, each bounded by two or more equations. What is the easiest way to draw inequalities and shade the regions using hatched patterns ?
To build up on #agstudy's answer, here's a quick-and-dirty way to represent inequalities in R:
plot(NA,xlim=c(0,1),ylim=c(0,1), xaxs="i",yaxs="i") # Empty plot
a <- curve(x^2, add = TRUE) # First curve
b <- curve(2*x^2-0.2, add = TRUE) # Second curve
names(a) <- c('xA','yA')
names(b) <- c('xB','yB')
with(as.list(c(b,a)),{
id <- yB<=yA
# b<a area
polygon(x = c(xB[id], rev(xA[id])),
y = c(yB[id], rev(yA[id])),
density=10, angle=0, border=NULL)
# a>b area
polygon(x = c(xB[!id], rev(xA[!id])),
y = c(yB[!id], rev(yA[!id])),
density=10, angle=90, border=NULL)
})
If the area in question is surrounded by more than 2 equations, just add more conditions:
plot(NA,xlim=c(0,1),ylim=c(0,1), xaxs="i",yaxs="i") # Empty plot
a <- curve(x^2, add = TRUE) # First curve
b <- curve(2*x^2-0.2, add = TRUE) # Second curve
d <- curve(0.5*x^2+0.2, add = TRUE) # Third curve
names(a) <- c('xA','yA')
names(b) <- c('xB','yB')
names(d) <- c('xD','yD')
with(as.list(c(a,b,d)),{
# Basically you have three conditions:
# curve a is below curve b, curve b is below curve d and curve d is above curve a
# assign to each curve coordinates the two conditions that concerns it.
idA <- yA<=yD & yA<=yB
idB <- yB>=yA & yB<=yD
idD <- yD<=yB & yD>=yA
polygon(x = c(xB[idB], xD[idD], rev(xA[idA])),
y = c(yB[idB], yD[idD], rev(yA[idA])),
density=10, angle=0, border=NULL)
})
In R, there is only limited support for fill patterns and they can only be
applied to rectangles and polygons.This is and only within the traditional graphics, no ggplot2 or lattice.
It is possible to fill a rectangle or polygon with a set of lines drawn
at a certain angle, with a specific separation between the lines. A density
argument controls the separation between the lines (in terms of lines per inch)
and an angle argument controls the angle of the lines.
here an example from the help:
plot(c(1, 9), 1:2, type = "n")
polygon(1:9, c(2,1,2,1,NA,2,1,2,1),
density = c(10, 20), angle = c(-45, 45))
EDIT
Another option is to use alpha blending to differentiate between regions. Here using #plannapus example and gridBase package to superpose polygons, you can do something like this :
library(gridBase)
vps <- baseViewports()
pushViewport(vps$figure,vps$plot)
with(as.list(c(a,b,d)),{
grid.polygon(x = xA, y = yA,gp =gpar(fill='red',lty=1,alpha=0.2))
grid.polygon(x = xB, y = yB,gp =gpar(fill='green',lty=2,alpha=0.2))
grid.polygon(x = xD, y = yD,gp =gpar(fill='blue',lty=3,alpha=0.2))
}
)
upViewport(2)
There are several submissions on the MATLAB Central File Exchange that will produce hatched plots in various ways for you.
I think a tool that will come handy for you here is gnuplot.
Take a look at the following demos:
feelbetween
statistics
some tricks

How can I recreate this 3d histogram?

I am talking about this picture:
Questions:
This is R, not Matlab right? Below the page it says it was made with R....
How can I do this? I mean, how can I create such a 3d scatterplot with this advanced green surface and this grid? I now how to make simple scatterplots and also 3d scatterplots, but how can I create such an advanced picture? Which package is this?
I want to include it in a paper where this picture should rotate automatically. I know how to include this into my tex-distribution, but therefore I need single png. So e.g. 1000 single pictures which I animate. But how can I get those with R? I would need to rotate it and then save every single small rotation as a graphic file.
Thanks a lot for your help, my biggest problems are the creation of this graphic (packages?) and how to make it rotate (r code?)
To create this figure, you might check out persp function. You can change the parameter to rotate the figure. Here's one demo:
require(grDevices) # for trans3d
x <- seq(-10, 10, length= 30)
y <- x
f <- function(x,y) { r <- sqrt(x^2+y^2); 10 * sin(r)/r }
z <- outer(x, y, f)
z[is.na(z)] <- 1
persp(x, y, z, theta = 90, phi = 30, expand = 0.5, col = "lightgreen")
When change theta = 30:
persp(x, y, z, theta = 30, phi = 30, expand = 0.5, col = "lightgreen")
For color, you can type colors() to see what color you can use. Currently, I found lightgreen might be the closest color you want.

Resources