I have plotted a mesh in rgl to visualize data on it. I.e., the mesh has colors that originate from applying a colormap to its data (one scalar value at each vertex). Here is a minimal example that consists of a mesh with a single face:
library(rgl);
library(squash);
# create a mesh
vertices <- c(
-1.0, -1.0, 0, 1.0,
1.0, -1.0, 0, 1.0,
1.0, 1.0, 0, 1.0,
-1.0, 1.0, 0, 1.0
)
indices <- c( 1, 2, 3, 4 )
# add a data value for each vertex
morph_data = rnorm(length(indices), mean = 3, sd = 1)
# create colors from values by applying a colormap
col = squash::cmap(morph_data, map = squash::makecmap(morph_data, colFn = squash::jet));
# plot
open3d()
shade3d( qmesh3d(vertices, indices), col=col )
How can I add a colorbar to this plot in rgl?
An example for what exactly I mean with colorbar is shown in the right part of this example picture from octave.sourceforge.io.
You can use bgplot3d() to draw any sort of 2D plot in the background of an rgl plot. There are lots of different implementations of colorbars around; see Colorbar from custom colorRampPalette for a discussion. The last post in that thread was in 2014, so there may be newer solutions.
For example, using the fields::image.plot function, you can put this after your plot:
bgplot3d(fields::image.plot(legend.only = TRUE, zlim = range(morph_data), col = col) )
A documented disadvantage of this approach is that the window doesn't resize nicely; you should set your window size first, then add the colorbar. You'll also want to work on your definition of col to get more than 4 colors to show up if you do use image.plot.
Related
Here I have got two 3D objects with the object formed by vertices2 half the size of that formed by vertices. I wish to plot these two objects in rgl window such that smaller one really looks smaller. However, when I tried the following code, I get two EQUALLY SIZED objects. How could I display these two objects with the one on the right proportionally smaller than the one on the left?
library(rgl)
vertices <- c(
-1.0, -1.0, 0,
1.0, -1.0, 0,
1.0, 1.0, 0,
-1.0, 1.0, 0
)
indices <- c( 1, 2, 3, 4 )
vertices2 <- vertices * 0.5
mfrow3d(1,2,sharedMouse = T)
wire3d( mesh3d(vertices = vertices, quads = indices) )
next3d()
wire3d( mesh3d(vertices = vertices2, quads = indices) )
You are plotting the two objects in independent windows. rgl automatically centers and resizes them to fill the window, so they end up looking the same. The way it does this is by moving the imagined observer closer or further from the scene.
To prevent this, you should set the observer locations to match, e.g.
mfrow3d(1,2,sharedMouse = TRUE)
wire3d( mesh3d(vertices = vertices, quads = indices) )
obs <- par3d("observer")
next3d()
wire3d( mesh3d(vertices = vertices2, quads = indices) )
observer3d(obs)
The observer location is specified using coordinates relative to the center of the bounding box of each scene.
I am using a triangle to mark an event on a timeline in R, and I've given the coordinates to the specific position on the line where the event occurs in days. In the points( function, I have supplied pch=25 to create the "filled triangle" shape. However, the positioning of the character is based on the center of the triangle. Is it possible to use an argument like "pos" (i.e. pos=3) so that the triangle is positioned immediately above the line and points to to X coordinate of interest?
Example:
plot.new()
segments(0, 0.5, 1, 0.5)
points(0.5, 0.5, pch=25)
have
want
I dont think there is an inherent function for this (i.e. pos-like function) but in the past, I have added a manual adjustment:
plot.new()
adj <- 0.015
segments(0, 0.5, 1, 0.5)
points(0.5, 0.5 + adj, pch=25)
So with multiple points:
points(seq(0.1, 0.9, 0.1), rep(0.5, 9) + adj, pch = 25)
Since R's interpreter supports a wide variety of encodings, and since the pch is just the text input, you can just paste the down triangle into the text editor and calculate:
strheight('▽') -> l
and change the last line to
points(0.5, 0.5 + l/2, pch=25)
to get the desired
> strheight('▽')
[1] 0.1022132
I have 3D meshes representing closed surfaces not necessarily convex for which I would like to get orthographic projections onto arbitrary directions (to put in context, the 3D meshes represent satellites and the end goal is to use the projections to calculate atmospheric drag).
As a first step, I am just aiming to compute the surface area of the resulting projection. Is there any way to perform such operation with rgl? Since the meshes represent closed surfaces, the projections will not contain multiple disconnected polygons.
I believe I can get the set of triangles/quads visible from a given direction by using the facing3d() function, specifying the direction in the up argument. But I am unsure on how to proceed from there.
You can do the projections using the rgl::shadow3d() function, and calculate area using geometry::polyarea(). For example,
library(rgl)
library(geometry)
satellite <- translate3d(icosahedron3d(), x = 0, y = 0, z = 5)
vertices <- asEuclidean2(satellite$vb)
xrange <- range(vertices[1,])
yrange <- range(vertices[2,])
floor <- mesh3d(x = c(2*xrange, 2*rev(xrange)),
y = rep(2*yrange, each = 2),
z = 0, quads = 1:4)
open3d()
#> glX
#> 1
shadow <- shadow3d(floor, satellite, plot = FALSE,
minVertices=1000 # Need this to get a good shadow
)
shade3d(satellite, col= "red")
shade3d(floor, col = "white", polygon_offset = 1, alpha = 0.1)
shade3d(shadow, col = "gray")
vertices <- unique(t(asEuclidean2(shadow$vb)))[,1:2]
hull <- chull(vertices)
hullx <- vertices[hull,1]
hully <- vertices[hull,2]
plot(c(hullx, hullx[1]), c(hully, hully[1]), type = "l")
polyarea(hullx, hully)
#> [1] 3.266855
Created on 2022-12-13 with reprex v2.0.2
I currently use following script to create a plot for betweenness centrality:
plot(g,
rescale = FALSE,
edge.color= edge_color,
edge.width=E(g)$Weight*0.5,
vertex.size= degree(g)*0.5,
main="Degree Centrality"
)
As you can see, I currently use a simple multiplier to adjust vertex.size. As some nodes are really big and some seem too small, I would like to set a range with a minimum and maximum size. Of course, that range should consider degree(g).
Is that somehow possible?
Note: Attempts with scale (degree(g), 5, 15) or similar did not work: "Error in symbols(x = coords[, 1], y = coords[, 2], bg = vertex.color, :
invalid symbol parameter"
To rescale numbers, x, with a domain of (a,b) to a range of (c,d) you need to make a rescaling function like:
rescale = function(x,a,b,c,d){c + (x-a)/(b-a)*(d-c)}
So then if you have degree sizes from 0 to 200, and want your vertex sizes to range from 1 to 5 units, specify the vertex size with:
rescale(degree(g), 0, 200, 1, 5)
This is just a simple linear transformation - you might want something non-linear for better visuals.
You might find a rescale function in a package somewhere (like the rescale function in the scales package), but its not what scale does!
Example
Suppose I have two triangles:
A triangle with points (0, 0), (10, 0), (10, 0.5) and
a triangle with points (0, 0), (1, 0), (0.5, 11)
The resulting two plots without specifying the xlim and ylimlook like this:
Question
What do I need to do to satisfy all points listed below?
Make the triangle visible, so that no line of the triangle is hidden by an axis
Specify the same margin for all plots in mm, cm or other unit.
(in the example above only two triangles were used. Actually I have n triangles.)
As margin I mean the distance between the outer points of the triangle and the axis.
The resulting plots should look like this
with the difference that the distances, which are marked with the red arrows, should all be the same!
I don't know of a way to to it in cm/mm, but you can do it with the precentage of the total size:
# you don't really need this see bellow
#from matplotlib.backends.backend_pdf import PdfPages
import pylab
import matplotlib.pyplot as plt
left,bottom,width,height = 0.2,0.1,0.6,0.6 # margins as % of canvas size
fig = plt.figure(figsize=(4,4),facecolor="yellow") # figure size in Inches
fig.patch.set_alpha(0.8) # just a trick so you can see the difference
# between the "canvas" and the axes
ax1 = plt.Axes(fig,[left,bottom,width,height])
ax1.plot([1,2,3,4],'b') # plot on the first axes you created
fig.add_axes(ax1)
ax1.plot([0,1,1,0,0], [0,0,1,1,0],"ro") # plot on the first axes you created
ax1.set_xlim([-1.1,2])
ax1.set_ylim([-1.1,2])
# pylab.plot([0,1,1,0,0], [0,0,1,1,0],"ro") avoid usig if you
# want to control more frames in a plot
# see my answer here
#http://stackoverflow.com/questions/8176458/\
#remove-top-and-right-axis-in-matplotlib-after-\
#increasing-margins/8180844#8180844
# pdf = PdfPages("Test.pdf")# you don't really need this
# pylab.savefig(pdf, papertype = "a4", format = "pdf")
# automagically make your pdf like this
pylab.savefig("Test1.pdf", papertype="a4",facecolor='y')
pylab.show()
pylab.close()
# pdf.close()
and the output is:
corrected image:
Your two triangles with points (0, 0), (10, 0), (10, 0.5) and (0, 0), (1, 0), (0.5, 11) would be represented in pylab as:
Ax = [0, 10, 10]
Ay = [0, 0, 0.5]
Bx = [0, 1, 0.5]
By = [0, 0, 11]
pylab.plot(Ax, Ay)
pylab.plot(Bx, By)
Let's see what the lowest X value is:
lowestX = None
for x in Ax+Bx:
if lowestX==None or x<lowestX:
lowestX = x
Exercise for the reader to do the same for highestX, lowestY, and highestY.
Now, consider a boundary of 2 units, you can add / subtract these units from the lowest and highest values and set xlim and ylim:
margin = 2
pylab.xlim([lowestX-margin, highestX+margin])
pylab.ylim([lowestY-margin, highestY+margin])