Add grid over 3D surface using persp3D (plot3D package) - r

I'm trying to add a grid on top of a 3D surface created with persp3D (package plot3D), however I can't sort a way of doing it without causing a deformation on the grid.
library(plot3D)
data("volcano")
volcano is a 3D matrix that can be used to create a 3D plot by simply calling:
persp3D(z=volcano)
What I intend to do is create a new grid using the dimensions of the 3D matrix and than add it to the 3D plot.
# new grid
x.seq <- seq(1, dim(volcano)[1], length = 20)
y.seq <- seq(1, dim(volcano)[2], length = 20)
# Visualize grid
plot(x=c(0,length(volcano[,1])), y=c(0,length(volcano[1,])), type='n')
abline(v=x.seq, h=y.seq)
I got close to it by subsetting the matrix volcano by the new sequences created and then plot the new 3D matrix over the original 3D surface.
# New matrix using sequences created
mtx.sub <- volcano[time.seq, freq.seq]
# Plot new matrix on top of original surface
persp3D(z=volcano)
persp3D(z=amp.sub, border="black", facets=NA, add=T, colkey=list(plot=F))
Even though the result is close to what I expected, a closer look will show that the grid is not really on top of the existing surface, it is a whole new surface that do not match the original one (which is quite obvious, given that it is a different matrix).
What I'm looking for is a way to add a 2D grid that will go over the original surface, something similar to abline, but for a 3D plot.
I had a look at plot3D documentation and searched on the web, but none of the solutions apply to persp3D().
Any thoughts on a way around this?

You can add the grid directly within the call to persp3D:
persp3D(z=volcano, border="black", lwd=0.3)
In response to your comment, you could plot at lower resolution to get wider borders, however, the surface will also be at lower resolution (see below). It would be nice to be able to plot the surface at full resolution and then have a sparser net of border lines that still matches the high-resolution surface, for example, by plotting the border lines only on every other facet, but I'm not sure how to do that without hacking persp3D (or one of the functions called by persp3D).
persp3D(z=volcano[seq(1,nrow(volcano),2), seq(1,ncol(volcano),2)],
border="black", lwd=0.4)

A work around is to use ribbon3D. It requires some more lines of code and messing around with parameters, but it does work reasonable.
require(plot3D)
### create data from 3D plotting
###
### x1, x2 a grid
### x the value of a normal distribution
x1 <- seq(-4,4,0.025)
x2 <- seq(-4,4,0.025)
mu = 0
z <- matrix(rep(0,length(x1)*length(x2)),length(x1))
for (i in 1:length(x1)) {
for(j in 1:length(x2)) {
z[i,j] <- dnorm(x1[i],mu,1)*dnorm(x2[j],mu,1)
}
}
### plot 3D
sel = 1+c(1:32)*10 ### selection of the grid lines to plot
persp3D(x1,x2,z,
border = NA, facets = TRUE, col = rgb(1,1,1,0.5),
theta = 30, phi = 30,
zlim = c(0,0.26))
ribbon3D(x1[sel],x2,z[sel,],
border = NA, facets = NA, col = 1, width = 0.02,
along = "y", space = 0.9, add = TRUE)
ribbon3D(x1[],x2[sel],z[,sel],
border = NA, facets = NA, col = 1, width = 0.02,
along = "x", space = 0.9, add = TRUE)
Example with the volcano data
require(plot3D)
### create data from 3D plotting
x <- 1:length(volcano[,1])
y <- 1:length(volcano[1,])
z <- volcano
### plot 3D
selx = seq(1,max(x),4)
sely = seq(1,max(y),4)
persp3D(x,y,z,
border = NA, facets = TRUE, lwd = 0.03,
theta = 30, phi = 30)
ribbon3D(x[selx],y,z[selx,],
border = 1, facets = 1, col = 1, width = 0.1,
along = "y", space = 0.9, add = TRUE)
ribbon3D(x[],y[sely],z[,sely],
border = 1, facets = 1, col = 1, width = 0.1,
along = "x", space = 0.9, add = TRUE)

Related

Plotting single points and their range

I am trying to plot some data points from a matrix complete with their standard deviation, but I am having troubles in plotting the latter.
My tools are:
a matrix with the data points to plot at a x coordinate within a properly xlim-defined x-axis;
a vector of as many y arbitrary coordinates for the plotting height, just not making them overlap;
a vector of lengths of the standard deviation lines, to be displayed horizontally around the data points.
Yeah, eventually it'll look like a flying saucer invasion.
I can easily plot the points at the given height, one by one - it is the way I want to do it.
Trouble comes in adding the standard deviation horizontal lines for each point.
Has someone an idea on how to do it?
x<-matrix(c(1:4,NA,NA,10:16), nrow=4, ncol=4)
y<-seq(0.001,0.006, 0.001)
std.dev<-c(runif(7, 0.1, 0.5), NA, NA, runif(7, 0.1, 0.5))
plot(0,0, xlim=c(min = 0, max(x), na.rm=T)+0.001), ylim = c(0,0.016), type = "n", xlab = "My x", yaxt = "n", ylab ="")
points(x = x[1,2], y = y[1], pch = 21, bg = "red", col = "red")
When working with base R it is amazing to find out that R does not provide a "built-in" support for error bars. You may want to consult doing this with other packages.
With base R the work-around is to use the arrow() function and setting the "arrow head angle" to 90 degrees.
Note: I had to change your given data definition as it threw errors. Also have a look at this part of your code.
I plot the error bars in vertical mode. You can easily adapt this for horizontal bars. I did this for presentation reasons to avoid overlapping error bars.
Using your full data will make it easier to deconflict the bars.
x<-matrix(c(1:7,NA,NA,10:16), nrow=4, ncol=4) # adapted to ensure same length
y<-seq(0.001,0.016, 0.001) # adapted to ensure same length
std.dev<-c(runif(7, 0.1, 0.5), NA, NA, runif(7, 0.1, 0.5))
plot(0,0
, xlim= c(min = 0, max(x, na.rm=T)) # had to fix xlim definition
, ylim = c(-1,1) # changed to show give std.dev
, type = "n", xlab = "My x", yaxt = "n", ylab ="")
points(x = x, y = y, pch = 21, bg = "red", col = "red") # set x and y to show all
# --------------- add arrows with "flat head --------------------------
arrows( x0 = x, , x1 = x
,y0 = y-std.dev, y1 = y+std.dev # center deviation on data point
, code=3, angle=90 # set the angle for the head to emulate error bar
, length=0.1)
This yields:

How to reduce the space between the plot and the border for geographic maps?

I am trying to plot a bathymetry map of the the northeast US using the marmap library. The following code loads the correct extent but when I plot the map I have blank space between the border and the map either at the top/bottom or left/right of the map. This also occurs when exporting the plots. If I drag the plot viewer screen size the plot adjusts and I can remove almost all of the empty space but I will be running this script in a loop so its not practical to solve this problem this way. Because of the loop I also can't hard code any dimensions into the plot because it will change for each new extent. How can I set the border of the plot to match the extent of the bathymetry?
library(marmap)
library(maps)
atl<- getNOAA.bathy(-80.93645,-41.61417,30.2 ,60.905 ,resolution=4)
blues <- colorRampPalette(c("darkblue", "cyan"))
greys <- colorRampPalette(c(grey(0.4),grey(0.99)))
plot(atl, image = TRUE, land = TRUE, n=0,
bpal = list(c(0, max(atl), greys(100)),
c(min(atl), 0, blues(100))))
map(database= "state", col="black", fill=FALSE, add=TRUE)
text(x=state.center$x, y=state.center$y, state.abb, cex=0.5)
This behavior is caused by the asp argument of plot.bathy(). By default, it is fixed as asp = 1 to ensure that the scales on both axes are the same (one degree of longitude equals one degree of latitude). An unwelcome consequence of this default, is the white bands appearing either on the left/right sides of the graph, or on the top/bottom sides depending on the dimensions of your bathymetric map and the plotting device.
So I suppose you have 2 options:
If you don't mind having a slightly distorted perspective, you can set asp = NA in your call to plot.bathy()
If you want to have the correct aspect ratio but need to use the default size for your plotting region, then you have to download a bathymetric region that covers the whole plotting region of your active device. For instance, you could call plot.bathy() once to create a "default" plot, then, use par("usr") to determine the limits of the bathymetry needed to fill the entire plotting area. You would then download a second bathymetry with the appropriate ranges in longitude and latitude. Which is maybe not desirable.
Here is what the code would look like for the second option:
atl <- getNOAA.bathy(-80.93645, -41.61417, 30.2, 60.905, resolution = 4)
blues <- colorRampPalette(c("darkblue", "cyan"))
greys <- colorRampPalette(c(grey(0.4), grey(0.99)))
plot(atl, image = TRUE, land = TRUE, n = 0,
bpal = list(c(0, max(atl), greys(100)),
c(min(atl), 0, blues(100))))
coord <- par("usr")
atl2 <- getNOAA.bathy(coord[1], coord[2], coord[3], coord[4], res = 4)
plot(atl2, image = TRUE, land = TRUE, lwd = 0.2,
bpal = list(c(0, max(atl2), greys(100)),
c(min(atl2), 0, blues(100))))
map(database = "state", col = "black", fill = FALSE, add = TRUE)
text(x = state.center$x, y = state.center$y, state.abb, cex = 0.5)
I suppose the solution proposed by Roman Luštrik works too, but it has the inconvenience of leaving the white bands visible on both sides of the plot.
As an aside, if you have a lot of bathymetric regions to plot, you should maybe consider using the keep = TRUE argument of getNOAA.bathy() to avoid querying the NOAA servers each time you need to re-execute your code (and it is much faster to load local data than remote ones). And you could also download once and for all the global 4Go ETOPO1 and use subset.bathy() to, well, subset the bathymetry you need for each plot.
Here is a proposal using a workaround. The idea is to convert the bathy object into raster object and then make the plot using levelplot from rasterVisthat correctly fits the plotting area to the raster extent. Note that using raster allows having a defined pixel size and, therefore, a correct width/height ratio that you don't seem to have with marmap::plot method.
library(raster)
library(rasterVis)
r <- marmap::as.raster(atl)
state <- map('state', plot = FALSE)
state <- data.frame(lon = state$x, lat = state$y)
state.lab <- data.frame(lon = state.center$x, lat = state.center$y,
label = state.abb)
# you can remove the color legend by adding colorkey = FALSE in levelplot()
levelplot(r,
at = c(seq(min(atl), 0, length.out = 100),
seq(0, max(atl), length.out = 100)[-1]),
col.regions = c(blues(100), greys(100)),
margin = FALSE) +
xyplot(lat ~ lon, state, type = 'l',
col = 'black') +
xyplot(lat ~ lon, data = state.lab,
panel = function(y, x, ...) {
ltext(x = x, y = y, labels = state.lab$label, cex = 0.75)
})

NMDS display and point symbol questions (vegan in r)

I've successfully produced NMDS plots (monoMDS, bray-curtis, 3 dimensions, local model). Each point represents an animal and their diet composition.
I have two questions:
(1) how do I change the symbology of points to show 2 levels (a or j) within 1 column (Life stage) on the NMDS plot?!
(2) How should I show 3D NMDS, I can't get the 3D orgl- functions to work on the 3D plot. Should I just make a few plots showing different dimensions in 2D? Looking for thoughtful ideas.
The code used:
plot((BC.NMDS.length.corr), choices = c(1, 2), type = "points",
xlim = c(-2.0, 2.0),las = 1, ylim = c(-1, 1),
xlab = "NMDS Axis 1", ylab = "NMDS Axis 2",mgp = c(3.25, 1, 0),
cex.lab = 1.35, cex.axis = 1.25)
with(DATA,
points(BC.NMDS.length.corr, Class, draw = "points",col = "gray0",
show.groups = "Adult",label = TRUE, lty = 1, lwd = 2))
Using an example of what you want with the default example of the package:
# Load library
library(vegan)
# Load data
data(dune)
# Compute the distance
dis <- vegdist(dune)
Specify if you want a 3D plot, the representation of the three dimensions
# Run monoMDS
m <- monoMDS(dis, model = "loc", k=3)
# The 3D representation
plot(m)
# Load library for 3D representation
library(scatterplot3d)
Coordinates are in m$points; each column referring to each dimension.
# Graphical representation
scatterplot3d(x=m$points[,1], y=m$points[,2], z=m$points[,3])
Additionally, if you want to colour the plots depending on a factor, you can specify color=A, where A is a numeric value where groups are codified.

R: Volumes around points in 3d scatterplot

I would like to generate a 3d scatterplot that includes volumes around groups of points. Something like this, but in R.
These could be rough spheroids or something more complex like in the image linked above, honestly I'm just trying to illustrate hypervolume overlap for a presentation. It seems like I could build a kernel density estimation for each set of points and plot that, but I can't figure out how to do so.
The scatterplot that I am using as a base is very similar to that generated by this code:
# Code from http://www.sthda.com/english/wiki/impressive-package-for-3d-and-4d-graph-r-software-and-data-visualization#change-the-color-by-groups
library(plot3D)
# Set up data
data(iris)
x <- sep.l <- iris$Sepal.Length
y <- pet.l <- iris$Petal.Length
z <- sep.w <- iris$Sepal.Width
# Make 3d scatterplot with colors by category
scatter3D(x, y, z, bty = "g", pch = 18,
col.var = as.integer(iris$Species),
col = c("#1B9E77", "#D95F02", "#7570B3"),
pch = 18, ticktype = "detailed",
colkey = list(at = c(2, 3, 4), side = 1,
addlines = TRUE, length = 0.5, width = 0.5,
labels = c("setosa", "versicolor", "virginica")) )
Bonus: If you can tell me how to edit the hypervolume() package so that plotting a hypervolume object works properly you would have my undying gratitude. See here for better description of the problems.

Plotting a 3D surface with no interpolation?

I have a following data:
library(rgl)
x <- c(rep(1,6),
rep(3,6),
rep(6,6),
rep(9,6),
rep(12,6))
y <- c(1.35,1.39,1.48,1.29,1.35,1.32,
NA,1.5,1.44,1.6,1.5,1.41,
NA,NA,1.72,1.56,1.6,1.55,
NA,NA,NA,1.95,1.9,1.75,
NA,NA,NA,NA,2.05,1.95)
z <- rep(1:6,5)
open3d()
plot3d(x,y,z, type = 'n')
lines3d(x,y,z)
Which is plotting lines in 3d as I expect.
But I cannot get it to plot a surface3d.
As I already read some threads I might need to interpolate my data. RGL docs has not cover this subject well. I tried akima but it doesn't accept NA's.
I would like to link lines to create a surface in linear way. I aware of the NA, so I expect that surface will be decreasing in the area for bigger x (more NA's).
Do I need to perform interpolation? If yes, how to do that on my sample data?
If no, how to achieve the surface3d on my sample data?
Thanks
the solution comes to me from this thread:
Making a wireframe plot from an x,y,z data.frame
below code will work for the sample data provided above (just switch x->y,y->z,z->x)
zmat <- matrix(data = z, nrow = 6, ncol = 5, byrow = FALSE)
surface3d(x = 1:6, y = c(1,3,6,9,12), z = zmat, alpha = 0.4, colour = 'blue')

Resources