I'm using plotly to draw an interactive 3D scatter plot with spheres, e.g.:
df <- data.frame(x = c(1,3,5), y = c(1,3,5), z = c(1,3,5), size = c(1,3,5))
library(plotly)
plot_ly(df, type = "scatter3d", x = ~x, y = ~y, z = ~z, size = ~size,
marker = list(symbol = 'circle', sizemode = 'diameter'))
The problem is: I hate plotly's spheres. I much prefer rgl's spheres which have a "shine" effect to them, e.g.:
library(rgl)
spheres3d(x = df$x, y = df$y, z = df$z, radius = 0.8 * sqrt(df$size))
And I think there's even a way in which rgl objects can be embedded in web page like plotly objects. However they lack the ability to present data on hover and other attractive capabilities of plotly.
Is there a way to get my plotly spheres to have a "shine" effect and look more like rgl's spheres? Or a way to make rgl plots have some of plotly's features especially hove info?
This should be possible with a lot of Javascript programming, since when it's embedded in a web page, rgl is using Javascript for everything. However, there's no built-in support for reacting to mouse movements like that. Within R (using Windows or X11 to display the scene, not a browser) it's probably harder, but the select3d and identify3d functions do related things (though not event-driven).
Related
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.
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.
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...
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.
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?