Plotting a 3D volume plot in R using discrete 3D points - r

I'm trying to create a volume plot in R that will show the smoothed density of points in 3D space. I'm using plotly to plot with scatter3D at the moment.
df = data.frame(a = sample(seq(.5,.8,.001),100),
b = sample(seq(0,.5,.001),100),
c = sample(seq(0,.3,.001),100),
value = sample(seq(0,1,.01),100))
plot_ly(df, x = ~a, y = ~b, z = ~c, type = 'scatter3d')
Setting type to 'mesh3d' works to connect these points as a surface in 3d space.
plot_ly(df, x = ~a, y = ~b, z = ~c, type = 'mesh3d')
However, trying to set the type to 'volume' gives me a blank plot each time.
plot_ly(df, x = ~a, y = ~b, z = ~c, value = ~value, type = 'volume',
isomin = 0, isomax = 1, opacity = 1)
The documentation is here: https://plotly.com/r/reference/volume/ and specifies:
Draws volume trace between iso-min and iso-max values with coordinates given by four 1-dimensional arrays containing the value, x, y and z of every vertex of a uniform or non-uniform 3-D grid. Horizontal or vertical slices, caps as well as spaceframe between iso-min and iso-max values could also be drawn using this trace.
The python version of plotly provides an example: https://plotly.com/python/3d-volume-plots/ but I don't know where I'm missing the conversion to R. I've included the extra value dimension for volume plots, but it doesn't seem to do anything.

Unlike the documentation of plotly where it states you can use non-uniform grids with a volume, plotting 3d volumes with isomin and isomax is actually not possible. Having a single coordinate which is non-uniform invalidates the whole plot. I struggled with the same issue for ages, wondering why my plots keep ending up blank.
To demonstrate, first create a plot with a uniform grid. For example, use the Python example from the resource you linked: https://plotly.com/python/3d-volume-plots/. (It doesn't matter that it's the Python API, rendering the plots is language independent).
import plotly.graph_objects as go
import numpy as np
X, Y, Z = np.mgrid[-8:8:40j, -8:8:40j, -8:8:40j]
values = np.sin(X*Y*Z) / (X*Y*Z)
fig = go.Figure(data=go.Volume(
x=X.flatten(),
y=Y.flatten(),
z=Z.flatten(),
value=values.flatten(),
isomin=0.1,
isomax=0.8,
opacity=0.1, # needs to be small to see through all surfaces
surface_count=17, # needs to be a large number for good volume rendering
))
fig.show()
This should show a plot. Next, change the coordinate of one point:
X[0, 0, 0] += 1
And tada: it no longer works and shows an empty plot.

Related

Can I assign specific values from a ColorGradient to a marker?

I apologize for this super basic question, but I am not experienced in plotting, and a lot of the documentation for Julia plotting assumes more knowledge than I have!
I am creating a scatter plot, using Plots, where each marker is plotted based on spatial position, and I want to scale the color by magnitude of value that each marker holds. I created a color gradient as such:
C(g::ColorGradient) = RGB[g[z] for z = LinRange(0,1,M)]
g = :inferno
cgrad(g,[0.01,0.99]) |> C
M is related to the number of markers, this way I create a suitable scale of colors based on the number of markers I have.
I assumed I was creating some kind of structure that would assign a color from this gradient based off a value ranging from 0.01 to 0.99. However, I guess I don't understand what the structure C is. When I assign color = C(v), where v is between 0 and 1.00, I get an error saying that C does not accept type Float64.
Is there a way I can assign a marker some color from this gradient based off its value? I have all of the values for each location stored in another array.
UPDATE: I have also tried indexing into C. I turned my values into Int64 ranging from 1-99, and tried to set color=C[v], but C also does not take Type Int64.
UPDATE 2: Ok, so i realized my issue was I did not understand the |> functionality, So i rewrote the code to look like:
C(g::ColorGradient) = RGB[g[z] for z = LinRange(0,1,M)]
g = :inferno
myGrad = (cgrad(g,[0.00,1.00]) |> C)
and now I can index into my color gradiant! However I still am having an issue setting the color equal to the value stored in the myGradient array.
for i = 1:M
X,Y = find_coords(i,pd)
colors = myGrad[c_index[i]]
outline = rand(Float64,3)
plt = plot!(X,Y,colors, markerstrokecolor = outline)
end
When I type myGrad[c_index[i]] into REPL it plots a color. However I am getting an error from the above code which states
"Cannot convert RGB{Float64} to series data for plotting"
If i change the plot line as follows I get a slightly different error:
plt = plot!(X,Y,markercolor = colors, markerstrokecolor = outline)
ERROR: LoadError: MethodError: no method matching plot_color(::Float64)
So for some reason I cant store this color, as a color variable for my plot.
There are a few different issues at play here. Firstly, if you want to create a scatter plot, you should probably use scatter. It also doesn't seem necessary to plot things in a loop here, although it's hard to tell as your code isn't a minimum working example (MWE), as it relies on things defined somewhere else in your code.
Here's an example of how this might work:
using Plots
# Create a discrete color gradient with 20 points
my_colors = [cgrad(:inferno, [0.01, 0.99])[z] for z ∈ range(0.0, 1.0, length = 20)]
# Draw some random data points
x, y = sort(rand(100)), rand(100)
# Assign a color between 1 and 20 on the color grid to each point
z = sort(rand(1:20, 100))
# Plot
scatter(x, y, color = my_colors[z], markerstrokecolor = "white", label = "",
markersize = [10 for _ ∈ 1:100])
gives:

3D pipe/tube plots in R - creating plots of tree roots

I'm trying to create 3D plots of simulated tree roots in R. Here is an example of a root system growing over time:
This is essentially a 3D network of cylinders, where the cylinder diameter (and, optionally, color) represents the size of the root. The available data includes:
x, y, z of the root centroid
direction of "parent" root (e.g. +x, -x, +y, -y, +z, -z), although this information could be captured in several different ways, including by calculating the x, y, z of the parent directly prior to plotting.
size of root
Example 3D data is here, but here is my first attempt at it in just 2D using ggplot2::geom_spoke:
dat <- data.frame(x = c(0,1,-1,0,1,-1),
y = c(-1,-1,-1,-2,-2,-2),
biomass = c(3,1.5,1.5,1,1,1),
parent.dir = c("+y","-x","+x","+y","+y","+y"))
dat$parent.dir <- as.numeric(as.character(factor(dat$parent.dir,
levels = c("-x", "+x", "-y", "+y"),
labels = c(pi, 0, pi*3/2, pi/2))))
ggplot(dat, aes(x = x, y = y)) +
geom_point(x = 0, y = 0, size = 20) +
geom_spoke(radius = 1,
aes(angle = parent.dir,
size = biomass)) +
coord_equal()
I prefer a solution based in the ggplot2 framework, but I realize that there are not a ton of 3D options for ggplot2. One interesting approach could be to creatively utilize the concept of network graphs via the ggraph and tidygraph packages. While those packages only operate in 2D as far as I know, their developer has also had some interesting related ideas in 3D that could also be applied.
The rgl library in seems to be the go-to for 3D plots in R, but an rgl solution just seems so much more complex and lacks the other benefits of ggplot2, such as faceting by year as in the example, easily adjusting scales, etc.
Example data is here:
I don't understand the format of your data so I'm sure this isn't the display you want, but it shows how to draw a bunch of cylinders in rgl:
root <- read.csv("~/temp/root.csv")
segments <- data.frame(row.names = unique(root$parent.direction),
x = c(-1,0,1,0,0),
y = c(0,1,0,0,-1),
z = c(0,0,0,0.2,0))
library(rgl)
open3d()
for (i in seq_len(nrow(root))) {
rbind(root[i,2:4],
root[i,2:4] - segments[root$parent.direction[i],]) %>%
cylinder3d(radius = root$size[i]^0.3, closed = -2, sides = 20) %>%
shade3d(col = "green")
}
decorate3d()
This gives the following display (rotatable in the original):
You can pass each cylinder through addNormals if you want it to look smooth, or use sides = <some big number> in the cylinder3d to make them look rounder.

create a video from plotly 3d chart

I'm wondering if anyone knows of a way to export plotly 3d charts as a video (more specifically if this can be done natively or requires bodging)?
Exporting a static image is simple, and exporting the interactive plots is fine for embedding in HTML etc.
Lets say I have a 3D chart that I want so simply rotate slowly, this seems like it could be pretty straight forward if the image can be rotated a given interval, an image taken, rotated further ad infinitum, perhaps in a loop - but I'm wondering if this isn't somehow supported natively?
Anyone know of a good strategy?
Solution ideally for R/RStudio, but since plotly is cross-platform, any solutions considered.
For future reference:
The key to being able to iterate multiple perspectives turns out to lie in the camera control "eye", the plotly help centre pointed me toward this:
https://plot.ly/r/reference/#layout-scene-camera
camera = list(eye = list(x = 1.25, y = 1.25, z = 1.25))) #1.25 is default
This is kind of answered here, though searching for my specific query as above didn't find it:
enter link description here
I've used a for loop in the script to pass iterators to a trigonometric function that plots out a circle for the camera co-ordinates, rendering a new image at each step.
(x,y) = cos(theta) + sin(theta)
The eventual code looked like this:
# assume dataset read in, manipulated and given as a matrix in "matrix"
matrix.list <- list(x = temp, y = scan, z = matrix)
font.pref <- list(size=12, family="Arial, sans-serif", color="black")
x.list <- list(title = "X", titlefont = font.pref)
y.list <- list(title = "Y", titlefont = font.pref)
z.list <- list(title = "Z",titlefont = font.pref)
zoom <- 2
for(i in seq(0,6.3,by=0.1){
# 6.3 is enough for a full 360 rotation
outfile <- paste(file,"plot",i, sep = "_")
graph <- plot_ly(matrix.list, x = temp, y = scan, z = z,
type="surface") %>%
layout(scene=list(xaxis = x.list,
yaxis = y.list,
zaxis = z.list,
camera = list(eye = list(x = cos(i)*zoom, y = sin(i)*zoom, z= 0.25))))
# The above camera parameters should orbit
# horizontally around the chart.
# The multiplier controls how far out from
# from the graph centre the camera is (so
# is functionally a 'zoom' control).
graph
plotly_IMAGE(graph, username="xxx", key="xxx",
out_file = paste(outfile,"png", sep="."))
}
NB Number of files and the resolution of them could end up taking up a fair amount of space.
NB 2 I had forgotten while constructing this that plotly's free API limits you to 50 API calls per day, so if you want to render a video, adjust your frames etc, accordingly...

adding data on top of a 3D base map using R

I am trying to plot a 3D space time cube in R and I want to have a basemap.
I am using rgl library. I know how to plot my data using x, y and z, where z is the time variable. I have also managed to download a map that I want to use as reference from openstreetmap, using the library in R. However, I cannot find a way to plot my data on the map in a 3D environment. I found the following code in several sites and as an answer to a similar question:
map3d <- function(map, ...){
if(length(map$tiles)!=1){stop("multiple tiles not implemented") }
nx = map$tiles[[1]]$xres
ny = map$tiles[[1]]$yres
xmin = map$tiles[[1]]$bbox$p1[1]
xmax = map$tiles[[1]]$bbox$p2[1]
ymin = map$tiles[[1]]$bbox$p1[2]
ymax = map$tiles[[1]]$bbox$p2[2]
xc = seq(xmin,xmax,len=ny)
yc = seq(ymin,ymax,len=nx)
colours = matrix(map$tiles[[1]]$colorData,ny,nx)
m = matrix(0,ny,nx)
surface3d(xc,yc,m,col=colours, ...)
}
However, I cannot really understand how it works.
Here's my code so far:
library(rgl)
library(ggplot2)
library(OpenStreetMap)
map <- openmap(c(53.5,73.6),c(15.7,134.7),type= 'esri-topo')
plot3d(x,y,z, col= colour) # to plot my data
autoplot(map) # to plot the map. though this is 2D
Again, I know how to plot my data on a 2D map. Confused with the 3D.
Any hints and tips on how to do this?
One option is to use the newish 'show2d' function in 'rgl'.
library(rgl)
library(OpenStreetMap)
library(raster)
map <- openmap(c(53.5,73.6),c(15.7,134.7),type= 'esri-topo')
## fake up some xyz
xyz <- expand.grid(x = map$bbox$p1,
y = map$bbox$p2,
z = 1:4)
plot3d(xyz, col = "black") # to plot my data
EDIT: this is wrong, it's only fitted to the bounding box
getting the orientation right is confusing, needs to be check with x, y, z arguments to show2d.
show2d(raster::plotRGB(raster(map)))
This function captures the normal plot expression, writes it to PNG and then texture maps it onto a quad in the scene.
I can't quite see how to control the position of the quad for the image texture with the x, y, z args - work in progress.

How to plot all of huge data points in scatter3d of plot_ly?

I have a data.frame named as z, which has 4 millions data points to make scatter3d plot by using plot_ly as follows.
p <- plot_ly(z, x = ~Lon, y = ~Lat, z = ~z,
mode = 'markers',
marker = list(color = "black",size = 2, opacity =0.05),
type = 'scatter3d', source = 'scatter')
htmlwidgets::saveWidget(as_widget(p), "scatter3d_plotly.html") # save as html
Then, I came across an error, saying
pandoc.exe: Out of memory
Error: pandoc document conversion failed with error 251
Because I assumed that this was a problem with memory allocation, I tested if downsampling can help to make a tentative plot as follows;
z_ds <- sample_frac(tbl = z, size = 0.01) # downsampled into 1%
Now, this works, which made sure that this is a problem of memory.
However, downsampling is not appropriate in my research purpose, and what I need is plotting all the 4 million data points in one figure.
In summary,
I want a scatter3d plot with all 4 millions data point
I do not really need completely "interactive" 3D plot if this makes difficult to deal with huge data point
But, hopefully, I want to keep the "3D rotating feature" in the html format by drag.
It does not have to use Plotly if there is alternative.
Would you please give me any suggestion about this?

Resources