Creating 3D plots in R opens up an interactive window where the user can rotate the view. For example below using package rgl:
library(rgl)
plot3d(iris[,1:3],col=c("red","green","blue")[iris$Species],size=5)
Is there some way to set a predefined view and export the plot as a regular image. I would like to do this in an automated non-interactive manner for many datasets.
Use scatterplot3d package.
library(scatterplot3d)
graphics.off()
png(filename = "test.png", width = 8, height = 6, units = "in", res = 300)
par(mai = c(0.5, 0.5, 0.5, 0.5))
scatterplot3d(x = iris$Sepal.Length, y = iris$Sepal.Width, z = iris$Petal.Length,
color = c("red","green","blue")[iris$Species],
cex.symbols = 1, pch = 19, angle = -30)
dev.off()
Besides #d.b's answer using scatterplot3d instead of rgl, you could save results using R Markdown. The advantage of doing it this way is that you get an interactive display instead of a static one; the disadvantage is that the format is HTML, not PNG or another bitmap format.
Before knitting the document, run code like this and interactively choose
the initial display you want:
library(rgl)
options(rgl.useNULL = FALSE)
plot3d(iris[,1:3],col=c("red","green","blue")[iris$Species],size=5)
Once you have it oriented correctly, run this code:
M <- par3d("userMatrix")
dput(M)
You'll get something like
structure(c(0.776694416999817, 0.198224693536758, -0.597873568534851,
0, -0.629868388175964, 0.249577552080154, -0.735511302947998,
0, 0.00341932475566864, 0.947849154472351, 0.318700969219208,
0, 0, 0, 0, 1), .Dim = c(4L, 4L))
as output. Then start your R Markdown document with something like
library(rgl)
options(rgl.useNULL = TRUE)
M <- structure(c(0.776694416999817, 0.198224693536758, -0.597873568534851,
0, -0.629868388175964, 0.249577552080154, -0.735511302947998,
0, 0.00341932475566864, 0.947849154472351, 0.318700969219208,
0, 0, 0, 0, 1), .Dim = c(4L, 4L))
(which you would probably choose not to echo), and in each code chunk that produces a plot, write code like this:
plot3d(iris[,1:3],col=c("red","green","blue")[iris$Species],size=5)
par3d(userMatrix = M)
rglwidget()
(If this is in a loop or isn't at the top level for some other reason, you'll need print(rglwidget()) instead.)
Then all of your plots will initially have the same orientation, but all of them will be user-rotatable.
Related
I want to plot a animated 3D scatterplot and save it as gif. I followed the code provided by the R Graph Gallery example: https://www.r-graph-gallery.com/3-r-animated-cube.html.
library(rgl)
library(magick)
options(rgl.printRglwidget = TRUE)
# Let's use the iris dataset
# iris
# This is ugly
colors <- c("royalblue1", "darkcyan", "oldlace")
iris$color <- colors[ as.numeric( as.factor(iris$Species) ) ]
# Static chart
plot3d( iris[,1], iris[,2], iris[,3], col = iris$color, type = "s", radius = .2 )
# We can indicate the axis and the rotation velocity
play3d( spin3d( axis = c(0, 0, 1), rpm = 20,dev = cur3d()),startTime = 0, duration = 10 )
# Save like gif
movie3d(
movie="3dAnimatedScatterplot",
spin3d( axis = c(0, 0, 1), rpm = 20,dev = cur3d()),
startTime = 0,
duration = 10,
dir = ".",
type = "gif",
clean = T,
fps=10,
convert=T
)
plot3d was successed output a 3d scatter plot.
Static 3d scatter plot
But the final output: 3dAnimatedScatterplot.gif,just a black image
3dAnimatedScatterplot.gif
when I set clean=F, all frame images are black. So, I guess the play3d() was not working.
Can anyone provide any help to me ? Thanks a lot !
Most likely snapshot3d isn't working for you. Try it with the option webshot = FALSE instead of the default webshot = TRUE. That uses a different mechanism for saving the image.
I am trying to follow help and Internet examples to create a very simple animation of a 3d-line in R. This is just a test and my final goal is to use this functionality to visually verify results of some geometrical transformations on 3d-movement data that I am analysing. So basically I need nothing more than a ‘3d-player’ interface that allows for usual interaction (rotation, zoom, play, stop, slide).
I figured out that rgl package does the job and I am able to use it for the sphere/points animation. But now I need to use it on lines and I get very strange results. In the example below there are 4 points and two lines (cyan and red) that connect the same points but the red line is for some reason in the ‘wrong’ place. The animation doesn’t make sense neither. Now, I am thinking may be it is impossible to do >> to animate more than one vertex with more than one attribute? But I don’t see this in documentation and obviously it is possible because line is animated! I spent quite a long time trying to figure out what is going on and will appreciate any help/advise/directions. many thanks
Ps: Tthe code below is a chunk in the markdown file and I am using Rstudio.
require(rgl)
require(rglwidget)
p11=c(0,0,0)
p21=c(50,50,0)
p12=c(50,0,0)
p22=c(10,50,50)
saveopts <- options(rgl.useNULL = TRUE)
did=list()
did[[1]]=plot3d(rbind(p11,p21,p12,p22), type="s", alpha = 1, lwd = 5, col = c('brown','darkgreen','orange','green'))
did[[2]]=spheres3d(c(100,100,100), alpha = 1, lwd = 5, col = "blue",radius=2)
did[[3]]=lines3d(rbind(p11,p21),lwd=8, col='cyan',alpha=.9)
did[[4]]=planes3d(0, 0, 1, 0, alpha=.4, col='green')
did[[5]]=lines3d(rbind(p11,p21),lwd=2, col='red')
aspect3d(1, 1, 1)
did[[6]]=grid3d(c("x-", "z-"),at = NULL,col = "gray",lwd = .5,lty = 1,n = 5)
sceneT = rglwidget(elementId = "plot3dT",width=500, height=300) #%>%
rgl.ids()
rgl.attrib(id=did[[3]],attrib = c(1:length(did[[3]])))
playwidget(sceneT,list(
vertexControl(values = rbind(c(0,0,0,0,0,0),c(50,50,50,50,50,50)),
vertices = 1:6, attributes = "z", objid = did[[4]], param = 1:2,interp =T),
vertexControl(values = r1,
vertices = 1:2, attributes = c('x',"y","z",'x',"y","z"), objid = did[[5]], param = 1:2,interp =T)),
start = 1, stop = 2, step = .1, precision = 3)
options(saveopts)
I am learning latex graphics. I generated latex graphs with standalone, but I am trying to generate R plots with latex fonts. Through online tutorials, here is my code with the Iris dataset in RStudio (I modified example code to get it working. Once I know how to fix the frame, I can study the code in more details):
library(tikzDevice)
options(tikzMetricPackages = c("\\usepackage[utf8]{inputenc}",
"\\usepackage[T1]{fontenc}", "\\usetikzlibrary{calc}",
"\\usepackage{amssymb}"))
## I need the amssymb package because I use \mathcal and \mathbb
tikz("formula.tex", width = 4, height = 4, standAlone = TRUE,
packages = c("\\usepackage{tikz}",
"\\usepackage[active,tightpage,psfixbb]{preview}",
"\\PreviewEnvironment{pgfpicture}",
"\\setlength\\PreviewBorder{0pt}",
"\\usepackage{amssymb}"))
par(mar = c(4, 4, 0.1, 0.1), mgp = c(2, 0.9, 0))
library(tikzDevice)
options(tikzMetricPackages = c("\\usepackage[utf8]{inputenc}",
"\\usepackage[T1]{fontenc}", "\\usetikzlibrary{calc}",
"\\usepackage{amssymb}"))
## I need the amssymb package because I use \mathcal and \mathbb
tikz("formula.tex", width = 4, height = 4, standAlone = TRUE,
packages = c("\\usepackage{tikz}",
"\\usepackage[active,tightpage,psfixbb]{preview}",
"\\PreviewEnvironment{pgfpicture}",
"\\setlength\\PreviewBorder{0pt}",
"\\usepackage{amssymb}"))
par(mar = c(4, 4, 0.1, 0.1), mgp = c(2, 0.9, 0))
plot(iris$Sepal.Length, iris$Sepal.Width, main="Iris sepal length vs width measurements", xlab="Length", ylab="Width")
dev.off()
tools::texi2pdf("formula.tex")
system(paste(getOption("pdfviewer"), "formula.pdf"))
Which gives me:
I would like to have a bit more white space on the right, so the 8 comes fully, and fix the title. The inner picture should be smaller and the title lower as well, if possible.
Sorry for this. I looked at the code and figured it out! I needed to remove
par(mar = c(4, 4, 0.1, 0.1), mgp = c(2, 0.9, 0))
which gives me:
So it works!!! :D
I am trying to plot a 3D graph in lattice using wireframe. I want to reset the margins between the 3d plot and the R window,
The top margins is too big and this wastes lots of space when I convert it into postscript files. I am trying to reduce to margins.
I tried the following R code par(mar=c(4,3,3,1)+0.1).
However nothing is happening.
Try this snippet, adapted from https://stat.ethz.ch/pipermail/r-help/2007-January/123556.html. It worked for me.
library(lattice)
theme.novpadding <- list(
layout.heights = list(
top.padding = 0,
main.key.padding = 0,
key.axis.padding = 0,
axis.xlab.padding = 0,
xlab.key.padding = 0,
key.sub.padding = 0,
bottom.padding = 0
),
layout.widths = list(
left.padding = 0,
key.ylab.padding = 0,
ylab.axis.padding = 0,
axis.key.padding = 0,
right.padding = 0
)
)
wireframe(volcano, shade = TRUE,
aspect = c(61/87, 0.4),
#par.settings = theme.novpadding, # uncomment this
light.source = c(10,0,10))
I want to plot three plots vary close together, so they appear as one field. My data are arrays with different dimensions.
Here is some example code to display my problem:
library(lattice)
theme.novpadding = list(layout.heights = list(top.padding = 0,
main.key.padding = 0,
key.axis.padding = 0,
axis.xlab.padding = 0,
xlab.key.padding = 0,
key.sub.padding = 0,
bottom.padding = 0),
layout.widths = list(left.padding = 0,
key.ylab.padding = 0,
ylab.axis.padding = 0,
axis.key.padding = 0,
right.padding = 0),
axis.line = list(col = "transparent"))
p1 = levelplot(array(c(1:100), c(10,10)), colorkey=F, par.settings=theme.novpadding)
p2 = levelplot(array(c(1:100), c(9,9)), colorkey=F, ylab = NULL, par.settings=theme.novpadding)
p3 = levelplot(array(c(1:100), c(11,11)), ylab=NULL, par.settings=theme.novpadding)
width = 0.33
height = 1
ph = list(5, "in")
print(p1, position = c(0, 0, width, height), panel.height=ph, more=T)
print(p2, position = c(width, 0, 2*width, height), panel.height=ph, more=T)
print(p3, position = c(2*width, 0, 3*width, height),panel.height=ph, more=F)
As you see, they are spread very wide. I want them as close as possible.
I use theme.novpadding to set the margins to zero.
Is the a way to say something like "distance between plots"?
This problem is easy to solve if you structure your data into a nice data.frame. Lattice works best on data.frames. See the example bellow:
g.xy <- expand.grid(1:10, 1:10)
my.data <- data.frame(x=g.xy$Var1, y = g.xy$Var2, value1=1:100, value2 = 1:100, value3=1:100, value4=1:100)
levelplot(value1+value2+value3+value4~x+y, data=my.data, scales=list(x="free", y="free")) # free
levelplot(value1+value2+value3+value4~x+y, data=my.data, scales=list(x="same", y="same")) # same range, so panels are "touching"
You can control for the plots to appear very close together by setting the scale argument.
A trick you could use is to tweak the position argument. In stead of not letting the areas overlap, you can do just that to make them close together. For example, you can change the position arguments to:
ovr = 0.05
print(p1, position = c(0, 0, width + ovr, height), panel.height=ph, more=T)
print(p2, position = c(width - ovr, 0, 2*width+ovr, height), panel.height=ph, more=T)
print(p3, position = c(2*width - ovr, 0, 3*width, height),panel.height=ph, more=F)
You have to some tweaking with this, and I have not tested this code. But I think the general idea should work.