I am attempting to reproduce a Stephen Few graphic with gradient circles that demonstrates the hard wired assumption that light appears from above. Here are the circles:
How can I recreate this? Drawing the circles isn't too bad but adding gradient is where I get thrown. I am thinking grid may create something more crisp but this may be a misconception I have.
Here is the start with drawing circles:
## John Fox circle function
source("http://dl.dropboxusercontent.com/u/61803503/wordpress/circle_fun.txt")
par(mar=rep(1, 4), bg = "grey80")
plot.new()
for (i in seq(0, 1, by = .2)) {
for (j in seq(.6, 1, by = .1)) {
circle(i, j, .5, "cm", , 1)
}
}
Related question: How to use R to build bubble charts with gradient fills
EDIT:
Thought I'd share the results:
And here's the code.
With some repeated use of clip, you can get there.
# set up a blank plot
par(mar=rep(0, 4))
par(bg="#cccccc")
plot(NA,xlim=0:1,ylim=0:1)
# define a function
grad.circ <- function(centrex,centrey,radius,col,resolution) {
colfunc <- colorRampPalette(col)
shades <- colfunc(resolution)
for (i in seq_along(shades) ) {
clip(
centrex - radius,
centrex + radius,
(centrey + radius) - ((i-1) * (radius*2)/length(shades)),
(centrey + radius) - (i * (radius*2)/length(shades))
)
symbols(
centrex,
centrey,
circles=radius,
bg=shades[i],
fg=NA,
add=TRUE,
inches=FALSE
)
}
}
# call the function
grad.circ(0.5,0.5,0.5,c("black", "white"),300)
Result:
EDIT (by Tyler Rinker):
I wanted to add the rest of the code I used to replicate the image:
FUN <- function(plot = TRUE, cols = c("black", "white")) {
plot(NA, xlim=0:1, ylim=0:1, axes=FALSE)
if (plot) {
grad.circ(0.5, 0.5, 0.5, cols, 300)
}
}
FUN2 <- function(){
lapply(1:3, function(i) FUN(,c("white", "black")))
FUN(F)
lapply(1:3, function(i) FUN())
}
X11(10, 4.5)
par(mfrow=c(3, 7))
par(mar=rep(0, 4))
par(bg="gray70")
invisible(lapply(1:3, function(i) FUN2()))
Here is a version using rasters and rasterImage:
image <- as.raster( matrix( seq(0,1,length.out=1001), nrow=1001, ncol=1001) )
tmp <- ( row(image) - 501 ) ^2 + ( col(image) - 501 )^2
image[tmp > 500^2] <- NA
image2 <- as.raster( matrix( seq(1,0, length.out=1001), nrow=1001, ncol=1001) )
image2[ tmp > 500^2 ] <- NA
image3 <- row(image) + col(image)
image3 <- image3/max(image3)
image3[tmp>500^2] <- NA
image4 <- 1-image3
image3 <- as.raster(image3)
image4 <- as.raster(image4)
plot( 0:1, 0:1, type='n', asp=1,ann=FALSE,axes=FALSE)
rect(0,0,1,1, col='grey')
rasterImage(image, 0.2, 0.2, 0.3, 0.3)
rasterImage(image2, 0.6, 0.6, 0.7, 0.7)
rasterImage(image3, 0.6, 0.3, 0.7, 0.4)
rasterImage(image4, 0.3, 0.7, 0.4, 0.8)
Other directions of shading can be made by changing the math a little.
You can do this using the (not on CRAN) package zernike . It's designed to produce various images related to Zernike polynomials, heavily used in optics & astronomy systems. Your desired images are pretty much the second Zernike term.
The author is Author: M.L. Peck (mpeck1#ix.netcom.com) ; I forget exactly where the R-package resides on hte web.
And here's an approach using sp and rgeos (similar application here and here).
library(sp)
library(rgeos)
library(raster)
Create two sets of 9 circles by buffering points, then plot their union to set up the plotting area.
b <- gBuffer(SpatialPoints(cbind(rep(1:3, 3), rep(1:3, each=3))), TRUE,
width=0.45, quadsegs=100)
b2 <- gBuffer(SpatialPoints(cbind(rep(5:7, 3), rep(1:3, each=3))), TRUE,
width=0.45, quadsegs=100)
plot(gUnion(b, b2), border=NA)
Step through the polygons and extract their bounding boxes.
bb <- sapply(b#polygons, bbox)
bb2 <- sapply(b2#polygons, bbox)
Plot stacked segments to simulate a gradient.
segments(rep(bb[1,], each=1000),
mapply(seq, bb[2,], bb[4,], len=1000),
rep(bb[3,], each=1000), col=gray.colors(1000, 0))
segments(rep(bb2[1,], each=1000),
mapply(seq, bb2[2,], bb2[4,], len=1000),
rep(bb2[3,], each=1000), col=rev(gray.colors(1000, 0)))
Difference the union of the SpatialPolygon objects and plot the differenced polygon to mask out the non-circles areas.
plot(gDifference(as(extent(par('usr')), 'SpatialPolygons'), gUnion(b, b2)),
col='gray80', border='gray80', add=TRUE)
For bonus circle smoothness, plot the circles once more, with colour equal to the background colour.
plot(gUnion(b, b2), border='gray80', lwd=2, add=TRUE)
Related
I have a list of points and a list of simplices. I would like to plot the simplices in 3D given their vertices. Essentially, I am looking for the equivalent of segment() in 3D.
Example
Pts<-matrix(c(0,0,0,1,0,0,0,1,0,0,0,1),ncol =3,byrow=TRUE)
Simplex<-c(1,2,3,4)
So, I am looking for a way to input Pts and Simplex and getting a plot of the tetrahedron.
I’ve tried searching but so far the only possibility seems to write out the functions for the linear spaces and plot those. Any tips will be highly appreciated.
With the 'rgl' package:
library(rgl)
vertices <- rbind(
c(0, 0, 0),
c(1, 0, 0),
c(0, 1, 0),
c(0, 0, 1)
)
faces <- combn(4,3)
for(f in 1:4){
triangles3d(rbind(
vertices[faces[1,f],],
vertices[faces[2,f],],
vertices[faces[3,f],]
), color="red", alpha=0.4)
}
You can add the edges and the vertices:
# add edges as thin cylinders
edges <- combn(4, 2)
for(e in 1:6){
shade3d(cylinder3d(rbind(vertices[edges[1,e],],vertices[edges[2,e],]),
radius = 0.02, sides = 30), col="yellow")
}
# add vertices as small spheres
spheres3d(vertices, radius= 0.03, color = "yellow")
Not as pretty or flexible, but here's a base R version using persp and segments for fun:
## empty perspective plot
tm <- persp(matrix(rep(0,4), nrow=2),
xlim=c(-1,1), ylim=c(-1,1), zlim=c(-1,1),
col="#00000000", border=NA, theta=30, phi=50, xlab="x")
## project points into 3d space
tpts <- data.frame(trans3d(pmat=tm, x=Pts[,1], y=Pts[,2], z=Pts[,3]))
## draw each segment
sgs <- combn(seq_len(nrow(tpts)), 2,
FUN=function(r) unlist(tpts[r,]), simplify=FALSE)
lapply(sgs, function(x) segments(x[1], x[3], x[2], x[4], col="red"))
In base R (and in sp), I wish to create arrows with a predefined shape but flexible rotation centered at provided coordinates. I came up with the following function:
my_arrow <- function(x,y, rotate=0, col=par("fg"), cex=1) {
xbase <- c(1.2,0.2,0.2,-1.2, -1.2, 0.2, 0.2)
ybase <- c(0,1,0.5,0.5,-0.5,-0.5,-1)
rotM <- matrix(c(cos(rotate*pi/180), sin(rotate*pi/180), -sin(rotate*pi/180), cos(rotate*pi/180)), nrow=2)
transf <- function(x1,y1) cex * rotM %*% c(x1,y1) + c(x,y)
ans <- t(mapply(transf, xbase, ybase))
polygon(x=ans[,1], y=ans[,2], col=col)
}
This produces the arrow I want if rotation=0, however it gets distorted when I do rotate. For instance,
plot(1:2, type="p", col="white", xlim=c(-5,5), ylim=c(-10,10))
my_arrow(0,0, rotate=45)
produces the chart below.
I think I need to apply some special types of coordinates, but I am stuck. Any ideas?
(The arrows function will not work for me since I have another shape in mind. Using gridBase and some rotated viewports sounds like overkill to me.)
After inspecting the function shapes::rotatexy, I found the solution myself: I need to address the aspect ratio issue. In the end, I came up with the following function which works fine for me:
my_arrow <- function(x,y, rotate=0, col=par("fg"), border=par("fg"), cex=1) {
scale_base <- strwidth("O")/2.4
xbase <- c(1.2,0.2,0.2,-1.2, -1.2, 0.2, 0.2) * scale_base
ybase <- c(0,1,0.5,0.5,-0.5,-0.5,-1) * scale_base
rotM <- matrix(c(cos(rotate*pi/180), sin(rotate*pi/180), -sin(rotate*pi/180), cos(rotate*pi/180)), nrow=2)
transf <- function(x1,y1) cex * rotM %*% c(x1,y1) + c(x,y)
ans <- t(mapply(transf, xbase, ybase))
# this is deliberately taken from shapes::rotatexy
user <- par("usr")
pin <- par("pin")
sy <- user[4] - user[3]
sx <- user[2] - user[1]
ans[,2] <- y + (ans[,2]-y) * sy/sx * pin[1]/pin[2]
polygon(x=ans[,1], y=ans[,2], col=col, border=border)
}
So, when I call:
plot(1:2, type="p", col="white", xlim=c(-5,5), ylim=c(-10,10))
my_arrow(0,0, rotate=45, cex=5)
I get what I wanted:
I have a triangle living in a 3d space, I want to plot only the edges of the triangle in an efficient way, since I will repeat it for a huge number of triangles.
I am able to plot it as a coloured surface using the package rgl:
rgl.open()
vertices = c(
0,0,0,1,
1,1,0,1,
0,0,1,1)
col = "blue"
shade3d( tmesh3d(vertices,indices) , col=col)
bg3d(color = "white")
But what I want is just the 3 lines connecting the points.
What I tried was:
vertices = c(
0,0,0,
1,1,0,
0,0,1)
rgl.lines(x=c(vertices[1],vertices[4]),y=c(vertices[2],vertices[5]),z=c(vertices[3],vertices[6]),col="black")
rgl.lines(x=c(vertices[4],vertices[7]),y=c(vertices[5],vertices[8]),z=c(vertices[6],vertices[9]),col="black")
rgl.lines(x=c(vertices[7],vertices[1]),y=c(vertices[8],vertices[2]),z=c(vertices[9],vertices[3]),col="black")
bg3d(color = "white")
However, this approach is considerably slower than the first one (around 10 times when tried on a real mesh).
I am wondering, is there a way to plot with shade3d the triangles as transparent with only their edges?
You should just be able to something like this:
wire3d( tmesh3d(vertices,indices) , col=col)
works for me.
Example using something I found in the rgl docs:
library(rgl)
# A trefoil knot
open3d()
theta <- seq(0, 2*pi, len = 25)
cen <- cbind( sin(theta) + 2*sin(2*theta),
2*sin(3*theta),
cos(theta) - 2*cos(2*theta) )
e1 <- cbind( cos(theta) + 4*cos(2*theta),
6*cos(3*theta),
sin(theta) + 4*sin(2*theta) )
knot <- cylinder3d( center=cen,e1=e1,radius = 0.8, closed = TRUE)
wire3d(addNormals(subdivision3d(knot, depth = 2)), col = "green")
yields:
where as using:
shade3d(addNormals(subdivision3d(knot, depth = 2)), col = "green")
yields:
i tried something like this a few weeks ago (Stackoverflow question):
library("rgl")
CCl4=c(5,5,5,10)
Luminol=c(0.01,0.001,0.005,0.005)
Na2CO3=c(0.01,0.01,0.1,0.05)
plot3d( Luminol, Na2CO3, CCl4, type = "s")
for(i in 1:4){
for(k in 1:4){
segments3d(x=Luminol[c(i,k)],y=Na2CO3[c(i,k)],z=CCl4[c(i,k)])
}
}
I hope this provide a guide for solving your problem
I am looking for a way to add an arrow to a plot in base R, such that the arrow will be filled with a grey-gradient color, like this:
I have seen this solution, but that seems quite complex and not that flexible: I need to draw a great mahy arrows, in a great many plots, all potentially with different length and width.
I am aware of the shape package, but that seems to only be able to fill arrowheads, and does not provide a fillable "base" of the arrow.
Any suggestions?
Here is one way to get you started, along the lines of #MrFlick's suggestion. You probably want to encapsulate this inside a function that will allow you to exert more influence over the size of the arrowhead, the width of the base and arrow head, the smoothness of the gradient, etc.
#empty box
plot(c(-1, 2), c(-1, 10), ,type="n",axes=FALSE, xlab = "", ylab = "")
# plot the arrow, without a fill
polygon(c(0,0,-.25,.5,1.25,1,1,0), y = c(0,6,6, 8,6,6,0,0), border = NA)
# create gradient colors
nslices = 100
cols <- colorRampPalette(colors = c("white", "black"))(nslices)
# split the base of the arrow in nslices and fill each progressively
ys <- seq(0,6, len = nslices + 1)
for (i in 1:nslices) {
polygon(c(0,0,1,1), c(ys[i], ys[i+1], ys[i+1], ys[i]), col = cols[i], border = NA)
}
# add a filled arrowhead
polygon(c(-.25, .5, 1.25, -.25), c(6, 8, 6, 6), col = "black")
This would get you an arrow like this:
HTH, Peter
using the arrow defined in the linked question, now in base graphics
# create a black arrow, saved as external file
library(grid)
png("mask.png")
grid.polygon(c(-0.06, 0.06, 0.06, 0.15, 0, -0.15, -0.06),
c(-5, -5, 2.5, 2, 5, 2, 2.5), gp=gpar(fill="black"),
def="native",
vp=viewport(xs=c(-0.15, 0.15), ys=c(-5, 5)))
dev.off()
## read back in as colour matrix
library(png)
m <- readPNG("mask.png", native=FALSE)
mask <- matrix(rgb(m[,,1],m[,,2],m[,,3]),
nrow=nrow(m))
rmat <- matrix(grey(seq(0,1,length=nrow(m))),
nrow=nrow(m), ncol=ncol(m))
rmat[mask == "#FFFFFF"] <- NA
## use in base plot
set.seed(12321)
plot(1:10, rnorm(10))
rasterImage(rmat, 2, -1, 2.5, 0)
Edit:
you don't have to use a temporary file to create the mask, it's just (much more) convenient than fiddling with logical matrices. Here's a starting point to create the arrow directly as a matrix,
marrow <- function(nr=500, nc=300, col = grey(seq(0, 1, length=nr))){
skin <- matrix(col, nrow=nr, ncol=nc)
head <- lower.tri(matrix(TRUE, nrow=nc/2, ncol=nc/2))
skull <- cbind(head[seq(nc/2,1),], head[seq(nc/2,1),seq(nc/2,1)])
rib <- matrix(TRUE, nrow=nr-nrow(skull), ncol=nc/4)
trunk <- cbind(rib, !rib, !rib, rib)
skeleton <- rbind(skull, trunk)
skin[skeleton] <- NA_character_
skin
}
grid.newpage()
grid.raster(marrow(),
width = unit(1,"npc"),
height=unit(1,"npc"))
I have a distance sequence, which I would like to plot of a line in spatstat. Example:
library(spatstat)
x <- c(0.3, 5)
y <- c(3, 1.2)
range.x <- c(0, max(x)+0.2)
range.y <- c(0, max(y)+0.2)
owin <- owin(range.x, range.y)
the.line <- psp(x0 = x[1],x1 = x[2],y0 = y[1],y1 = y[2], window = owin)
plot(the.line)
seqs <- data.frame(name = seq(1,7), distance = c(0.12, 0.3, 0.45, 0.5, 0.7, 0.89, 0.95))
lengths <- seqs$distance*lengths.psp(the.line)
I would like to plot lengths on top of the.line using seqs$name as labels in a following way (labels added with Illustrator):
Would anyone know how to do this? Help would be very much appreciated!
The text function will allow you to add text to an existing plot. Whether you can rotate the text or not depends on the graphics device that you are using, see ?par sections on 'crt' and 'srt'. Also see the 'adj' argument to text for how to get the text above the line rather than obscuring the line.
This all assumes that the plotting is being done in base graphics.
The following worked for me on windows (using the default windows graphics device) after running the code above:
x.new <- seqs$distance*x[2] + (1-seqs$distance)*x[1]
y.new <- seqs$distance*y[2] + (1-seqs$distance)*y[1]
tmp.x <- grconvertX(x, to='inches')
tmp.y <- grconvertY(y, to='inches')
theta <- atan2(diff(tmp.y),diff(tmp.x))*180/pi
text( x.new, y.new, seqs$name, adj=c(0,0), srt=theta )