Related
I am trying to draw a plot in R that its x axis and y axis have an arrow in their tips. I googled it and did not found anything helpful. I found it how to do it in Python in this page.
I want something like this picture:
My attempt:
I remove the plot frame and add axis separately.
x=seq(0,5,by=.1)
y=x*x
plot(x, y, axes = FALSE, frame.plot = FALSE, type="l")
box(bty="l")
axis(1)
axis(2)
Function Arrowhead from package shape draws arrowhead-shaped polygons centered at user-supplied points. To draw arrowheads at the top left and bottom right corners of the plot region pointing in the direction of your axes, you can do:
usr <- par("usr")
shape::Arrowhead(
x0 = usr[1:2],
y0 = usr[4:3],
angle = c(90, 0),
xpd = TRUE
)
where par("usr") is a vector c(left, right, bottom, top) specifying the boundary of the plot region in user coordinates. xpd = TRUE ensures that arrowheads are not clipped to the plot region.
If you want to avoid installing a package, then note that base R has arrows, but it does not draw filled arrowheads and it complains if you ask for a zero-length arrow (i.e., an arrow with a head but no shaft). One way around the latter issue is to draw arrows that are collinear with the axes:
usr <- par("usr")
arrows(
x0 = usr[1L],
x1 = usr[1:2],
y0 = usr[3L],
y1 = usr[4:3],
length = 0.1,
angle = 20,
xpd = TRUE
)
As you might expect, both functions have optional arguments that you can use to adjust the appearance of the arrow(head)s.
I use scatterplot3d to plot 3D with R. The orientation of the y-axis label bothers me because it is vertical and not parallel to the y-axis.
Is there a way to rotate the label and adjust its angle? Unfortunately, I didn't finde anything in the documentation.
If you don't have to draw many plots and are willing to adjust values manually, you can pass ylab = "" when making the 3d scatter and then add text later on with appropriate srt value. srt allows you to rotate text at desired angle. Note that x and y when adding text is different from x and y of 3d scatter.
set.seed(42)
scatterplot3d(rnorm(20), rnorm(20), rnorm(20), ylab = "")
text(x = 5, y = -2.5, "Y-axis", srt = 45)
Using scale.y
set.seed(42)
scatterplot3d(rnorm(20), rnorm(20), rnorm(20), ylab = "", scale.y = 2)
text(x = 6.5, y = -1.5, "Somewhat longer Y-axis", srt = 45)
TL;DR: What is the most efficient way to crop a rectangular image to a circle?
Explanation/Background:
I'm working on some code in R that will display Spotify artist images as circles instead of the default rectanges/squares. I couldn't find any packages or commands that crop images in R, especially to a circle, so I wrote my own function, circ, which reads 3-Dimensional (or 4-Dimensional) RGB(A) arrays and crops them to a circle using the parametric equation of a circle to determine the x values for every unique y. Here's my psuedocode:
Given an RGB(A) array:
Find the center of the image, radius = min(x coord, y coord)
Pre-crop the image to a square of dimensions 2r x 2r
For every unique y value:
Determine the x coordinates on the circle
Make pixels outside of the circle transparent
Return the cropped image as an RGBA array
This function is a tremendous improvement over my previous one, which checked the position of every pixel to see if it was inside or outside of the circle, but I still feel like it could be sped up further.
Is there a way I could check maybe half of the y-values instead of all of them, and mirror across the circle? Is there an actual cropping function I could use instead? Any and all help is much appreciated!
Edited to add some copy-paste-run code (thanks #lukeA):
My original cropping method:
circ = function(a){
# First part of the function finds the radius of the circle and crops the image accordingly
xc = floor(dim(a[,,1])[2]/2) # X coordinate of the center
yc = floor(dim(a[,,1])[1]/2) # Y coordinate of the center
r = min(xc, yc) - 1 # Radius is the smaller of the two -1 to avoid reading nonexistent data
ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)], # Read in the cropped image
a[,,2][(yc-r):(yc+r),(xc-r):(xc+r)], # Of dimensions 2r x 2r, centered
a[,,3][(yc-r):(yc+r),(xc-r):(xc+r)], # Around (xc, yc)
rep(1,length(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)]))), # Add fourth alpha layer
dim = c(length((yc-r):(yc+r)),length((xc-r):(xc+r)),4))
if(yc > xc) yc = xc else if(xc > yc) xc = yc # Re-evaluate your center for the cropped image
xmax = dim(ma[,,1])[2]; ymax = dim(ma[,,1])[1] # Find maximum x and y values
# Second part of the function traces circle by the parametric eqn. and makes outside pixels transparent
for(y in 1:ymax){ # For every y in the cropped image
theta = asin((y - yc) / r) # y = yc + r * sin(theta) by parametric equation for a circle
x = xc + r * cos(theta) # Then we can find the exact x coordinate using the same formula
x = which.min(abs(1:xmax - x)) # Find which x in array is closest to exact coordinate
if(!x - xc == 0 && !xmax - x == 0){ # If you're not at the "corners" of the circle
ma[,,4][y,c(1:(xmax-x), (x+1):xmax)] = 0 # Make pixels on either side of the circle trans.
} else if(!xmax - x == 0) ma[,,4][y,] = 0 # This line makes tops/bottoms transparent
}
return(ma)
}
library(jpeg)
a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg")
par(bg = "grey"); plot(1:2, type="n") # Color background to check transparency
rasterImage(circ(a),1,1,2,2)
Modified version (thanks #dww):
dwwcirc = function(a){
# First part of the function finds the radius of the circle and crops the image accordingly
xc = floor(dim(a[,,1])[2]/2) # X coordinate of the center
yc = floor(dim(a[,,1])[1]/2) # Y coordinate of the center
r = min(xc, yc) - 1 # Radius is the smaller of the two -1 to avoid reading nonexistent data
ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)], # Read in the cropped image
a[,,2][(yc-r):(yc+r),(xc-r):(xc+r)], # Of dimensions 2r x 2r, centered
a[,,3][(yc-r):(yc+r),(xc-r):(xc+r)], # Around (xc, yc)
rep(1,length(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)]))), # Add fourth alpha layer
dim = c(length((yc-r):(yc+r)),length((xc-r):(xc+r)),4))
if(yc > xc) yc = xc else if(xc > yc) xc = yc # Re-evaluate your center for the cropped image
xmax = dim(ma[,,1])[2]; ymax = dim(ma[,,1])[1] # Find maximum x and y values
x = rep(1:xmax, ymax) # Vector containing all x values
y = rep(1:ymax, each=xmax) # Value containing all y values
r2 = r^2
ma[,,4][which(( (x-xc)^2 + (y-yc)^2 ) > r2)] = 0
return(ma)
}
library(jpeg)
a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg")
par(bg = "grey"); plot(1:2, type="n") # Color background to check transparency
rasterImage(dwwcirc(a),1,1,2,2)
Version using magick and plotrix (thanks #lukeA and #hrbrmstr):
library(plotrix)
jpeg(tf <- tempfile(fileext = "jpeg"), 1000, 1000)
par(mar = rep(0,4), yaxs="i", xaxs = "i")
plot(0, type = "n", ylim = c(0, 1), xlim = c(0,1), axes=F, xlab=NA, ylab=NA)
draw.circle(.5,.5,.5,col="black")
dev.off()
library(magick)
img = image_read("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg")
mask = image_read(tf)
radius = min(c(image_info(img)$width, image_info(img)$height))
mask = image_scale(mask, as.character(radius))
par(bg = "grey"); plot(1:2, type="n")
rasterImage(as.raster(image_composite(image = mask, composite_image = img, operator = "plus")),1,1,2,2)
I dunno about "efficiency", but I would not reinvent the wheel here. Like suggested in the comments by #hrbrmstr, you may wanna try magick, which gives you all the flexibility you might need:
png(tf <- tempfile(fileext = ".png"), 1000, 1000)
par(mar = rep(0,4), yaxs="i", xaxs="i")
plot(0, type = "n", ylim = c(0,1), xlim=c(0,1), axes=F, xlab=NA, ylab=NA)
plotrix::draw.circle(.5,0.5,.5, col="black")
dev.off()
library(magick)
fn <- "https://www.gravatar.com/avatar/f57aba01c52e5c67696817eb87df84f2?s=328&d=identicon&r=PG&f=1"
img <- image_read(fn)
mask <- image_read(tf)
mask <- image_scale(mask, as.character(image_info(img)$width))
Now
img
mask
image_composite(mask, img, "plus")
image_composite(mask, img, "minus")
Some other composite operators:
# https://www.imagemagick.org/Magick++/Enumerations.html#CompositeOperator
ops <- c("over", "in", "out", "atop", "xor", "plus", "minus", "add", "difference", "multiply")
for (op in ops) {
print(image_composite(img, mask, op))
print(op)
readline()
}
You can improve the performance of your circ function if you do a vectorised subset-assign operation on your array (instead of looping) using the the fact that (x-xc)^2 +(y-yc)^2 > r^2 for points outside a circle.
To do this, replace the 2nd part of your function with
# Second part of the function traces circle by...
x = rep(1:xmax, ymax)
y = rep(1:ymax, each=xmax)
r2 = r^2
ma[,,4][which(( (x-xc)^2 + (y-yc)^2 ) > r2)] <- 0
return(ma)
I need some help with axis labels in base R plotting, thanks in advance for any guidance!
What I need:
In R base plot() I would like to rotate my axis(3, ...) label to -90 degrees to get the following output:
(note that I have rotated the pic outside R)
Why I need it (big picture):
I am using labcurve for curve annotation and strangely enough for my data the annotation results are visually waay better if applied to the -90 degree rotated graph. After running labcurve I can rotate the resulting R-generated PDF back 90 degrees in LaTeX.
What I have tried:
#1
I know that this is governed by the las option in par with the following options:
0: always parallel to the axis [default],
1: always horizontal,
2: always perpendicular to the axis,
3: always vertical.
However, these four options available only cover the two angles 0 and 90 degrees as either of the following:
plot(x=c(0,10), y=c(0,1), type='n', xlab='',ylab='', axes=FALSE)
lines(x=c(0,7,7,10), y=c(0,0.33,0.67,1))
axis(2, at=c(0,1), labels=c('',''), las=2)
xlabels <- c('0','10')
axis(3, at=c(0,10), labels=xlabels, las=0)
or
axis(3, at=c(0,10), labels=xlabels, las=1)
axis(3, at=c(0,10), labels=xlabels, las=2)
or
axis(3, at=c(0,10), labels=xlabels, las=3)
#2:
One could think of str but according to the doc:
Note that string/character rotation via argument srt to par does not
affect the axis labels.
Thanks again!
The general procedure for creating rotated axis labels is described in R FAQ 7.27. Here's a modified example which hopefully suits your needs.
# some toy data
x <- c(0, 2, 6, 10)
y <- sample(1:4)
# Increase top margin to make room for rotated labels
par(mar = c(5, 4, 7, 2) + 0.1)
# Create plot without axis or labels
plot(x, y, type = "l", axes = FALSE, xlab = "", ylab = "")
# positions for tick marks
atx <- range(x)
aty <- range(y)
# x axis without labels
axis(side = 3, at = atx, labels = FALSE)
# y axis without labels
axis(side = 2, at = aty, labels = FALSE)
# add -90 rotated x axis labels
text(x = atx, y = par("usr")[4] + 0.25, srt = -90, adj = 1,
labels = atx, xpd = TRUE)
I have the following plot that I want to use with plot3D.
The commands I use are the following:
library("plot3D");
N <- 100
xs <- runif(N) * 87
ys <- runif(N) * 61
zs <- runif(N)*50 + 154
# scatter + surface
scatter3D(xs, ys, zs, ticktype = "detailed", pch = 16,
bty = "f", xlim = c(1, 87), ylim = c(1,61), zlim = c(94, 215))
This basically plots what I want (other than the legend, which I believe I can remove), but not quite in the right format - I want it to be surfacy, and not just a scatter plot. With the regular plot command, it is relatively easy to add a line that connects between the dots, but I am not sure how to do it in this case.
There is a surf parameter for scatter3D(), which I believe could be used to solve that, but I am not sure what help means by "a fitted surface" and how to create the surface manually. I would expect to just have a way of automatically drawing the surface (as a smooth function).
EDIT: By "surfacy" I am referring to a 3D generalization of a smooth line that goes through points in a 2D plot.
EDIT: Here is an example of what I want to do with the same code above.
par(mfrow = c(1, 1))
# surface = volcano
M <- mesh(1:nrow(volcano), 1:ncol(volcano))
# 100 points above volcano
N <- 100
xs <- runif(N) * 87
ys <- runif(N) * 61
zs <- runif(N)*50 + 154
# scatter + surface
scatter3D(xs, ys, zs, ticktype = "detailed", pch = 16,
bty = "f", xlim = c(1, 87), ylim = c(1,61), zlim = c(94, 215),
surf = list(x = M$x, y = M$y, z = volcano,
NAcol = "grey", shade = 0.1))
This set of commands also creates a surface (the "mountain" like part). What I am not sure is how to define this surface from a set of points (i.e., how to create the "volcano" matrix). Also, I am not interested in having the scattered dots, only a fixed surface which is determined from a set of scattered points.